From fa9057724142dc9f0682bcda22c25567b2077539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Esser?= Date: Thu, 7 Sep 2017 22:46:31 +0200 Subject: [PATCH] Add support for online-accounts --- Makefile.am | 6 +- configure.ac | 50 + libgd/.gitignore | 20 + libgd/Makefile.am | 279 ++++ libgd/README | 131 ++ libgd/libgd.doap | 33 + libgd/libgd.m4 | 151 ++ libgd/libgd/gd-entry-focus-hack.c | 89 ++ libgd/libgd/gd-entry-focus-hack.h | 30 + libgd/libgd/gd-header-bar.c | 1350 ++++++++++++++++ libgd/libgd/gd-header-bar.h | 75 + libgd/libgd/gd-header-button.c | 488 ++++++ libgd/libgd/gd-header-button.h | 67 + libgd/libgd/gd-icon-utils.c | 253 +++ libgd/libgd/gd-icon-utils.h | 42 + libgd/libgd/gd-main-icon-view.c | 425 +++++ libgd/libgd/gd-main-icon-view.h | 74 + libgd/libgd/gd-main-list-view.c | 346 ++++ libgd/libgd/gd-main-list-view.h | 80 + libgd/libgd/gd-main-toolbar.c | 515 ++++++ libgd/libgd/gd-main-toolbar.h | 111 ++ libgd/libgd/gd-main-view-generic.c | 330 ++++ libgd/libgd/gd-main-view-generic.h | 117 ++ libgd/libgd/gd-main-view.c | 1183 ++++++++++++++ libgd/libgd/gd-main-view.h | 96 ++ libgd/libgd/gd-margin-container.c | 379 +++++ libgd/libgd/gd-margin-container.h | 75 + libgd/libgd/gd-notification.c | 875 ++++++++++ libgd/libgd/gd-notification.h | 67 + libgd/libgd/gd-revealer.c | 744 +++++++++ libgd/libgd/gd-revealer.h | 68 + libgd/libgd/gd-stack-switcher.c | 377 +++++ libgd/libgd/gd-stack-switcher.h | 66 + libgd/libgd/gd-stack.c | 1419 +++++++++++++++++ libgd/libgd/gd-stack.h | 85 + libgd/libgd/gd-styled-text-renderer.c | 124 ++ libgd/libgd/gd-styled-text-renderer.h | 79 + libgd/libgd/gd-tagged-entry-default.css | 34 + libgd/libgd/gd-tagged-entry.c | 1263 +++++++++++++++ libgd/libgd/gd-tagged-entry.gresource.xml | 6 + libgd/libgd/gd-tagged-entry.h | 117 ++ libgd/libgd/gd-toggle-pixbuf-renderer.c | 269 ++++ libgd/libgd/gd-toggle-pixbuf-renderer.h | 75 + libgd/libgd/gd-two-lines-renderer.c | 628 ++++++++ libgd/libgd/gd-two-lines-renderer.h | 75 + libgd/libgd/gd-types-catalog.c | 142 ++ libgd/libgd/gd-types-catalog.h | 31 + libgd/libgd/gd.h | 92 ++ libgd/test-header-bar.c | 71 + libgd/test-revealer.c | 33 + libgd/test-stack.c | 177 ++ libgd/test-tagged-entry.c | 111 ++ panels/Makefile.am | 6 + panels/online-accounts/Makefile.am | 47 + .../cc-online-accounts-panel.c | 984 ++++++++++++ .../cc-online-accounts-panel.h | 65 + ...nnamon-online-accounts-panel.desktop.in.in | 14 + .../online-accounts/icons/16x16/Makefile.am | 14 + .../icons/16x16/cs-online-accounts.png | Bin 0 -> 917 bytes .../online-accounts/icons/22x22/Makefile.am | 14 + .../icons/22x22/cs-online-accounts.png | Bin 0 -> 1431 bytes .../online-accounts/icons/24x24/Makefile.am | 14 + .../icons/24x24/cs-online-accounts.png | Bin 0 -> 1469 bytes .../online-accounts/icons/256x256/Makefile.am | 14 + .../icons/256x256/cs-online-accounts.png | Bin 0 -> 42185 bytes .../online-accounts/icons/32x32/Makefile.am | 14 + .../icons/32x32/cs-online-accounts.png | Bin 0 -> 2170 bytes .../online-accounts/icons/48x48/Makefile.am | 14 + .../icons/48x48/cs-online-accounts.png | Bin 0 -> 3747 bytes panels/online-accounts/icons/Makefile.am | 23 + .../online-accounts/online-accounts-module.c | 33 + .../online-accounts.gresource.xml | 6 + panels/online-accounts/online-accounts.ui | 282 ++++ 73 files changed, 15364 insertions(+), 3 deletions(-) create mode 100644 libgd/.gitignore create mode 100644 libgd/Makefile.am create mode 100644 libgd/README create mode 100644 libgd/libgd.doap create mode 100644 libgd/libgd.m4 create mode 100644 libgd/libgd/gd-entry-focus-hack.c create mode 100644 libgd/libgd/gd-entry-focus-hack.h create mode 100644 libgd/libgd/gd-header-bar.c create mode 100644 libgd/libgd/gd-header-bar.h create mode 100644 libgd/libgd/gd-header-button.c create mode 100644 libgd/libgd/gd-header-button.h create mode 100644 libgd/libgd/gd-icon-utils.c create mode 100644 libgd/libgd/gd-icon-utils.h create mode 100644 libgd/libgd/gd-main-icon-view.c create mode 100644 libgd/libgd/gd-main-icon-view.h create mode 100644 libgd/libgd/gd-main-list-view.c create mode 100644 libgd/libgd/gd-main-list-view.h create mode 100644 libgd/libgd/gd-main-toolbar.c create mode 100644 libgd/libgd/gd-main-toolbar.h create mode 100644 libgd/libgd/gd-main-view-generic.c create mode 100644 libgd/libgd/gd-main-view-generic.h create mode 100644 libgd/libgd/gd-main-view.c create mode 100644 libgd/libgd/gd-main-view.h create mode 100644 libgd/libgd/gd-margin-container.c create mode 100644 libgd/libgd/gd-margin-container.h create mode 100644 libgd/libgd/gd-notification.c create mode 100644 libgd/libgd/gd-notification.h create mode 100644 libgd/libgd/gd-revealer.c create mode 100644 libgd/libgd/gd-revealer.h create mode 100644 libgd/libgd/gd-stack-switcher.c create mode 100644 libgd/libgd/gd-stack-switcher.h create mode 100644 libgd/libgd/gd-stack.c create mode 100644 libgd/libgd/gd-stack.h create mode 100644 libgd/libgd/gd-styled-text-renderer.c create mode 100644 libgd/libgd/gd-styled-text-renderer.h create mode 100644 libgd/libgd/gd-tagged-entry-default.css create mode 100644 libgd/libgd/gd-tagged-entry.c create mode 100644 libgd/libgd/gd-tagged-entry.gresource.xml create mode 100644 libgd/libgd/gd-tagged-entry.h create mode 100644 libgd/libgd/gd-toggle-pixbuf-renderer.c create mode 100644 libgd/libgd/gd-toggle-pixbuf-renderer.h create mode 100644 libgd/libgd/gd-two-lines-renderer.c create mode 100644 libgd/libgd/gd-two-lines-renderer.h create mode 100644 libgd/libgd/gd-types-catalog.c create mode 100644 libgd/libgd/gd-types-catalog.h create mode 100644 libgd/libgd/gd.h create mode 100644 libgd/test-header-bar.c create mode 100644 libgd/test-revealer.c create mode 100644 libgd/test-stack.c create mode 100644 libgd/test-tagged-entry.c create mode 100644 panels/online-accounts/Makefile.am create mode 100644 panels/online-accounts/cc-online-accounts-panel.c create mode 100644 panels/online-accounts/cc-online-accounts-panel.h create mode 100644 panels/online-accounts/cinnamon-online-accounts-panel.desktop.in.in create mode 100644 panels/online-accounts/icons/16x16/Makefile.am create mode 100644 panels/online-accounts/icons/16x16/cs-online-accounts.png create mode 100644 panels/online-accounts/icons/22x22/Makefile.am create mode 100644 panels/online-accounts/icons/22x22/cs-online-accounts.png create mode 100644 panels/online-accounts/icons/24x24/Makefile.am create mode 100644 panels/online-accounts/icons/24x24/cs-online-accounts.png create mode 100644 panels/online-accounts/icons/256x256/Makefile.am create mode 100644 panels/online-accounts/icons/256x256/cs-online-accounts.png create mode 100644 panels/online-accounts/icons/32x32/Makefile.am create mode 100644 panels/online-accounts/icons/32x32/cs-online-accounts.png create mode 100644 panels/online-accounts/icons/48x48/Makefile.am create mode 100644 panels/online-accounts/icons/48x48/cs-online-accounts.png create mode 100644 panels/online-accounts/icons/Makefile.am create mode 100644 panels/online-accounts/online-accounts-module.c create mode 100644 panels/online-accounts/online-accounts.gresource.xml create mode 100644 panels/online-accounts/online-accounts.ui diff --git a/Makefile.am b/Makefile.am index 9e8b4c74..3b169f49 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,8 +1,8 @@ -ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} +ACLOCAL_AMFLAGS = -I m4 -I libgd ${ACLOCAL_FLAGS} -SUBDIRS = po shell panels +SUBDIRS = libgd po shell panels -DIST_SUBDIRS = po shell panels +DIST_SUBDIRS = libgd po shell panels MAINTAINERCLEANFILES = \ $(srcdir)/INSTALL \ diff --git a/configure.ac b/configure.ac index 343b0dc1..f61d4b02 100644 --- a/configure.ac +++ b/configure.ac @@ -101,6 +101,7 @@ LIBNOTIFY_REQUIRED_VERSION=0.7.3 CINNAMON_DESKTOP_REQUIRED_VERSION=1.0.0 CSD_REQUIRED_VERSION=1.0.0 LIBWACOM_REQUIRED_VERSION=0.7 +GOA_REQUIRED_VERSION=3.21.5 COMMON_MODULES="gtk+-3.0 >= $GTK_REQUIRED_VERSION glib-2.0 >= $GLIB_REQUIRED_VERSION @@ -110,6 +111,8 @@ COMMON_MODULES="gtk+-3.0 >= $GTK_REQUIRED_VERSION cinnamon-desktop >= $CINNAMON_DESKTOP_REQUIRED_VERSION libnotify >= $LIBNOTIFY_REQUIRED_VERSION" +LIBGD_INIT([_view-common static]) + PKG_CHECK_MODULES(LIBCINNAMON_CONTROL_CENTER, $COMMON_MODULES) PKG_CHECK_MODULES(LIBLANGUAGE, $COMMON_MODULES cinnamon-desktop fontconfig) PKG_CHECK_MODULES(LIBSHORTCUTS, $COMMON_MODULES x11) @@ -127,6 +130,8 @@ PKG_CHECK_MODULES(NETWORK_PANEL, $COMMON_MODULES gmodule-2.0 PKG_CHECK_MODULES(REGION_PANEL, $COMMON_MODULES libgnomekbd >= 2.91.91 polkit-gobject-1 >= $POLKIT_REQUIRED_VERSION libxklavier >= 5.1 libgnomekbdui >= 2.91.91) +PKG_CHECK_MODULES(ONLINE_ACCOUNTS_PANEL, $COMMON_MODULES goa-1.0 + goa-backend-1.0 >= $GOA_REQUIRED_VERSION) #---------------------------------------------- # Network Manager Stuff @@ -280,6 +285,35 @@ AM_CONDITIONAL(BUILD_WACOM, test "x$build_wacom" = "xtrue") AC_SUBST(WACOM_PANEL_CFLAGS) AC_SUBST(WACOM_PANEL_LIBS) +# Online accounts +AC_ARG_ENABLE(onlineaacounts, + AC_HELP_STRING([--disable-onlineaccounts], + [build online-accounts panel]), + [case "${enableval}" in + yes) enable_onlineaccounts=yes ;; + no) enable_onlineaccounts=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-onlineaccounts) ;; + esac], + [enable_onlineaccounts=yes]) dnl Enabled by default + +if test "x$enable_onlineaccounts" = xyes; then + PKG_CHECK_MODULES(ONLINE_ACCOUNTS, goa-1.0, + [have_onlineaccounts=yes], have_onlineaccounts=no) + if test "x$have_onlineaccounts" = xno ; then + AC_MSG_ERROR(*** Gnome Online Accounts not found ***) + fi + + AC_DEFINE(BUILD_ONLINE_ACCOUNTS, 1, [Define to 1 to build the online-accounts panel]) + + if test x${have_onlineaccounts} = xyes; then + AC_DEFINE(HAVE_ONLINE_ACCOUNTS, 1, [Define to 1 if Gnome Online Accounts is available]) + fi +fi + +AM_CONDITIONAL(BUILD_ONLINE_ACCOUNTS, [test x$have_onlineaccounts = xyes]) +AC_SUBST(ONLINE_ACCOUNTS_CFLAGS) +AC_SUBST(ONLINE_ACCOUNTS_LIBS) + # This is a hard-dependency for the region and user-accounts panels PKG_CHECK_MODULES(ISOCODES, iso-codes) @@ -382,6 +416,7 @@ fi AC_OUTPUT([ Makefile +libgd/Makefile panels/Makefile panels/common/Makefile panels/display/Makefile @@ -408,6 +443,15 @@ panels/wacom/calibrator/Makefile panels/wacom/cinnamon-wacom-panel.desktop.in panels/datetime/Makefile panels/datetime/po-timezones/Makefile +panels/online-accounts/Makefile +panels/online-accounts/cinnamon-online-accounts-panel.desktop.in +panels/online-accounts/icons/Makefile +panels/online-accounts/icons/16x16/Makefile +panels/online-accounts/icons/22x22/Makefile +panels/online-accounts/icons/24x24/Makefile +panels/online-accounts/icons/32x32/Makefile +panels/online-accounts/icons/48x48/Makefile +panels/online-accounts/icons/256x256/Makefile po/Makefile.in shell/Makefile shell/cinnamon-control-center.desktop.in @@ -443,4 +487,10 @@ else AC_MSG_NOTICE([ Wacom support disabled]) fi +if test "x$have_onlineaccounts" = "xyes"; then + AC_MSG_NOTICE([** Online-Accounts support (Online-Accounts panel)]) +else + AC_MSG_NOTICE([ Online-Accounts support disabled]) +fi + AC_MSG_NOTICE([End options]) diff --git a/libgd/.gitignore b/libgd/.gitignore new file mode 100644 index 00000000..ab3fa34d --- /dev/null +++ b/libgd/.gitignore @@ -0,0 +1,20 @@ +*~ +\#*\# +*.swp +.deps/ +.libs/ +Gd-1.0.gir +Gd-1.0.typelib +gd.gresource.xml +gd-resources.* +test-header-bar +test-revealer +test-stack +test-tagged-entry +Makefile +Makefile.in +gd-1.0.vapi +*.lo +*.o +*.la +.dirstamp diff --git a/libgd/Makefile.am b/libgd/Makefile.am new file mode 100644 index 00000000..026b7101 --- /dev/null +++ b/libgd/Makefile.am @@ -0,0 +1,279 @@ +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-entry-focus-hack.c \ + libgd/gd-entry-focus-hack.h \ + 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__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_MAIN_TOOLBAR +main_toolbar_sources = \ + libgd/gd-main-toolbar.c \ + libgd/gd-main-toolbar.h \ + $(NULL) + +nodist_libgd_la_SOURCES += $(main_toolbar_sources) +EXTRA_DIST += $(main_toolbar_sources) +endif + +if LIBGD_HEADER_BAR +header_bar_sources = \ + libgd/gd-header-bar.c \ + libgd/gd-header-bar.h \ + $(NULL) + +nodist_libgd_la_SOURCES += $(header_bar_sources) +EXTRA_DIST += $(header_bar_sources) + +noinst_PROGRAMS += test-header-bar +test_header_bar_SOURCES = \ + test-header-bar.c \ + $(NULL) +test_header_bar_LDADD = \ + $(LIBGD_LIBS) \ + libgd.la \ + $(NULL) +endif + +if LIBGD__HEADER_BUTTON +header_button_sources = \ + libgd/gd-header-button.c \ + libgd/gd-header-button.h \ + $(NULL) + +nodist_libgd_la_SOURCES += $(header_button_sources) +EXTRA_DIST += $(header_button_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_REVEALER +revealer_sources = \ + libgd/gd-revealer.c \ + libgd/gd-revealer.h \ + $(NULL) + +nodist_libgd_la_SOURCES += $(revealer_sources) +EXTRA_DIST += $(revealer_sources) + +noinst_PROGRAMS += test-revealer +test_revealer_SOURCES = \ + test-revealer.c \ + $(NULL) +test_revealer_LDADD = \ + $(LIBGD_LIBS) \ + libgd.la \ + $(NULL) +endif + +if LIBGD_STACK +stack_sources = \ + libgd/gd-stack.c \ + libgd/gd-stack.h \ + libgd/gd-stack-switcher.c \ + libgd/gd-stack-switcher.h \ + $(NULL) + +nodist_libgd_la_SOURCES += $(stack_sources) +EXTRA_DIST += $(stack_sources) + +noinst_PROGRAMS += test-stack +test_stack_SOURCES = \ + test-stack.c \ + $(NULL) +test_stack_LDADD = \ + $(LIBGD_LIBS) \ + libgd.la \ + $(NULL) +endif + +if LIBGD_TAGGED_ENTRY +tagged_entry_resource_files = $(shell glib-compile-resources --sourcedir=$(srcdir)/libgd --generate-dependencies $(srcdir)/libgd/gd-tagged-entry.gresource.xml) +libgd/gd-tagged-entry-resources.c: libgd/gd-tagged-entry.gresource.xml $(tagged_entry_resource_files) libgd/gd-tagged-entry-resources.h + glib-compile-resources --target=$@ --sourcedir=$(srcdir)/libgd --generate-source --c-name gd_tagged_entry $(srcdir)/libgd/gd-tagged-entry.gresource.xml +libgd/gd-tagged-entry-resources.h: libgd/gd-tagged-entry.gresource.xml $(tagged_entry_resource_files) + glib-compile-resources --target=$@ --sourcedir=$(srcdir)/libgd --generate-header --c-name gd_tagged_entry $(srcdir)/libgd/gd-tagged-entry.gresource.xml + +tagged_entry_sources = \ + libgd/gd-tagged-entry-resources.c \ + libgd/gd-tagged-entry-resources.h \ + libgd/gd-tagged-entry.c \ + libgd/gd-tagged-entry.h \ + $(NULL) + +nodist_libgd_la_SOURCES += $(tagged_entry_sources) +EXTRA_DIST += \ + $(tagged_entry_sources) \ + libgd/gd-tagged-entry.gresource.xml \ + libgd/gd-tagged-entry-default.css \ + $(NULL) +CLEANFILES += \ + libgd/gd-tagged-entry-resources.c \ + libgd/gd-tagged-entry-resources.h \ + $(NULL) + +noinst_PROGRAMS += test-tagged-entry +test_tagged_entry_SOURCES = \ + test-tagged-entry.c \ + $(NULL) +test_tagged_entry_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/libgd/README b/libgd/README new file mode 100644 index 00000000..779f7c97 --- /dev/null +++ b/libgd/README @@ -0,0 +1,131 @@ +===== +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 + (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 + +- header-bar + +- main-icon-view + +- main-toolbar + +- margin-container + +- notification + +- revealer + +- stack + +- 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/libgd/libgd.doap b/libgd/libgd.doap new file mode 100644 index 00000000..944e083f --- /dev/null +++ b/libgd/libgd.doap @@ -0,0 +1,33 @@ + + + libgd + A common GNOME 3 applications submodule + No long description yet + + + + + + + + Cosimo Cecchi + + cosimoc + + + + + Marc-André Lureau + + malureau + + + + diff --git a/libgd/libgd.m4 b/libgd/libgd.m4 new file mode 100644 index 00000000..4a5b086c --- /dev/null +++ b/libgd/libgd.m4 @@ -0,0 +1,151 @@ +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])]) + + # 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]) + ]) + + # 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]) + 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]) + ]) + + # main-toolbar: + AM_CONDITIONAL([LIBGD_MAIN_TOOLBAR],[_LIBGD_IF_OPTION_SET([main-toolbar],[true],[false])]) + _LIBGD_IF_OPTION_SET([main-toolbar],[ + _LIBGD_SET_OPTION([_header-button]) + AC_DEFINE([LIBGD_MAIN_TOOLBAR], [1], [Description]) + ]) + + # header-bar: + AM_CONDITIONAL([LIBGD_HEADER_BAR],[_LIBGD_IF_OPTION_SET([header-bar],[true],[false])]) + _LIBGD_IF_OPTION_SET([header-bar],[ + _LIBGD_SET_OPTION([_header-button]) + AC_DEFINE([LIBGD_HEADER_BAR], [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]) + ]) + + # revealer: + AM_CONDITIONAL([LIBGD_REVEALER],[_LIBGD_IF_OPTION_SET([revealer],[true],[false])]) + _LIBGD_IF_OPTION_SET([revealer],[ + AC_DEFINE([LIBGD_REVEALER], [1], [Description]) + ]) + + # stack: + AM_CONDITIONAL([LIBGD_STACK],[_LIBGD_IF_OPTION_SET([stack],[true],[false])]) + _LIBGD_IF_OPTION_SET([stack],[ + _LIBGD_SET_OPTION([_header-button]) + AC_DEFINE([LIBGD_STACK], [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]) + ]) + + # _header-button: + AM_CONDITIONAL([LIBGD__HEADER_BUTTON],[_LIBGD_IF_OPTION_SET([_header-button],[true],[false])]) + _LIBGD_IF_OPTION_SET([_header-button],[ + AC_DEFINE([LIBGD__HEADER_BUTTON], [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/libgd/libgd/gd-entry-focus-hack.c b/libgd/libgd/gd-entry-focus-hack.c new file mode 100644 index 00000000..8f2860eb --- /dev/null +++ b/libgd/libgd/gd-entry-focus-hack.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2011, 2012 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 + * + */ + +#include "gd-entry-focus-hack.h" + +/* taken from gtk/gtktreeview.c */ +static void +send_focus_change (GtkWidget *widget, + GdkDevice *device, + gboolean in) +{ + GdkDeviceManager *device_manager; + GList *devices, *d; + + device_manager = gdk_display_get_device_manager (gtk_widget_get_display (widget)); + devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER); + devices = g_list_concat (devices, gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_SLAVE)); + devices = g_list_concat (devices, gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_FLOATING)); + + for (d = devices; d; d = d->next) + { + GdkDevice *dev = d->data; + GdkEvent *fevent; + GdkWindow *window; + + if (gdk_device_get_source (dev) != GDK_SOURCE_KEYBOARD) + continue; + + window = gtk_widget_get_window (widget); + if (!window) + continue; + + /* Skip non-master keyboards that haven't + * selected for events from this window + */ + if (gdk_device_get_device_type (dev) != GDK_DEVICE_TYPE_MASTER && + !gdk_window_get_device_events (window, dev)) + continue; + + fevent = gdk_event_new (GDK_FOCUS_CHANGE); + + fevent->focus_change.type = GDK_FOCUS_CHANGE; + fevent->focus_change.window = g_object_ref (window); + fevent->focus_change.in = in; + gdk_event_set_device (fevent, device); + + gtk_widget_send_focus_change (widget, fevent); + + gdk_event_free (fevent); + } + + g_list_free (devices); +} + +void +gd_entry_focus_hack (GtkWidget *entry, + GdkDevice *device) +{ + GtkEntryClass *entry_class; + GtkWidgetClass *entry_parent_class; + + /* Grab focus will select all the text. We don't want that to happen, so we + * call the parent instance and bypass the selection change. This is probably + * really non-kosher. */ + entry_class = g_type_class_peek (GTK_TYPE_ENTRY); + entry_parent_class = g_type_class_peek_parent (entry_class); + (entry_parent_class->grab_focus) (entry); + + /* send focus-in event */ + send_focus_change (entry, device, TRUE); +} diff --git a/libgd/libgd/gd-entry-focus-hack.h b/libgd/libgd/gd-entry-focus-hack.h new file mode 100644 index 00000000..8daa5592 --- /dev/null +++ b/libgd/libgd/gd-entry-focus-hack.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011, 2012 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 + * + */ + +#ifndef __GD_ENTRY_FOCUS_HACK_H__ +#define __GD_ENTRY_FOCUS_HACK_H__ + +#include + +void gd_entry_focus_hack (GtkWidget *entry, + GdkDevice *device); + +#endif/* __GD_ENTRY_FOCUS_HACK_H__ */ diff --git a/libgd/libgd/gd-header-bar.c b/libgd/libgd/gd-header-bar.c new file mode 100644 index 00000000..ff2f068d --- /dev/null +++ b/libgd/libgd/gd-header-bar.c @@ -0,0 +1,1350 @@ +/* + * Copyright (c) 2013 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-header-bar.h" + +#include + +/* TODO + * - wm communication + */ + +#define DEFAULT_SPACING 8 +#define DEFAULT_HPADDING 8 +#define DEFAULT_VPADDING 6 + +struct _GdHeaderBarPrivate +{ + gchar *title; + gchar *subtitle; + + GtkWidget *title_label; + GtkWidget *subtitle_label; + GtkWidget *labels_box; + GtkWidget *labels_sizing_box; + + GtkWidget *custom_title; + GdkWindow *event_window; + gint spacing; + gint hpadding; + gint vpadding; + + GList *children; +}; + +typedef struct _Child Child; +struct _Child +{ + GtkWidget *widget; + GtkPackType pack_type; +}; + +enum { + PROP_0, + PROP_TITLE, + PROP_SUBTITLE, + PROP_CUSTOM_TITLE, + PROP_SPACING, + PROP_HPADDING, + PROP_VPADDING +}; + +enum { + CHILD_PROP_0, + CHILD_PROP_PACK_TYPE, + CHILD_PROP_POSITION +}; + +static void gd_header_buildable_init (GtkBuildableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GdHeaderBar, gd_header_bar, GTK_TYPE_CONTAINER, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + gd_header_buildable_init)); + +static void +boldify_label (GtkWidget *label) +{ + PangoAttrList *attrs; + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_weight_new (PANGO_WEIGHT_BOLD)); + gtk_label_set_attributes (GTK_LABEL (label), attrs); + pango_attr_list_unref (attrs); +} + +static void +smallify_label (GtkWidget *label) +{ + PangoAttrList *attrs; + attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, pango_attr_scale_new (PANGO_SCALE_SMALL)); + gtk_label_set_attributes (GTK_LABEL (label), attrs); + pango_attr_list_unref (attrs); + + gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label"); +} + +static void +get_css_padding_and_border (GtkWidget *widget, + GtkBorder *border) +{ + GtkStyleContext *context; + GtkStateFlags state; + GtkBorder tmp; + + context = gtk_widget_get_style_context (widget); + state = gtk_widget_get_state_flags (widget); + + 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 void +init_sizing_box (GdHeaderBar *bar) +{ + GdHeaderBarPrivate *priv = bar->priv; + GtkWidget *w; + + /* We use this box to always request size for the two labels (title and subtitle) + * as if they were always visible, but then allocate the real label box + * with its actual size, to keep it center-aligned in case we have only the title. + */ + priv->labels_sizing_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + + w = gtk_label_new (NULL); + boldify_label (w); + gtk_box_pack_start (GTK_BOX (priv->labels_sizing_box), w, FALSE, FALSE, 0); + gtk_label_set_line_wrap (GTK_LABEL (w), FALSE); + gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE); + gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_END); + + w = gtk_label_new (NULL); + smallify_label (w); + gtk_box_pack_start (GTK_BOX (priv->labels_sizing_box), w, FALSE, FALSE, 0); + gtk_label_set_line_wrap (GTK_LABEL (w), FALSE); + gtk_label_set_single_line_mode (GTK_LABEL (w), TRUE); + gtk_label_set_ellipsize (GTK_LABEL (w), PANGO_ELLIPSIZE_END); + + gtk_widget_show_all (priv->labels_sizing_box); +} + +static void +gd_header_bar_init (GdHeaderBar *bar) +{ + GtkStyleContext *context; + GdHeaderBarPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE (bar, GD_TYPE_HEADER_BAR, GdHeaderBarPrivate); + bar->priv = priv; + + gtk_widget_set_has_window (GTK_WIDGET (bar), FALSE); + gtk_widget_set_redraw_on_allocate (GTK_WIDGET (bar), FALSE); + + priv->labels_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_parent (priv->labels_box, GTK_WIDGET (bar)); + gtk_widget_set_valign (priv->labels_box, GTK_ALIGN_CENTER); + gtk_widget_show (priv->labels_box); + + init_sizing_box (bar); + + priv->title_label = gtk_label_new (""); + boldify_label (priv->title_label); + gtk_box_pack_start (GTK_BOX (priv->labels_box), priv->title_label, FALSE, FALSE, 0); + gtk_label_set_line_wrap (GTK_LABEL (priv->title_label), FALSE); + gtk_label_set_single_line_mode (GTK_LABEL (priv->title_label), TRUE); + gtk_label_set_ellipsize (GTK_LABEL (priv->title_label), PANGO_ELLIPSIZE_END); + gtk_widget_show (priv->title_label); + + priv->subtitle_label = gtk_label_new (""); + smallify_label (priv->subtitle_label); + gtk_box_pack_start (GTK_BOX (priv->labels_box), priv->subtitle_label, FALSE, FALSE, 0); + gtk_label_set_line_wrap (GTK_LABEL (priv->subtitle_label), FALSE); + gtk_label_set_single_line_mode (GTK_LABEL (priv->subtitle_label), TRUE); + gtk_label_set_ellipsize (GTK_LABEL (priv->subtitle_label), PANGO_ELLIPSIZE_END); + + priv->title = NULL; + priv->custom_title = NULL; + priv->children = NULL; + priv->spacing = DEFAULT_SPACING; + priv->hpadding = DEFAULT_HPADDING; + priv->vpadding = DEFAULT_VPADDING; + + context = gtk_widget_get_style_context (GTK_WIDGET (bar)); + gtk_style_context_add_class (context, "header-bar"); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_HORIZONTAL); +} + +static gint +count_visible_children (GdHeaderBar *bar) +{ + GList *l; + Child *child; + gint n; + + n = 0; + for (l = bar->priv->children; l; l = l->next) + { + child = l->data; + if (gtk_widget_get_visible (child->widget)) + n++; + } + + return n; +} + +static void +gd_header_bar_get_size (GtkWidget *widget, + GtkOrientation orientation, + gint *minimum_size, + gint *natural_size) +{ + GdHeaderBar *bar = GD_HEADER_BAR (widget); + GdHeaderBarPrivate *priv = bar->priv; + GList *l; + gint nvis_children; + gint minimum, natural; + GtkBorder css_borders; + + minimum = natural = 0; + nvis_children = 0; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_width (priv->labels_sizing_box, + &minimum, &natural); + else + gtk_widget_get_preferred_height (priv->labels_sizing_box, + &minimum, &natural); + + for (l = priv->children; l; l = l->next) + { + Child *child = l->data; + + if (gtk_widget_get_visible (child->widget)) + { + gint child_minimum, child_natural; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_width (child->widget, + &child_minimum, &child_natural); + else + gtk_widget_get_preferred_height (child->widget, + &child_minimum, &child_natural); + + if (GTK_ORIENTATION_HORIZONTAL == orientation) + { + minimum += child_minimum; + natural += child_natural; + } + else + { + minimum = MAX (minimum, child_minimum); + natural = MAX (natural, child_natural); + } + nvis_children += 1; + } + } + + if (nvis_children > 0 && orientation == GTK_ORIENTATION_HORIZONTAL) + { + minimum += nvis_children * priv->spacing; + natural += nvis_children * priv->spacing; + } + + get_css_padding_and_border (widget, &css_borders); + + if (GTK_ORIENTATION_HORIZONTAL == orientation) + { + minimum += 2 * priv->hpadding + css_borders.left + css_borders.right; + natural += 2 * priv->hpadding + css_borders.left + css_borders.right; + } + else + { + minimum += 2 * priv->vpadding + css_borders.top + css_borders.bottom; + natural += 2 * priv->vpadding + css_borders.top + css_borders.bottom; + } + + if (minimum_size) + *minimum_size = minimum; + + if (natural_size) + *natural_size = natural; +} + +static void +gd_header_bar_compute_size_for_orientation (GtkWidget *widget, + gint avail_size, + gint *minimum_size, + gint *natural_size) +{ + GdHeaderBar *bar = GD_HEADER_BAR (widget); + GdHeaderBarPrivate *priv = bar->priv; + GList *children; + gint required_size = 0; + gint required_natural = 0; + gint child_size; + gint child_natural; + gint nvis_children; + GtkBorder css_borders; + + avail_size -= 2 * priv->vpadding; + + nvis_children = 0; + + for (children = priv->children; children != NULL; children = children->next) + { + Child *child = children->data; + + if (gtk_widget_get_visible (child->widget)) + { + gtk_widget_get_preferred_width_for_height (child->widget, + avail_size, &child_size, &child_natural); + + required_size += child_size; + required_natural += child_natural; + + nvis_children += 1; + } + } + + gtk_widget_get_preferred_width (priv->labels_sizing_box, + &child_size, &child_natural); + required_size += child_size; + required_natural += child_natural; + + if (nvis_children > 0) + { + required_size += nvis_children * priv->spacing; + required_natural += nvis_children * priv->spacing; + } + + get_css_padding_and_border (widget, &css_borders); + + required_size += 2 * priv->hpadding + css_borders.left + css_borders.right; + required_natural += 2 * priv->hpadding + css_borders.left + css_borders.right; + + if (minimum_size) + *minimum_size = required_size; + + if (natural_size) + *natural_size = required_natural; +} + +static void +gd_header_bar_compute_size_for_opposing_orientation (GtkWidget *widget, + gint avail_size, + gint *minimum_size, + gint *natural_size) +{ + GdHeaderBar *bar = GD_HEADER_BAR (widget); + GdHeaderBarPrivate *priv = bar->priv; + Child *child; + GList *children; + gint nvis_children; + gint computed_minimum = 0; + gint computed_natural = 0; + GtkRequestedSize *sizes; + GtkPackType packing; + gint size; + gint i; + gint child_size; + gint child_minimum; + gint child_natural; + GtkBorder css_borders; + + nvis_children = count_visible_children (bar); + + if (nvis_children <= 0) + return; + + sizes = g_newa (GtkRequestedSize, nvis_children); + size = avail_size - 2 * priv->hpadding; + + /* Retrieve desired size for visible children */ + for (i = 0, children = priv->children; children; children = children->next) + { + child = children->data; + + if (gtk_widget_get_visible (child->widget)) + { + gtk_widget_get_preferred_width (child->widget, + &sizes[i].minimum_size, + &sizes[i].natural_size); + + size -= sizes[i].minimum_size; + sizes[i].data = child; + i += 1; + } + } + + /* Bring children up to size first */ + size = gtk_distribute_natural_allocation (MAX (0, size), nvis_children, sizes); + + /* Allocate child positions. */ + for (packing = GTK_PACK_START; packing <= GTK_PACK_END; ++packing) + { + for (i = 0, children = priv->children; children; children = children->next) + { + child = children->data; + + /* If widget is not visible, skip it. */ + if (!gtk_widget_get_visible (child->widget)) + continue; + + /* If widget is packed differently skip it, but still increment i, + * since widget is visible and will be handled in next loop + * iteration. + */ + if (child->pack_type != packing) + { + i++; + continue; + } + + child_size = sizes[i].minimum_size; + + gtk_widget_get_preferred_height_for_width (child->widget, + child_size, &child_minimum, &child_natural); + + computed_minimum = MAX (computed_minimum, child_minimum); + computed_natural = MAX (computed_natural, child_natural); + } + i += 1; + } + + gtk_widget_get_preferred_height (priv->labels_sizing_box, + &child_minimum, &child_natural); + computed_minimum = MAX (computed_minimum, child_minimum); + computed_natural = MAX (computed_natural, child_natural); + + get_css_padding_and_border (widget, &css_borders); + + computed_minimum += 2 * priv->vpadding + css_borders.top + css_borders.bottom; + computed_natural += 2 * priv->vpadding + css_borders.top + css_borders.bottom; + + if (minimum_size) + *minimum_size = computed_minimum; + + if (natural_size) + *natural_size = computed_natural; +} + +static void +gd_header_bar_get_preferred_width (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + gd_header_bar_get_size (widget, GTK_ORIENTATION_HORIZONTAL, minimum_size, natural_size); +} + +static void +gd_header_bar_get_preferred_height (GtkWidget *widget, + gint *minimum_size, + gint *natural_size) +{ + gd_header_bar_get_size (widget, GTK_ORIENTATION_VERTICAL, minimum_size, natural_size); +} + +static void +gd_header_bar_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + gd_header_bar_compute_size_for_orientation (widget, height, minimum_width, natural_width); +} + +static void +gd_header_bar_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + gd_header_bar_compute_size_for_opposing_orientation (widget, width, minimum_height, natural_height); +} + +static void +gd_header_bar_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GdHeaderBar *bar = GD_HEADER_BAR (widget); + GdHeaderBarPrivate *priv = bar->priv; + GtkRequestedSize *sizes; + gint width, height; + gint nvis_children; + gint title_minimum_size; + gint title_natural_size; + gint side[2]; + GList *l; + gint i; + Child *child; + GtkPackType packing; + GtkAllocation child_allocation; + gint x; + gint child_size; + GtkTextDirection direction; + GtkBorder css_borders; + + gtk_widget_set_allocation (widget, allocation); + + if (gtk_widget_get_realized (widget)) + gdk_window_move_resize (priv->event_window, + allocation->x, + allocation->y, + allocation->width, + allocation->height); + + direction = gtk_widget_get_direction (widget); + nvis_children = count_visible_children (bar); + sizes = g_newa (GtkRequestedSize, nvis_children); + + get_css_padding_and_border (widget, &css_borders); + width = allocation->width - nvis_children * priv->spacing - + 2 * priv->hpadding - css_borders.left - css_borders.right; + height = allocation->height - 2 * priv->vpadding - css_borders.top - css_borders.bottom; + + i = 0; + for (l = priv->children; l; l = l->next) + { + child = l->data; + if (!gtk_widget_get_visible (child->widget)) + continue; + + gtk_widget_get_preferred_width_for_height (child->widget, + height, + &sizes[i].minimum_size, + &sizes[i].natural_size); + width -= sizes[i].minimum_size; + i++; + } + + if (priv->custom_title) + { + gtk_widget_get_preferred_width_for_height (priv->custom_title, + height, + &title_minimum_size, + &title_natural_size); + } + else + { + gtk_widget_get_preferred_width_for_height (priv->labels_box, + height, + &title_minimum_size, + &title_natural_size); + } + width -= title_natural_size; + + width = gtk_distribute_natural_allocation (MAX (0, width), nvis_children, sizes); + + side[0] = side[1] = 0; + for (packing = GTK_PACK_START; packing <= GTK_PACK_END; packing++) + { + child_allocation.y = allocation->y + priv->vpadding + css_borders.top; + child_allocation.height = height; + if (packing == GTK_PACK_START) + x = allocation->x + priv->hpadding + css_borders.left; + else + x = allocation->x + allocation->width - priv->hpadding - css_borders.right; + + if (packing == GTK_PACK_START) + { + l = priv->children; + i = 0; + } + else + { + l = g_list_last (priv->children); + i = nvis_children - 1; + } + + for (; l != NULL; (packing == GTK_PACK_START) ? (l = l->next) : (l = l->prev)) + { + child = l->data; + if (!gtk_widget_get_visible (child->widget)) + continue; + + if (child->pack_type != packing) + goto next; + + child_size = sizes[i].minimum_size; + + child_allocation.width = child_size; + + if (packing == GTK_PACK_START) + { + child_allocation.x = x; + x += child_size; + x += priv->spacing; + } + else + { + x -= child_size; + child_allocation.x = x; + x -= priv->spacing; + } + + side[packing] += child_size + priv->spacing; + + if (direction == GTK_TEXT_DIR_RTL) + child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width; + + gtk_widget_size_allocate (child->widget, &child_allocation); + + next: + if (packing == GTK_PACK_START) + i++; + else + i--; + } + } + + child_allocation.y = allocation->y + priv->vpadding + css_borders.top; + child_allocation.height = height; + + width = MAX(side[0], side[1]); + + if (allocation->width - 2 * width >= title_natural_size) + child_size = MIN (title_natural_size, allocation->width - 2 * width); + else if (allocation->width - side[0] - side[1] >= title_natural_size) + child_size = MIN (title_natural_size, allocation->width - side[0] - side[1]); + else + child_size = allocation->width - side[0] - side[1]; + + child_allocation.x = allocation->x + (allocation->width - child_size) / 2; + child_allocation.width = child_size; + + if (allocation->x + side[0] > child_allocation.x) + child_allocation.x = allocation->x + side[0]; + else if (allocation->x + allocation->width - side[1] < child_allocation.x + child_allocation.width) + child_allocation.x = allocation->x + allocation->width - side[1] - child_allocation.width; + + if (direction == GTK_TEXT_DIR_RTL) + child_allocation.x = allocation->x + allocation->width - (child_allocation.x - allocation->x) - child_allocation.width; + + if (priv->custom_title) + gtk_widget_size_allocate (priv->custom_title, &child_allocation); + else + gtk_widget_size_allocate (priv->labels_box, &child_allocation); +} + +/** + * gd_header_bar_set_title: + * @bar: a #GdHeaderBar + * @title: (allow-none): a title + * + * Sets the title of the #GdHeaderBar. The title should help a user + * identify the current view. A good title should not include the + * application name. + * + **/ +void +gd_header_bar_set_title (GdHeaderBar *bar, + const gchar *title) +{ + GdHeaderBarPrivate *priv; + char *new_title; + + g_return_if_fail (GD_IS_HEADER_BAR (bar)); + + priv = bar->priv; + + new_title = g_strdup (title); + g_free (priv->title); + priv->title = new_title; + + gtk_label_set_label (GTK_LABEL (priv->title_label), priv->title); + gtk_widget_queue_resize (GTK_WIDGET (bar)); + + g_object_notify (G_OBJECT (bar), "title"); +} + +/** + * gd_header_bar_get_title: + * @bar: a #GdHeaderBar + * + * Retrieves the title of the header. See gd_header_bar_set_title(). + * + * Return value: the title of the header, or %NULL if none has + * been set explicitely. The returned string is owned by the widget + * and must not be modified or freed. + **/ +const gchar * +gd_header_bar_get_title (GdHeaderBar *bar) +{ + g_return_val_if_fail (GD_IS_HEADER_BAR (bar), NULL); + + return bar->priv->title; +} + +/** + * gd_header_bar_set_subtitle: + * @bar: a #GdHeaderBar + * @subtitle: (allow-none): a subtitle + * + * Sets the subtitle of the #GdHeaderBar. The subtitle should give a user + * an additional detail to help him identify the current view. + * + **/ +void +gd_header_bar_set_subtitle (GdHeaderBar *bar, + const gchar *subtitle) +{ + GdHeaderBarPrivate *priv; + char *new_subtitle; + + g_return_if_fail (GD_IS_HEADER_BAR (bar)); + + priv = bar->priv; + + new_subtitle = g_strdup (subtitle); + g_free (priv->subtitle); + priv->subtitle = new_subtitle; + + gtk_label_set_label (GTK_LABEL (priv->subtitle_label), priv->subtitle); + gtk_widget_set_visible (priv->subtitle_label, (priv->subtitle != NULL)); + + gtk_widget_queue_resize (GTK_WIDGET (bar)); + + g_object_notify (G_OBJECT (bar), "subtitle"); +} + +/** + * gd_header_bar_get_subtitle: + * @bar: a #GdHeaderBar + * + * Retrieves the subtitle of the header. See gd_header_bar_set_subtitle(). + * + * Return value: the subtitle of the header, or %NULL if none has + * been set explicitely. The returned string is owned by the widget + * and must not be modified or freed. + **/ +const gchar * +gd_header_bar_get_subtitle (GdHeaderBar *bar) +{ + g_return_val_if_fail (GD_IS_HEADER_BAR (bar), NULL); + + return bar->priv->subtitle; +} + +/** + * gd_header_bar_set_custom_title: + * @bar: a #GdHeaderBar + * @title_widget: (allow-none): a custom widget to use for a title + * + * Sets a custom title for the #GdHeaderBar. The title should help a + * user identify the current view. This supercedes any title set by + * gd_header_bar_set_title(). You should set the custom title to %NULL, + * for the header title label to be visible again. + * + **/ +void +gd_header_bar_set_custom_title (GdHeaderBar *bar, + GtkWidget *title_widget) +{ + GdHeaderBarPrivate *priv; + + g_return_if_fail (GD_IS_HEADER_BAR (bar)); + if (title_widget) + g_return_if_fail (GTK_IS_WIDGET (title_widget)); + + priv = bar->priv; + + /* No need to do anything if the custom widget stays the same */ + if (priv->custom_title == title_widget) + return; + + if (priv->custom_title) + { + GtkWidget *custom = priv->custom_title; + + priv->custom_title = NULL; + gtk_widget_unparent (custom); + g_object_unref (custom); + } + + if (title_widget) + { + priv->custom_title = g_object_ref (title_widget); + + gtk_widget_hide (priv->labels_box); + + gtk_widget_set_parent (priv->custom_title, GTK_WIDGET (bar)); + gtk_widget_set_valign (priv->custom_title, GTK_ALIGN_CENTER); + + gtk_widget_show (title_widget); + } + else + { + gtk_widget_show (priv->labels_box); + } + + gtk_widget_queue_resize (GTK_WIDGET (bar)); + + g_object_notify (G_OBJECT (bar), "custom-title"); +} + +/** + * gd_header_bar_get_custom_title: + * @bar: a #GdHeaderBar + * + * Retrieves the custom title widget of the header. See + * gd_header_bar_set_custom_title(). + * + * Return value: (transfer none): the custom title widget of the header, or %NULL if + * none has been set explicitely. + **/ +GtkWidget * +gd_header_bar_get_custom_title (GdHeaderBar *bar) +{ + g_return_val_if_fail (GD_IS_HEADER_BAR (bar), NULL); + + return bar->priv->custom_title; +} + +static void +gd_header_bar_finalize (GObject *object) +{ + GdHeaderBar *bar = GD_HEADER_BAR (object); + GdHeaderBarPrivate *priv = bar->priv; + + g_free (priv->title); + g_free (priv->subtitle); + + g_list_free (priv->children); + + G_OBJECT_CLASS (gd_header_bar_parent_class)->finalize (object); +} + +static void +gd_header_bar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdHeaderBar *bar = GD_HEADER_BAR (object); + GdHeaderBarPrivate *priv = bar->priv; + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, priv->title); + break; + + case PROP_SUBTITLE: + g_value_set_string (value, priv->subtitle); + break; + + case PROP_CUSTOM_TITLE: + g_value_set_object (value, priv->custom_title); + break; + + case PROP_SPACING: + g_value_set_int (value, priv->spacing); + break; + + case PROP_HPADDING: + g_value_set_int (value, priv->hpadding); + break; + + case PROP_VPADDING: + g_value_set_int (value, priv->vpadding); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gd_header_bar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdHeaderBar *bar = GD_HEADER_BAR (object); + GdHeaderBarPrivate *priv = bar->priv; + + switch (prop_id) + { + case PROP_TITLE: + gd_header_bar_set_title (bar, g_value_get_string (value)); + break; + + case PROP_SUBTITLE: + gd_header_bar_set_subtitle (bar, g_value_get_string (value)); + break; + + case PROP_CUSTOM_TITLE: + gd_header_bar_set_custom_title (bar, g_value_get_object (value)); + break; + + case PROP_SPACING: + priv->spacing = g_value_get_int (value); + gtk_widget_queue_resize (GTK_WIDGET (bar)); + break; + + case PROP_HPADDING: + priv->hpadding = g_value_get_int (value); + gtk_widget_queue_resize (GTK_WIDGET (bar)); + break; + + case PROP_VPADDING: + priv->vpadding = g_value_get_int (value); + gtk_widget_queue_resize (GTK_WIDGET (bar)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gd_header_bar_pack (GdHeaderBar *bar, + GtkWidget *widget, + GtkPackType pack_type) +{ + Child *child; + + g_return_if_fail (gtk_widget_get_parent (widget) == NULL); + + child = g_new (Child, 1); + child->widget = widget; + child->pack_type = pack_type; + + bar->priv->children = g_list_append (bar->priv->children, child); + + gtk_widget_freeze_child_notify (widget); + gtk_widget_set_parent (widget, GTK_WIDGET (bar)); + gtk_widget_child_notify (widget, "pack-type"); + gtk_widget_child_notify (widget, "position"); + gtk_widget_thaw_child_notify (widget); +} + +static void +gd_header_bar_add (GtkContainer *container, + GtkWidget *child) +{ + gd_header_bar_pack (GD_HEADER_BAR (container), child, GTK_PACK_START); +} + +static GList * +find_child_link (GdHeaderBar *bar, + GtkWidget *widget) +{ + GList *l; + Child *child; + + for (l = bar->priv->children; l; l = l->next) + { + child = l->data; + if (child->widget == widget) + return l; + } + + return NULL; +} + +static void +gd_header_bar_remove (GtkContainer *container, + GtkWidget *widget) +{ + GdHeaderBar *bar = GD_HEADER_BAR (container); + GList *l; + Child *child; + + l = find_child_link (bar, widget); + if (l) + { + child = l->data; + gtk_widget_unparent (child->widget); + bar->priv->children = g_list_remove_link (bar->priv->children, l); + g_free (child); + gtk_widget_queue_resize (GTK_WIDGET (container)); + } +} + +static void +gd_header_bar_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GdHeaderBar *bar = GD_HEADER_BAR (container); + GdHeaderBarPrivate *priv = bar->priv; + Child *child; + GList *children; + + children = priv->children; + while (children) + { + child = children->data; + children = children->next; + if (child->pack_type == GTK_PACK_START) + (* callback) (child->widget, callback_data); + } + + if (include_internals) + { + if (priv->custom_title) + (* callback) (priv->custom_title, callback_data); + else + (* callback) (priv->labels_box, callback_data); + } + + children = g_list_last (priv->children); + while (children) + { + child = children->data; + children = children->prev; + if (child->pack_type == GTK_PACK_END) + (* callback) (child->widget, callback_data); + } +} + +static GType +gd_header_bar_child_type (GtkContainer *container) +{ + return GTK_TYPE_WIDGET; +} + +static void +gd_header_bar_get_child_property (GtkContainer *container, + GtkWidget *widget, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GList *l; + Child *child; + + l = find_child_link (GD_HEADER_BAR (container), widget); + child = l->data; + + switch (property_id) + { + case CHILD_PROP_PACK_TYPE: + g_value_set_enum (value, child->pack_type); + break; + + case CHILD_PROP_POSITION: + g_value_set_int (value, g_list_position (GD_HEADER_BAR (container)->priv->children, l)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } +} + +static void +gd_header_bar_set_child_property (GtkContainer *container, + GtkWidget *widget, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GList *l; + Child *child; + + l = find_child_link (GD_HEADER_BAR (container), widget); + child = l->data; + + switch (property_id) + { + case CHILD_PROP_PACK_TYPE: + child->pack_type = g_value_get_enum (value); + break; + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } +} + +static gboolean +gd_header_bar_button_press (GtkWidget *toolbar, + GdkEventButton *event) +{ + GtkWidget *window; + + if (gdk_event_triggers_context_menu ((GdkEvent *) event)) + { + + /* FIXME menu ? */ + + return FALSE; + } + + if (event->type != GDK_BUTTON_PRESS) + return FALSE; + + window = gtk_widget_get_toplevel (toolbar); + + if (window) + { + gtk_window_begin_move_drag (GTK_WINDOW (window), + event->button, + event->x_root, + event->y_root, + event->time); + + return TRUE; + } + + return FALSE; +} + +static void +gd_header_bar_realize (GtkWidget *widget) +{ + GdHeaderBar *bar = GD_HEADER_BAR (widget); + GdHeaderBarPrivate *priv = bar->priv; + GtkAllocation allocation; + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_set_realized (widget, TRUE); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + 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_TOUCH_MASK); + attributes_mask = GDK_WA_X | GDK_WA_Y; + + window = gtk_widget_get_parent_window (widget); + gtk_widget_set_window (widget, window); + g_object_ref (window); + + priv->event_window = gdk_window_new (window, + &attributes, attributes_mask); + gdk_window_set_user_data (priv->event_window, widget); +} +static void +gd_header_bar_unrealize (GtkWidget *widget) +{ + GdHeaderBar *bar = GD_HEADER_BAR (widget); + GdHeaderBarPrivate *priv = bar->priv; + + if (priv->event_window) + { + gdk_window_set_user_data (priv->event_window, NULL); + gdk_window_destroy (priv->event_window); + priv->event_window = NULL; + } + + GTK_WIDGET_CLASS (gd_header_bar_parent_class)->unrealize (widget); +} + +static void +gd_header_bar_map (GtkWidget *widget) +{ + GdHeaderBar *bar = GD_HEADER_BAR (widget); + GdHeaderBarPrivate *priv = bar->priv; + + GTK_WIDGET_CLASS (gd_header_bar_parent_class)->map (widget); + + if (priv->event_window) + gdk_window_show_unraised (priv->event_window); +} + +static void +gd_header_bar_unmap (GtkWidget *widget) +{ + GdHeaderBar *bar = GD_HEADER_BAR (widget); + GdHeaderBarPrivate *priv = bar->priv; + + if (priv->event_window) + gdk_window_hide (priv->event_window); + + GTK_WIDGET_CLASS (gd_header_bar_parent_class)->unmap (widget); +} + +static gint +gd_header_bar_draw (GtkWidget *widget, + cairo_t *cr) +{ + GtkStyleContext *context; + + 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)); + + + GTK_WIDGET_CLASS (gd_header_bar_parent_class)->draw (widget, cr); + + return TRUE; +} + +static void +gd_header_bar_class_init (GdHeaderBarClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (class); + + object_class->get_property = gd_header_bar_get_property; + object_class->set_property = gd_header_bar_set_property; + object_class->finalize = gd_header_bar_finalize; + + widget_class->size_allocate = gd_header_bar_size_allocate; + widget_class->get_preferred_width = gd_header_bar_get_preferred_width; + widget_class->get_preferred_height = gd_header_bar_get_preferred_height; + widget_class->get_preferred_height_for_width = gd_header_bar_get_preferred_height_for_width; + widget_class->get_preferred_width_for_height = gd_header_bar_get_preferred_width_for_height; + widget_class->button_press_event = gd_header_bar_button_press; + widget_class->realize = gd_header_bar_realize; + widget_class->unrealize = gd_header_bar_unrealize; + widget_class->map = gd_header_bar_map; + widget_class->unmap = gd_header_bar_unmap; + widget_class->draw = gd_header_bar_draw; + + container_class->add = gd_header_bar_add; + container_class->remove = gd_header_bar_remove; + container_class->forall = gd_header_bar_forall; + container_class->child_type = gd_header_bar_child_type; + container_class->set_child_property = gd_header_bar_set_child_property; + container_class->get_child_property = gd_header_bar_get_child_property; + gtk_container_class_handle_border_width (container_class); + + gtk_container_class_install_child_property (container_class, + CHILD_PROP_PACK_TYPE, + g_param_spec_enum ("pack-type", + "Pack type", + "A GtkPackType indicating whether the child is packed with reference to the start or end of the parent", + GTK_TYPE_PACK_TYPE, GTK_PACK_START, + G_PARAM_READWRITE)); + gtk_container_class_install_child_property (container_class, + CHILD_PROP_POSITION, + g_param_spec_int ("position", + "Position", + "The index of the child in the parent", + -1, G_MAXINT, 0, + G_PARAM_READABLE)); + + g_object_class_install_property (object_class, + PROP_TITLE, + g_param_spec_string ("title", + "Title", + "The title to display", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SUBTITLE, + g_param_spec_string ("subtitle", + "Subtitle", + "The subtitle to display", + NULL, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_CUSTOM_TITLE, + g_param_spec_object ("custom-title", + "Custom Title", + "Custom title widget to display", + GTK_TYPE_WIDGET, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, + PROP_SPACING, + g_param_spec_int ("spacing", + "Spacing", + "The amount of space between children", + 0, G_MAXINT, + DEFAULT_SPACING, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_HPADDING, + g_param_spec_int ("hpadding", + "Horizontal padding", + "The amount of space to the left and right of children", + 0, G_MAXINT, + DEFAULT_HPADDING, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_VPADDING, + g_param_spec_int ("vpadding", + "Vertical padding", + "The amount of space to the above and below children", + 0, G_MAXINT, + DEFAULT_VPADDING, + G_PARAM_READWRITE)); + + g_type_class_add_private (object_class, sizeof (GdHeaderBarPrivate)); +} + +static void +gd_header_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *type) +{ + if (type && strcmp (type, "title") == 0) + gd_header_bar_set_custom_title (GD_HEADER_BAR (buildable), GTK_WIDGET (child)); + else if (!type) + gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child)); + else + GTK_BUILDER_WARN_INVALID_CHILD_TYPE (GD_HEADER_BAR (buildable), type); +} + +static void +gd_header_buildable_init (GtkBuildableIface *iface) +{ + iface->add_child = gd_header_buildable_add_child; +} + +void +gd_header_bar_pack_start (GdHeaderBar *bar, + GtkWidget *child) +{ + gd_header_bar_pack (bar, child, GTK_PACK_START); +} + +void +gd_header_bar_pack_end (GdHeaderBar *bar, + GtkWidget *child) +{ + gd_header_bar_pack (bar, child, GTK_PACK_END); +} + +GtkWidget * +gd_header_bar_new (void) +{ + return GTK_WIDGET (g_object_new (GD_TYPE_HEADER_BAR, NULL)); +} diff --git a/libgd/libgd/gd-header-bar.h b/libgd/libgd/gd-header-bar.h new file mode 100644 index 00000000..a85202a5 --- /dev/null +++ b/libgd/libgd/gd-header-bar.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2013 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_HEADER_BAR_H__ +#define __GD_HEADER_BAR_H__ + +#include + +G_BEGIN_DECLS + +#define GD_TYPE_HEADER_BAR (gd_header_bar_get_type ()) +#define GD_HEADER_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_HEADER_BAR, GdHeaderBar)) +#define GD_HEADER_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_HEADER_BAR, GdHeaderBarClass)) +#define GD_IS_HEADER_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_HEADER_BAR)) +#define GD_IS_HEADER_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_HEADER_BAR)) +#define GD_HEADER_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_HEADER_BAR, GdHeaderBarClass)) + +typedef struct _GdHeaderBar GdHeaderBar; +typedef struct _GdHeaderBarPrivate GdHeaderBarPrivate; +typedef struct _GdHeaderBarClass GdHeaderBarClass; + +struct _GdHeaderBar +{ + GtkContainer container; + + /*< private >*/ + GdHeaderBarPrivate *priv; +}; + +struct _GdHeaderBarClass +{ + GtkContainerClass parent_class; + + /* Padding for future expansion */ + void (*_gd_reserved1) (void); + void (*_gd_reserved2) (void); + void (*_gd_reserved3) (void); + void (*_gd_reserved4) (void); +}; + +GType gd_header_bar_get_type (void) G_GNUC_CONST; +GtkWidget *gd_header_bar_new (void); +void gd_header_bar_set_title (GdHeaderBar *bar, + const char *title); +const char * gd_header_bar_get_title (GdHeaderBar *bar); +void gd_header_bar_set_subtitle (GdHeaderBar *bar, + const gchar *subtitle); +const gchar *gd_header_bar_get_subtitle (GdHeaderBar *bar); +void gd_header_bar_set_custom_title (GdHeaderBar *bar, + GtkWidget *title_widget); +GtkWidget * gd_header_bar_get_custom_title (GdHeaderBar *bar); +void gd_header_bar_pack_start (GdHeaderBar *bar, + GtkWidget *child); +void gd_header_bar_pack_end (GdHeaderBar *bar, + GtkWidget *child); + +G_END_DECLS + +#endif /* __GD_HEADER_BAR_H__ */ diff --git a/libgd/libgd/gd-header-button.c b/libgd/libgd/gd-header-button.c new file mode 100644 index 00000000..5a3c2c6e --- /dev/null +++ b/libgd/libgd/gd-header-button.c @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2013 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-header-button.h" + +typedef GTypeInterface GdHeaderButtonIface; +typedef GdHeaderButtonIface GdHeaderButtonInterface; +#define GD_HEADER_BUTTON_GET_IFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GD_TYPE_HEADER_BUTTON, GdHeaderButtonIface)) + +G_DEFINE_INTERFACE (GdHeaderButton, gd_header_button, GTK_TYPE_BUTTON) + +enum { + PROP_0, + PROP_LABEL, + PROP_USE_MARKUP, + PROP_SYMBOLIC_ICON_NAME +}; + +static void +gd_header_button_default_init (GdHeaderButtonIface *iface) +{ + GParamSpec *pspec; + + /** + * GdHeaderButton:label: + * + * The label of the #GdHeaderButton object. + */ + pspec = g_param_spec_string ("label", + "Text label", + "Label displayed by the button", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + g_object_interface_install_property (iface, pspec); + + /** + * GdHeaderButton:use-markup: + * + * Whether the label of the #GdHeaderButton object should use markup. + */ + pspec = g_param_spec_boolean ("use-markup", + "Use markup", + "Whether the label should use markup", + FALSE, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + g_object_interface_install_property (iface, pspec); + + /** + * GdHeaderButton:symbolic-icon-name: + * + * The symbolic icon name of the #GdHeaderButton object. + */ + pspec = g_param_spec_string ("symbolic-icon-name", + "Symbolic icon name", + "The name of the symbolic icon displayed by the button", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + g_object_interface_install_property (iface, pspec); +} + +/** + * gd_header_button_get_label: + * @self: + * + * Returns: (transfer full): + */ +gchar * +gd_header_button_get_label (GdHeaderButton *self) +{ + gchar *label; + g_object_get (self, "label", &label, NULL); + + return label; +} + +/** + * gd_header_button_set_label: + * @self: + * @label: (allow-none): + * + */ +void +gd_header_button_set_label (GdHeaderButton *self, + const gchar *label) +{ + g_object_set (self, "label", label, NULL); +} + +/** + * gd_header_button_get_symbolic_icon_name: + * @self: + * + * Returns: (transfer full): + */ +gchar * +gd_header_button_get_symbolic_icon_name (GdHeaderButton *self) +{ + gchar *symbolic_icon_name; + g_object_get (self, "symbolic-icon-name", &symbolic_icon_name, NULL); + + return symbolic_icon_name; +} + +/** + * gd_header_button_set_symbolic_icon_name: + * @self: + * @symbolic_icon_name: (allow-none): + * + */ +void +gd_header_button_set_symbolic_icon_name (GdHeaderButton *self, + const gchar *symbolic_icon_name) +{ + if (symbolic_icon_name != NULL && + !g_str_has_suffix (symbolic_icon_name, "-symbolic")) + { + g_warning ("gd_header_button_set_symbolic_icon_name was called with " + "a non-symbolic name."); + return; + } + + g_object_set (self, "symbolic-icon-name", symbolic_icon_name, NULL); +} + +/** + * gd_header_button_get_use_markup: + * @self: + * + * Returns: + */ +gboolean +gd_header_button_get_use_markup (GdHeaderButton *self) +{ + gboolean use_markup; + + g_object_get (self, "use-markup", &use_markup, NULL); + return use_markup; +} + +/** + * gd_header_button_set_use_markup: + * @self: + * @use_markup: + * + */ +void +gd_header_button_set_use_markup (GdHeaderButton *self, + gboolean use_markup) +{ + g_object_set (self, "use-markup", use_markup, NULL); +} + +/* generic implementation for all private subclasses */ +typedef struct _GdHeaderButtonPrivate GdHeaderButtonPrivate; +struct _GdHeaderButtonPrivate { + gchar *label; + gchar *symbolic_icon_name; + + gboolean use_markup; +}; + +#define GET_PRIVATE(inst) G_TYPE_INSTANCE_GET_PRIVATE (inst, G_OBJECT_TYPE (inst), GdHeaderButtonPrivate) +#define GET_PARENT_CLASS(inst) g_type_class_peek_parent (G_OBJECT_GET_CLASS (inst)) + +static void +rebuild_child (GdHeaderButton *self) +{ + GdHeaderButtonPrivate *priv = GET_PRIVATE (self); + GtkStyleContext *context; + GtkWidget *button_child, *label; + + gtk_widget_set_valign (GTK_WIDGET (self), GTK_ALIGN_CENTER); + + button_child = gtk_bin_get_child (GTK_BIN (self)); + if (button_child != NULL) + gtk_widget_destroy (button_child); + + button_child = NULL; + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + + if (priv->symbolic_icon_name != NULL) + { + button_child = gtk_image_new_from_icon_name (priv->symbolic_icon_name, GTK_ICON_SIZE_MENU); + if (priv->label != NULL) + gtk_widget_set_tooltip_text (GTK_WIDGET (self), priv->label); + + gtk_style_context_remove_class (context, "text-button"); + gtk_style_context_add_class (context, "image-button"); + } + else if (priv->label != NULL) + { + label = gtk_label_new (priv->label); + gtk_label_set_use_markup (GTK_LABEL (label), priv->use_markup); + + if (GTK_IS_MENU_BUTTON (self)) + { + GtkWidget *arrow; + + button_child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_add (GTK_CONTAINER (button_child), label); + + arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (button_child), arrow); + } + else + { + button_child = label; + } + + gtk_style_context_remove_class (context, "image-button"); + gtk_style_context_add_class (context, "text-button"); + } + + if (button_child) + { + gtk_widget_show_all (button_child); + gtk_container_add (GTK_CONTAINER (self), button_child); + } +} + +static void +button_set_label (GdHeaderButton *self, + const gchar *label) +{ + GdHeaderButtonPrivate *priv = GET_PRIVATE (self); + + if (g_strcmp0 (priv->label, label) != 0) + { + g_free (priv->label); + priv->label = g_strdup (label); + + rebuild_child (self); + g_object_notify (G_OBJECT (self), "label"); + } +} + +static void +button_set_use_markup (GdHeaderButton *self, + gboolean use_markup) +{ + GdHeaderButtonPrivate *priv = GET_PRIVATE (self); + + if (priv->use_markup != use_markup) + { + priv->use_markup = use_markup; + + rebuild_child (self); + g_object_notify (G_OBJECT (self), "use-markup"); + } +} + +static void +button_set_symbolic_icon_name (GdHeaderButton *self, + const gchar *symbolic_icon_name) +{ + GdHeaderButtonPrivate *priv = GET_PRIVATE (self); + + if (g_strcmp0 (priv->symbolic_icon_name, symbolic_icon_name) != 0) + { + g_free (priv->symbolic_icon_name); + priv->symbolic_icon_name = g_strdup (symbolic_icon_name); + + rebuild_child (self); + g_object_notify (G_OBJECT (self), "symbolic-icon-name"); + } +} + +static void +gd_header_button_generic_finalize (GObject *object) +{ + GdHeaderButton *self = GD_HEADER_BUTTON (object); + GdHeaderButtonPrivate *priv = GET_PRIVATE (self); + + g_free (priv->label); + g_free (priv->symbolic_icon_name); + + G_OBJECT_CLASS (GET_PARENT_CLASS (object))->finalize (object); +} + +static void +gd_header_button_generic_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdHeaderButton *self = GD_HEADER_BUTTON (object); + + switch (prop_id) + { + case PROP_LABEL: + button_set_label (self, g_value_get_string (value)); + break; + case PROP_USE_MARKUP: + button_set_use_markup (self, g_value_get_boolean (value)); + break; + case PROP_SYMBOLIC_ICON_NAME: + button_set_symbolic_icon_name (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gd_header_button_generic_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdHeaderButton *self = GD_HEADER_BUTTON (object); + GdHeaderButtonPrivate *priv = GET_PRIVATE (self); + + switch (prop_id) + { + case PROP_LABEL: + g_value_set_string (value, priv->label); + break; + case PROP_USE_MARKUP: + g_value_set_boolean (value, priv->use_markup); + break; + case PROP_SYMBOLIC_ICON_NAME: + g_value_set_string (value, priv->symbolic_icon_name); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gd_header_button_generic_iface_init (GdHeaderButtonIface *iface) +{ +} + +static void +gd_header_button_generic_class_init (gpointer klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->get_property = gd_header_button_generic_get_property; + oclass->set_property = gd_header_button_generic_set_property; + oclass->finalize = gd_header_button_generic_finalize; + + g_object_class_override_property (oclass, PROP_LABEL, "label"); + g_object_class_override_property (oclass, PROP_USE_MARKUP, "use-markup"); + g_object_class_override_property (oclass, PROP_SYMBOLIC_ICON_NAME, "symbolic-icon-name"); + + g_type_class_add_private (klass, sizeof (GdHeaderButtonPrivate)); +} + +/* private subclasses */ +typedef GtkButtonClass GdHeaderSimpleButtonClass; +G_DEFINE_TYPE_WITH_CODE (GdHeaderSimpleButton, gd_header_simple_button, GTK_TYPE_BUTTON, + G_IMPLEMENT_INTERFACE (GD_TYPE_HEADER_BUTTON, gd_header_button_generic_iface_init)) + +static void +gd_header_simple_button_class_init (GdHeaderSimpleButtonClass *klass) +{ + gd_header_button_generic_class_init (klass); +} + +static void +gd_header_simple_button_init (GdHeaderSimpleButton *self) +{ +} + +typedef GtkToggleButtonClass GdHeaderToggleButtonClass; +G_DEFINE_TYPE_WITH_CODE (GdHeaderToggleButton, gd_header_toggle_button, GTK_TYPE_TOGGLE_BUTTON, + G_IMPLEMENT_INTERFACE (GD_TYPE_HEADER_BUTTON, gd_header_button_generic_iface_init)) + +static void +gd_header_toggle_button_class_init (GdHeaderToggleButtonClass *klass) +{ + gd_header_button_generic_class_init (klass); +} + +static void +gd_header_toggle_button_init (GdHeaderToggleButton *self) +{ +} + +typedef GtkMenuButtonClass GdHeaderMenuButtonClass; +G_DEFINE_TYPE_WITH_CODE (GdHeaderMenuButton, gd_header_menu_button, GTK_TYPE_MENU_BUTTON, + G_IMPLEMENT_INTERFACE (GD_TYPE_HEADER_BUTTON, gd_header_button_generic_iface_init)) + +static void +gd_header_menu_button_class_init (GdHeaderMenuButtonClass *klass) +{ + gd_header_button_generic_class_init (klass); +} + +static void +gd_header_menu_button_init (GdHeaderMenuButton *self) +{ +} + +typedef GtkRadioButtonClass GdHeaderRadioButtonClass; +G_DEFINE_TYPE_WITH_CODE (GdHeaderRadioButton, gd_header_radio_button, GTK_TYPE_RADIO_BUTTON, + G_IMPLEMENT_INTERFACE (GD_TYPE_HEADER_BUTTON, gd_header_button_generic_iface_init)) + +static void +gd_header_radio_button_constructed (GObject *object) +{ + GdHeaderRadioButton *self = (GdHeaderRadioButton *) (object); + + G_OBJECT_CLASS (GET_PARENT_CLASS (object))->constructed (object); + + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (self), FALSE); +} + +static void +gd_header_radio_button_class_init (GdHeaderRadioButtonClass *klass) +{ + GObjectClass *oclass = G_OBJECT_CLASS (klass); + + oclass->constructed = gd_header_radio_button_constructed; + + gd_header_button_generic_class_init (klass); +} + +static void +gd_header_radio_button_init (GdHeaderRadioButton *self) +{ +} + +/** + * gd_header_simple_button_new: + * + * Returns: (transfer none): + */ +GtkWidget * +gd_header_simple_button_new (void) +{ + return g_object_new (GD_TYPE_HEADER_SIMPLE_BUTTON, NULL); +} + +/** + * gd_header_toggle_button_new: + * + * Returns: (transfer none): + */ +GtkWidget * +gd_header_toggle_button_new (void) +{ + return g_object_new (GD_TYPE_HEADER_TOGGLE_BUTTON, NULL); +} + +/** + * gd_header_radio_button_new: + * + * Returns: (transfer none): + */ +GtkWidget * +gd_header_radio_button_new (void) +{ + return g_object_new (GD_TYPE_HEADER_RADIO_BUTTON, NULL); +} + +/** + * gd_header_menu_button_new: + * + * Returns: (transfer none): + */ +GtkWidget * +gd_header_menu_button_new (void) +{ + return g_object_new (GD_TYPE_HEADER_MENU_BUTTON, NULL); +} diff --git a/libgd/libgd/gd-header-button.h b/libgd/libgd/gd-header-button.h new file mode 100644 index 00000000..fa5c41b3 --- /dev/null +++ b/libgd/libgd/gd-header-button.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2013 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_HEADER_BUTTON_H__ +#define __GD_HEADER_BUTTON_H__ + +#include + +G_BEGIN_DECLS + +#define GD_TYPE_HEADER_BUTTON (gd_header_button_get_type ()) +#define GD_HEADER_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_HEADER_BUTTON, GdHeaderButton)) +#define GD_IS_HEADER_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_HEADER_BUTTON)) + +typedef struct _GdHeaderButton GdHeaderButton; + +GType gd_header_button_get_type (void) G_GNUC_CONST; + +void gd_header_button_set_label (GdHeaderButton *self, + const gchar *label); +void gd_header_button_set_symbolic_icon_name (GdHeaderButton *self, + const gchar *symbolic_icon_name); +void gd_header_button_set_use_markup (GdHeaderButton *self, + gboolean use_markup); +gchar * gd_header_button_get_label (GdHeaderButton *self); +gchar * gd_header_button_get_symbolic_icon_name (GdHeaderButton *self); +gboolean gd_header_button_get_use_markup (GdHeaderButton *self); + +#define GD_TYPE_HEADER_SIMPLE_BUTTON (gd_header_simple_button_get_type ()) +typedef GtkButton GdHeaderSimpleButton; +GType gd_header_simple_button_get_type (void) G_GNUC_CONST; +GtkWidget * gd_header_simple_button_new (void); + +#define GD_TYPE_HEADER_TOGGLE_BUTTON (gd_header_toggle_button_get_type ()) +typedef GtkToggleButton GdHeaderToggleButton; +GType gd_header_toggle_button_get_type (void) G_GNUC_CONST; +GtkWidget * gd_header_toggle_button_new (void); + +#define GD_TYPE_HEADER_RADIO_BUTTON (gd_header_radio_button_get_type ()) +typedef GtkRadioButton GdHeaderRadioButton; +GType gd_header_radio_button_get_type (void) G_GNUC_CONST; +GtkWidget * gd_header_radio_button_new (void); + +#define GD_TYPE_HEADER_MENU_BUTTON (gd_header_menu_button_get_type ()) +typedef GtkMenuButton GdHeaderMenuButton; +GType gd_header_menu_button_get_type (void) G_GNUC_CONST; +GtkWidget * gd_header_menu_button_new (void); + +G_END_DECLS + +#endif /* __GD_HEADER_BUTTON_H__ */ diff --git a/libgd/libgd/gd-icon-utils.c b/libgd/libgd/gd-icon-utils.c new file mode 100644 index 00000000..d68f89db --- /dev/null +++ b/libgd/libgd/gd-icon-utils.c @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2011, 2012, 2015 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 + * + */ + +#include "gd-icon-utils.h" + +#include +#include +#include + +#define _BG_MIN_SIZE 20 +#define _EMBLEM_MIN_SIZE 8 + +/** + * 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; + GdkPixbuf *retval; + 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), 600); + + 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/libgd/libgd/gd-icon-utils.h b/libgd/libgd/gd-icon-utils.h new file mode 100644 index 00000000..63f96da0 --- /dev/null +++ b/libgd/libgd/gd-icon-utils.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2011, 2012, 2015 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 + * + */ + +#ifndef __GD_CREATE_SYMBOLIC_ICON_H__ +#define __GD_CREATE_SYMBOLIC_ICON_H__ + +#include + +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/libgd/libgd/gd-main-icon-view.c b/libgd/libgd/gd-main-icon-view.c new file mode 100644 index 00000000..5b230ed0 --- /dev/null +++ b/libgd/libgd/gd-main-icon-view.c @@ -0,0 +1,425 @@ +/* + * 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 + * + */ + +#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 +#include +#include + +#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_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) +{ + GtkTreeModel *model = gtk_icon_view_get_model (GTK_ICON_VIEW (self)); + GtkCellLayout *layout = GTK_CELL_LAYOUT (self); + GType icon_gtype; + + if (!model) + return; + + gtk_cell_layout_clear_attributes (layout, self->priv->pixbuf_cell); + gtk_cell_layout_clear_attributes (layout, self->priv->text_cell); + + gtk_cell_layout_add_attribute (layout, self->priv->pixbuf_cell, + "active", GD_MAIN_COLUMN_SELECTED); + gtk_cell_layout_add_attribute (layout, self->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, self->priv->pixbuf_cell, + "pixbuf", GD_MAIN_COLUMN_ICON); + else if (icon_gtype == CAIRO_GOBJECT_TYPE_SURFACE) + gtk_cell_layout_add_attribute (layout, self->priv->pixbuf_cell, + "surface", GD_MAIN_COLUMN_ICON); + else + g_assert_not_reached (); + + gtk_cell_layout_add_attribute (layout, self->priv->text_cell, + "text", GD_MAIN_COLUMN_PRIMARY_TEXT); + gtk_cell_layout_add_attribute (layout, self->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); + GtkTreeModel *model = gtk_icon_view_get_model (GTK_ICON_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_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); + GtkCellRenderer *cell; + const GtkTargetEntry targets[] = { + { (char *) "text/uri-list", GTK_TARGET_OTHER_APP, 0 } + }; + + 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); + + self->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); + + self->priv->text_cell = cell = gd_two_lines_renderer_new (); + g_object_set (cell, + "xalign", 0.5, + "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)); + + g_type_class_add_private (klass, sizeof (GdMainIconViewPrivate)); + + + 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) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_MAIN_ICON_VIEW, GdMainIconViewPrivate); + + 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); + + self->priv->selection_mode = selection_mode; + + g_object_set (self->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/libgd/libgd/gd-main-icon-view.h b/libgd/libgd/gd-main-icon-view.h new file mode 100644 index 00000000..7370b2af --- /dev/null +++ b/libgd/libgd/gd-main-icon-view.h @@ -0,0 +1,74 @@ +/* + * 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 + * + */ + +#ifndef __GD_MAIN_ICON_VIEW_H__ +#define __GD_MAIN_ICON_VIEW_H__ + +#include +#include + +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; + + GdMainIconViewPrivate *priv; +}; + +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/libgd/libgd/gd-main-list-view.c b/libgd/libgd/gd-main-list-view.c new file mode 100644 index 00000000..26128afd --- /dev/null +++ b/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 + * + */ + +#include "gd-main-list-view.h" +#include "gd-main-view-generic.h" +#include "gd-two-lines-renderer.h" + +#include +#include + +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/libgd/libgd/gd-main-list-view.h b/libgd/libgd/gd-main-list-view.h new file mode 100644 index 00000000..317e9c4b --- /dev/null +++ b/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 + * + */ + +#ifndef __GD_MAIN_LIST_VIEW_H__ +#define __GD_MAIN_LIST_VIEW_H__ + +#include +#include + +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/libgd/libgd/gd-main-toolbar.c b/libgd/libgd/gd-main-toolbar.c new file mode 100644 index 00000000..735f9b7b --- /dev/null +++ b/libgd/libgd/gd-main-toolbar.c @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2011, 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 + * + * Author: Cosimo Cecchi + * + */ + +#include "gd-main-toolbar.h" +#include "gd-header-button.h" + +#include +#include + +G_DEFINE_TYPE (GdMainToolbar, gd_main_toolbar, GTK_TYPE_TOOLBAR) + +struct _GdMainToolbarPrivate { + GtkSizeGroup *size_group; + GtkSizeGroup *vertical_size_group; + + GtkToolItem *left_group; + GtkToolItem *center_group; + GtkToolItem *right_group; + + GtkWidget *left_grid; + GtkWidget *center_grid; + + GtkWidget *labels_grid; + GtkWidget *title_label; + GtkWidget *detail_label; + + GtkWidget *modes_box; + + GtkWidget *center_menu; + GtkWidget *center_menu_child; + + GtkWidget *right_grid; + + gboolean show_modes; +}; + +enum { + PROP_0, + PROP_SHOW_MODES, +}; + +static void +gd_main_toolbar_dispose (GObject *obj) +{ + GdMainToolbar *self = GD_MAIN_TOOLBAR (obj); + + g_clear_object (&self->priv->size_group); + g_clear_object (&self->priv->vertical_size_group); + + G_OBJECT_CLASS (gd_main_toolbar_parent_class)->dispose (obj); +} + +static GtkSizeGroup * +get_vertical_size_group (GdMainToolbar *self) +{ + GtkSizeGroup *retval; + GtkWidget *dummy; + GtkToolItem *container; + + dummy = gd_header_simple_button_new (); + gd_header_button_set_label (GD_HEADER_BUTTON (dummy), "Dummy"); + container = gtk_tool_item_new (); + gtk_widget_set_no_show_all (GTK_WIDGET (container), TRUE); + gtk_container_add (GTK_CONTAINER (container), dummy); + gtk_toolbar_insert (GTK_TOOLBAR (self), container, -1); + + retval = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); + gtk_size_group_add_widget (retval, dummy); + + return retval; +} + +gboolean +gd_main_toolbar_get_show_modes (GdMainToolbar *self) +{ + return self->priv->show_modes; +} + +void +gd_main_toolbar_set_show_modes (GdMainToolbar *self, + gboolean show_modes) +{ + if (self->priv->show_modes == show_modes) + return; + + self->priv->show_modes = show_modes; + if (self->priv->show_modes) + { + gtk_widget_set_no_show_all (self->priv->labels_grid, TRUE); + gtk_widget_hide (self->priv->labels_grid); + + gtk_widget_set_valign (self->priv->center_grid, GTK_ALIGN_FILL); + gtk_widget_set_no_show_all (self->priv->modes_box, FALSE); + gtk_widget_show_all (self->priv->modes_box); + } + else + { + gtk_widget_set_no_show_all (self->priv->modes_box, TRUE); + gtk_widget_hide (self->priv->modes_box); + + gtk_widget_set_valign (self->priv->center_grid, GTK_ALIGN_CENTER); + gtk_widget_set_no_show_all (self->priv->labels_grid, FALSE); + gtk_widget_show_all (self->priv->labels_grid); + } + + g_object_notify (G_OBJECT (self), "show-modes"); +} + +static void +gd_main_toolbar_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + + GdMainToolbar *self = GD_MAIN_TOOLBAR (object); + + switch (prop_id) + { + case PROP_SHOW_MODES: + gd_main_toolbar_set_show_modes (GD_MAIN_TOOLBAR (self), g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gd_main_toolbar_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdMainToolbar *self = GD_MAIN_TOOLBAR (object); + + switch (prop_id) + { + case PROP_SHOW_MODES: + g_value_set_boolean (value, self->priv->show_modes); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gd_main_toolbar_constructed (GObject *obj) +{ + GdMainToolbar *self = GD_MAIN_TOOLBAR (obj); + GtkToolbar *tb = GTK_TOOLBAR (obj); + GtkWidget *grid; + + G_OBJECT_CLASS (gd_main_toolbar_parent_class)->constructed (obj); + + self->priv->vertical_size_group = get_vertical_size_group (self); + + /* left section */ + self->priv->left_group = gtk_tool_item_new (); + gtk_widget_set_margin_right (GTK_WIDGET (self->priv->left_group), 12); + gtk_toolbar_insert (tb, self->priv->left_group, -1); + gtk_size_group_add_widget (self->priv->vertical_size_group, + GTK_WIDGET (self->priv->left_group)); + + /* left button group */ + self->priv->left_grid = gtk_grid_new (); + gtk_grid_set_column_spacing (GTK_GRID (self->priv->left_grid), 12); + gtk_container_add (GTK_CONTAINER (self->priv->left_group), self->priv->left_grid); + gtk_widget_set_halign (self->priv->left_grid, GTK_ALIGN_START); + + /* center section */ + self->priv->center_group = gtk_tool_item_new (); + gtk_tool_item_set_expand (self->priv->center_group, TRUE); + gtk_toolbar_insert (tb, self->priv->center_group, -1); + self->priv->center_grid = gtk_grid_new (); + gtk_widget_set_halign (self->priv->center_grid, GTK_ALIGN_CENTER); + gtk_widget_set_valign (self->priv->center_grid, GTK_ALIGN_CENTER); + gtk_container_add (GTK_CONTAINER (self->priv->center_group), self->priv->center_grid); + gtk_size_group_add_widget (self->priv->vertical_size_group, + GTK_WIDGET (self->priv->center_group)); + + /* centered label group */ + self->priv->labels_grid = grid = gtk_grid_new (); + gtk_grid_set_column_spacing (GTK_GRID (grid), 12); + gtk_container_add (GTK_CONTAINER (self->priv->center_grid), grid); + + self->priv->title_label = gtk_label_new (NULL); + gtk_label_set_ellipsize (GTK_LABEL (self->priv->title_label), PANGO_ELLIPSIZE_END); + gtk_container_add (GTK_CONTAINER (grid), self->priv->title_label); + + self->priv->detail_label = gtk_label_new (NULL); + gtk_label_set_ellipsize (GTK_LABEL (self->priv->detail_label), PANGO_ELLIPSIZE_END); + gtk_widget_set_no_show_all (self->priv->detail_label, TRUE); + gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->detail_label), "dim-label"); + gtk_container_add (GTK_CONTAINER (grid), self->priv->detail_label); + + /* centered mode group */ + self->priv->modes_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_set_homogeneous (GTK_BOX (self->priv->modes_box), TRUE); + gtk_widget_set_no_show_all (self->priv->modes_box, TRUE); + gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->modes_box), "linked"); + gtk_container_add (GTK_CONTAINER (self->priv->center_grid), self->priv->modes_box); + + /* right section */ + self->priv->right_group = gtk_tool_item_new (); + gtk_widget_set_margin_left (GTK_WIDGET (self->priv->right_group), 12); + gtk_toolbar_insert (tb, self->priv->right_group, -1); + gtk_size_group_add_widget (self->priv->vertical_size_group, + GTK_WIDGET (self->priv->right_group)); + + self->priv->right_grid = gtk_grid_new (); + gtk_grid_set_column_spacing (GTK_GRID (self->priv->right_grid), 12); + gtk_container_add (GTK_CONTAINER (self->priv->right_group), self->priv->right_grid); + gtk_widget_set_halign (self->priv->right_grid, GTK_ALIGN_END); + + self->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + gtk_size_group_add_widget (self->priv->size_group, GTK_WIDGET (self->priv->left_group)); + gtk_size_group_add_widget (self->priv->size_group, GTK_WIDGET (self->priv->right_group)); +} + +static void +gd_main_toolbar_init (GdMainToolbar *self) +{ + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_MAIN_TOOLBAR, GdMainToolbarPrivate); +} + +static void +gd_main_toolbar_class_init (GdMainToolbarClass *klass) +{ + GObjectClass *oclass; + + oclass = G_OBJECT_CLASS (klass); + oclass->constructed = gd_main_toolbar_constructed; + oclass->set_property = gd_main_toolbar_set_property; + oclass->get_property = gd_main_toolbar_get_property; + oclass->dispose = gd_main_toolbar_dispose; + + g_object_class_install_property (oclass, + PROP_SHOW_MODES, + g_param_spec_boolean ("show-modes", + "Show Modes", + "Show Modes", + FALSE, + G_PARAM_READWRITE)); + + g_type_class_add_private (klass, sizeof (GdMainToolbarPrivate)); +} + +void +gd_main_toolbar_clear (GdMainToolbar *self) +{ + /* reset labels */ + gtk_label_set_text (GTK_LABEL (self->priv->title_label), ""); + gtk_label_set_text (GTK_LABEL (self->priv->detail_label), ""); + + /* clear all added buttons */ + gtk_container_foreach (GTK_CONTAINER (self->priv->left_grid), + (GtkCallback) gtk_widget_destroy, self); + gtk_container_foreach (GTK_CONTAINER (self->priv->modes_box), + (GtkCallback) gtk_widget_destroy, self); + gtk_container_foreach (GTK_CONTAINER (self->priv->right_grid), + (GtkCallback) gtk_widget_destroy, self); +} + +/** + * gd_main_toolbar_set_labels: + * @self: + * @primary: (allow-none): + * @detail: (allow-none): + * + */ +void +gd_main_toolbar_set_labels (GdMainToolbar *self, + const gchar *primary, + const gchar *detail) +{ + gchar *real_primary = NULL; + + if (primary != NULL) + real_primary = g_markup_printf_escaped ("%s", primary); + + if (real_primary == NULL) + { + gtk_label_set_markup (GTK_LABEL (self->priv->title_label), ""); + gtk_widget_hide (self->priv->title_label); + } + else + { + gtk_label_set_markup (GTK_LABEL (self->priv->title_label), real_primary); + gtk_widget_show (self->priv->title_label); + } + + if (detail == NULL) + { + gtk_label_set_text (GTK_LABEL (self->priv->detail_label), ""); + gtk_widget_hide (self->priv->detail_label); + } + else + { + gtk_label_set_text (GTK_LABEL (self->priv->detail_label), detail); + gtk_widget_show (self->priv->detail_label); + } + + g_free (real_primary); +} + +GtkWidget * +gd_main_toolbar_new (void) +{ + return g_object_new (GD_TYPE_MAIN_TOOLBAR, NULL); +} + +/** + * gd_main_toolbar_set_labels_menu: + * @self: + * @menu: (allow-none): + * + */ +void +gd_main_toolbar_set_labels_menu (GdMainToolbar *self, + GMenuModel *menu) +{ + GtkWidget *button, *grid, *w; + + if (menu == NULL && + ((gtk_widget_get_parent (self->priv->labels_grid) == self->priv->center_grid) || + self->priv->center_menu_child == NULL)) + return; + + if (menu != NULL) + { + g_object_ref (self->priv->labels_grid); + gtk_container_remove (GTK_CONTAINER (self->priv->center_grid), + self->priv->labels_grid); + + self->priv->center_menu_child = grid = gtk_grid_new (); + gtk_grid_set_column_spacing (GTK_GRID (grid), 6); + gtk_container_add (GTK_CONTAINER (grid), self->priv->labels_grid); + g_object_unref (self->priv->labels_grid); + + w = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (grid), w); + + self->priv->center_menu = button = gtk_menu_button_new (); + gtk_style_context_add_class (gtk_widget_get_style_context (self->priv->center_menu), + "selection-menu"); + gtk_widget_destroy (gtk_bin_get_child (GTK_BIN (button))); + gtk_widget_set_halign (button, GTK_ALIGN_CENTER); + gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), menu); + gtk_container_add (GTK_CONTAINER (self->priv->center_menu), grid); + + gtk_container_add (GTK_CONTAINER (self->priv->center_grid), button); + } + else + { + g_object_ref (self->priv->labels_grid); + gtk_container_remove (GTK_CONTAINER (self->priv->center_menu_child), + self->priv->labels_grid); + gtk_widget_destroy (self->priv->center_menu); + + self->priv->center_menu = NULL; + self->priv->center_menu_child = NULL; + + gtk_container_add (GTK_CONTAINER (self->priv->center_grid), + self->priv->labels_grid); + g_object_unref (self->priv->labels_grid); + } + + gtk_widget_show_all (self->priv->center_grid); +} + +/** + * gd_main_toolbar_add_mode: + * @self: + * @label: + * + * Returns: (transfer none): + */ +GtkWidget * +gd_main_toolbar_add_mode (GdMainToolbar *self, + const gchar *label) +{ + GtkWidget *button; + GList *group; + + button = gtk_radio_button_new_with_label (NULL, label); + gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE); + gtk_widget_set_size_request (button, 100, -1); + gtk_style_context_add_class (gtk_widget_get_style_context (button), "raised"); + gtk_style_context_add_class (gtk_widget_get_style_context (button), "text-button"); + + group = gtk_container_get_children (GTK_CONTAINER (self->priv->modes_box)); + if (group != NULL) + { + gtk_radio_button_join_group (GTK_RADIO_BUTTON (button), GTK_RADIO_BUTTON (group->data)); + g_list_free (group); + } + + gtk_container_add (GTK_CONTAINER (self->priv->modes_box), button); + gtk_widget_show (button); + + return button; +} + +/** + * gd_main_toolbar_add_button: + * @self: + * @icon_name: (allow-none): + * @label: (allow-none): + * @pack_start: + * + * Returns: (transfer none): + */ +GtkWidget * +gd_main_toolbar_add_button (GdMainToolbar *self, + const gchar *icon_name, + const gchar *label, + gboolean pack_start) +{ + GtkWidget *button = gd_header_simple_button_new (); + + gd_header_button_set_symbolic_icon_name (GD_HEADER_BUTTON (button), icon_name); + gd_header_button_set_label (GD_HEADER_BUTTON (button), label); + + gd_main_toolbar_add_widget (self, button, pack_start); + + return button; +} + +/** + * gd_main_toolbar_add_menu: + * @self: + * @icon_name: (allow-none): + * @label: (allow-none): + * @pack_start: + * + * Returns: (transfer none): + */ +GtkWidget * +gd_main_toolbar_add_menu (GdMainToolbar *self, + const gchar *icon_name, + const gchar *label, + gboolean pack_start) +{ + GtkWidget *button = gd_header_menu_button_new (); + + gd_header_button_set_symbolic_icon_name (GD_HEADER_BUTTON (button), icon_name); + gd_header_button_set_label (GD_HEADER_BUTTON (button), label); + + gd_main_toolbar_add_widget (self, button, pack_start); + + return button; +} + +/** + * gd_main_toolbar_add_toggle: + * @self: + * @icon_name: (allow-none): + * @label: (allow-none): + * @pack_start: + * + * Returns: (transfer none): + */ +GtkWidget * +gd_main_toolbar_add_toggle (GdMainToolbar *self, + const gchar *icon_name, + const gchar *label, + gboolean pack_start) +{ + GtkWidget *button = gd_header_toggle_button_new (); + + gd_header_button_set_symbolic_icon_name (GD_HEADER_BUTTON (button), icon_name); + gd_header_button_set_label (GD_HEADER_BUTTON (button), label); + + gd_main_toolbar_add_widget (self, button, pack_start); + + return button; +} + +/** + * gd_main_toolbar_add_widget: + * @self: + * @widget: + * @pack_start: + * + */ +void +gd_main_toolbar_add_widget (GdMainToolbar *self, + GtkWidget *widget, + gboolean pack_start) +{ + if (pack_start) + gtk_container_add (GTK_CONTAINER (self->priv->left_grid), widget); + else + gtk_container_add (GTK_CONTAINER (self->priv->right_grid), widget); +} diff --git a/libgd/libgd/gd-main-toolbar.h b/libgd/libgd/gd-main-toolbar.h new file mode 100644 index 00000000..555e6820 --- /dev/null +++ b/libgd/libgd/gd-main-toolbar.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2011, 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 + * + * Author: Cosimo Cecchi + * + */ + +#ifndef __GD_MAIN_TOOLBAR_H__ +#define __GD_MAIN_TOOLBAR_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define GD_TYPE_MAIN_TOOLBAR gd_main_toolbar_get_type() + +#define GD_MAIN_TOOLBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + GD_TYPE_MAIN_TOOLBAR, GdMainToolbar)) + +#define GD_MAIN_TOOLBAR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + GD_TYPE_MAIN_TOOLBAR, GdMainToolbarClass)) + +#define GD_IS_MAIN_TOOLBAR(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + GD_TYPE_MAIN_TOOLBAR)) + +#define GD_IS_MAIN_TOOLBAR_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GD_TYPE_MAIN_TOOLBAR)) + +#define GD_MAIN_TOOLBAR_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + GD_TYPE_MAIN_TOOLBAR, GdMainToolbarClass)) + +typedef struct _GdMainToolbar GdMainToolbar; +typedef struct _GdMainToolbarClass GdMainToolbarClass; +typedef struct _GdMainToolbarPrivate GdMainToolbarPrivate; + +struct _GdMainToolbar +{ + GtkToolbar parent; + + GdMainToolbarPrivate *priv; +}; + +struct _GdMainToolbarClass +{ + GtkToolbarClass parent_class; +}; + +GType gd_main_toolbar_get_type (void) G_GNUC_CONST; + +GtkWidget *gd_main_toolbar_new (void); + +void gd_main_toolbar_set_labels (GdMainToolbar *self, + const gchar *primary, + const gchar *detail); + +void gd_main_toolbar_set_labels_menu (GdMainToolbar *self, + GMenuModel *menu); + +GtkWidget * gd_main_toolbar_add_mode (GdMainToolbar *self, + const gchar *label); + +void gd_main_toolbar_clear (GdMainToolbar *self); + +GtkWidget * gd_main_toolbar_add_button (GdMainToolbar *self, + const gchar *icon_name, + const gchar *label, + gboolean pack_start); + +GtkWidget * gd_main_toolbar_add_toggle (GdMainToolbar *self, + const gchar *icon_name, + const gchar *label, + gboolean pack_start); + +GtkWidget * gd_main_toolbar_add_menu (GdMainToolbar *self, + const gchar *icon_name, + const gchar *label, + gboolean pack_start); + +void gd_main_toolbar_add_widget (GdMainToolbar *self, + GtkWidget *widget, + gboolean pack_start); + +gboolean gd_main_toolbar_get_show_modes (GdMainToolbar *self); + +void gd_main_toolbar_set_show_modes (GdMainToolbar *self, + gboolean show_modes); + +G_END_DECLS + +#endif /* __GD_MAIN_TOOLBAR_H__ */ diff --git a/libgd/libgd/gd-main-view-generic.c b/libgd/libgd/gd-main-view-generic.c new file mode 100644 index 00000000..51347e05 --- /dev/null +++ b/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 + * + */ + +#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/libgd/libgd/gd-main-view-generic.h b/libgd/libgd/gd-main-view-generic.h new file mode 100644 index 00000000..dd53e0ee --- /dev/null +++ b/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 + * + */ + +#ifndef __GD_MAIN_VIEW_GENERIC_H__ +#define __GD_MAIN_VIEW_GENERIC_H__ + +#include + +#include + +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/libgd/libgd/gd-main-view.c b/libgd/libgd/gd-main-view.c new file mode 100644 index 00000000..70fc5a31 --- /dev/null +++ b/libgd/libgd/gd-main-view.c @@ -0,0 +1,1183 @@ +/* + * 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 + * + */ + +#include "gd-main-view.h" + +#include "gd-main-view-generic.h" +#include "gd-main-icon-view.h" +#include "gd-main-list-view.h" + +#include + +#define MAIN_VIEW_TYPE_INITIAL -1 +#define MAIN_VIEW_DND_ICON_OFFSET 20 +#define MAIN_VIEW_RUBBERBAND_SELECT_TRIGGER_LENGTH 32 + +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 (GdMainView, gd_main_view, GTK_TYPE_SCROLLED_WINDOW) + +static void +gd_main_view_dispose (GObject *obj) +{ + GdMainView *self = GD_MAIN_VIEW (obj); + + g_clear_object (&self->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); + + g_free (self->priv->button_press_item_path); + g_free (self->priv->last_selected_id); + + if (self->priv->rubberband_select_first_path) + gtk_tree_path_free (self->priv->rubberband_select_first_path); + + if (self->priv->rubberband_select_last_path) + gtk_tree_path_free (self->priv->rubberband_select_last_path); + + G_OBJECT_CLASS (gd_main_view_parent_class)->finalize (obj); +} + +static void +gd_main_view_init (GdMainView *self) +{ + GtkStyleContext *context; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GD_TYPE_MAIN_VIEW, GdMainViewPrivate); + + /* so that we get constructed with the right view even at startup */ + self->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_type_class_add_private (klass, sizeof (GdMainViewPrivate)); + g_object_class_install_properties (oclass, NUM_PROPERTIES, properties); +} + +static cairo_surface_t * +gd_main_view_get_counter_icon (GdMainView *self, + cairo_surface_t *base, + gint number) +{ + GtkStyleContext *context; + cairo_t *cr, *emblem_cr; + cairo_surface_t *surface, *emblem_surface; + gint width, height; + gint layout_width, layout_height; + gint emblem_size; + gdouble scale; + gchar *str; + PangoLayout *layout; + PangoAttrList *attr_list; + PangoAttribute *attr; + PangoFontDescription *desc; + GdkRGBA color; + + context = gtk_widget_get_style_context (GTK_WIDGET (self)); + gtk_style_context_save (context); + gtk_style_context_add_class (context, "documents-counter"); + + width = cairo_image_surface_get_width (base); + height = cairo_image_surface_get_height (base); + + surface = cairo_surface_create_similar (base, CAIRO_CONTENT_COLOR_ALPHA, + width, height); + cr = cairo_create (surface); + cairo_set_source_surface (cr, base, 0, 0); + cairo_paint (cr); + + emblem_size = MIN (width / 2, height / 2); + emblem_surface = cairo_surface_create_similar (base, CAIRO_CONTENT_COLOR_ALPHA, + emblem_size, emblem_size); + 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 (self), 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; +} + +static GdMainViewGeneric * +get_generic (GdMainView *self) +{ + if (self->priv->current_view != NULL) + return GD_MAIN_VIEW_GENERIC (self->priv->current_view); + + return NULL; +} + +static void +do_select_row (GdMainView *self, + GtkTreeIter *iter, + gboolean value) +{ + GtkTreeModel *model; + GtkTreeIter my_iter; + GtkTreePath *path; + + model = self->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 (self->priv->model, iter); + if (path) + { + gtk_tree_model_row_changed (self->priv->model, path, iter); + gtk_tree_path_free (path); + } +} + +static void +selection_mode_do_select_range (GdMainView *self, + GtkTreeIter *first_element, + GtkTreeIter *last_element) +{ + GtkTreeIter iter; + GtkTreePath *path, *last_path; + gboolean equal; + + path = gtk_tree_model_get_path (self->priv->model, first_element); + last_path = gtk_tree_model_get_path (self->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 (self->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 (self->priv->model, &iter)); + + gtk_tree_path_free (last_path); +} + +static void +selection_mode_select_range (GdMainView *self, + GtkTreeIter *iter) +{ + GtkTreeIter other; + gboolean found = FALSE; + gboolean selected; + char *id; + + if (self->priv->last_selected_id != NULL && + gtk_tree_model_get_iter_first (self->priv->model, &other)) + { + do + { + gtk_tree_model_get (self->priv->model, &other, + GD_MAIN_COLUMN_ID, &id, + -1); + if (g_strcmp0 (id, self->priv->last_selected_id) == 0) + { + g_free (id); + found = TRUE; + break; + } + g_free (id); + } + while (gtk_tree_model_iter_next (self->priv->model, &other)); + } + + if (!found) + { + other = *iter; + while (gtk_tree_model_iter_previous (self->priv->model, &other)) + { + gtk_tree_model_get (self->priv->model, &other, + GD_MAIN_COLUMN_SELECTED, &selected, + -1); + + if (selected) + { + found = TRUE; + break; + } + } + } + + if (!found) + { + other = *iter; + while (gtk_tree_model_iter_next (self->priv->model, &other)) + { + gtk_tree_model_get (self->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) +{ + gboolean selected; + GtkTreeIter iter; + char *id; + + if (self->priv->model == NULL) + return FALSE; + + if (!gtk_tree_model_get_iter (self->priv->model, &iter, path)) + return FALSE; + + gtk_tree_model_get (self->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 (self->priv->model, &iter, + GD_MAIN_COLUMN_ID, &id, + -1); + g_free (self->priv->last_selected_id); + self->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) +{ + GtkTreeIter iter; + gchar *id; + + if (self->priv->model == NULL) + return FALSE; + + if (!gtk_tree_model_get_iter (self->priv->model, &iter, path)) + return FALSE; + + gtk_tree_model_get (self->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; + 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; + + /* 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 (self->priv->button_press_item_path, button_release_item_path) == 0) + same_item = TRUE; + + g_free (button_release_item_path); + } + + g_free (self->priv->button_press_item_path); + self->priv->button_press_item_path = NULL; + + self->priv->track_motion = FALSE; + if (self->priv->rubberband_select) + { + self->priv->rubberband_select = FALSE; + gd_main_view_generic_set_rubberband_range (get_generic (self), NULL, NULL); + if (self->priv->rubberband_select_last_path) + { + if (!self->priv->selection_mode) + g_signal_emit (self, signals[SELECTION_MODE_REQUEST], 0); + if (!self->priv->selection_mode) + { + res = FALSE; + goto out; + } + + start_path = gtk_tree_path_copy (self->priv->rubberband_select_first_path); + end_path = gtk_tree_path_copy (self->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 (self->priv->model, + &iter, start_path)) + { + gtk_tree_model_get (self->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 (&self->priv->rubberband_select_first_path, + gtk_tree_path_free); + g_clear_pointer (&self->priv->rubberband_select_last_path, + gtk_tree_path_free); + + res = TRUE; + goto out; + } + + if (!same_item) + { + res = FALSE; + goto out; + } + + selection_mode = self->priv->selection_mode; + + if (!selection_mode) + { + if (event_triggers_selection_mode (event)) + { + g_signal_emit (self, signals[SELECTION_MODE_REQUEST], 0); + if (!self->priv->selection_mode) + { + res = FALSE; + goto out; + } + selection_mode = self->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; + GdMainViewGeneric *generic = get_generic (self); + GtkTreePath *path; + GList *selection, *l; + GtkTreePath *sel_path; + gboolean found = FALSE; + gboolean force_selection; + + path = gd_main_view_generic_get_path_at_pos (generic, event->x, event->y); + + if (path != NULL) + self->priv->button_press_item_path = gtk_tree_path_to_string (path); + + force_selection = event_triggers_selection_mode (event); + if (!self->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) + { + self->priv->track_motion = TRUE; + self->priv->rubberband_select = FALSE; + self->priv->rubberband_select_first_path = NULL; + self->priv->rubberband_select_last_path = NULL; + self->priv->button_down_x = event->x; + self->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; + GtkTreePath *path; + + if (self->priv->track_motion) + { + if (!self->priv->rubberband_select && + (event->x - self->priv->button_down_x) * (event->x - self->priv->button_down_x) + + (event->y - self->priv->button_down_y) * (event->y - self->priv->button_down_y) > + MAIN_VIEW_RUBBERBAND_SELECT_TRIGGER_LENGTH * MAIN_VIEW_RUBBERBAND_SELECT_TRIGGER_LENGTH) + { + self->priv->rubberband_select = TRUE; + if (self->priv->button_press_item_path) + { + self->priv->rubberband_select_first_path = + gtk_tree_path_new_from_string (self->priv->button_press_item_path); + } + } + + if (self->priv->rubberband_select) + { + path = gd_main_view_generic_get_path_at_pos (get_generic (self), event->x, event->y); + if (path != NULL) + { + if (self->priv->rubberband_select_first_path == NULL) + self->priv->rubberband_select_first_path = gtk_tree_path_copy (path); + + if (self->priv->rubberband_select_last_path == NULL || + gtk_tree_path_compare (self->priv->rubberband_select_last_path, path) != 0) + { + if (self->priv->rubberband_select_last_path) + gtk_tree_path_free (self->priv->rubberband_select_last_path); + self->priv->rubberband_select_last_path = path; + + gd_main_view_generic_set_rubberband_range (get_generic (self), + self->priv->rubberband_select_first_path, + self->priv->rubberband_select_last_path); + } + else + gtk_tree_path_free (path); + } + } + } + return FALSE; +} + +static cairo_surface_t * +copy_surface (cairo_surface_t *surface) +{ + cairo_surface_t *copy; + cairo_t *cr; + + copy = cairo_surface_create_similar (surface, CAIRO_CONTENT_COLOR_ALPHA, + cairo_image_surface_get_width (surface), + cairo_image_surface_get_height (surface)); + cr = cairo_create (copy); + cairo_set_source_surface (cr, surface, 0, 0); + cairo_paint (cr); + cairo_destroy (cr); + + return copy; +} + +static void +on_drag_begin (GdMainViewGeneric *generic, + GdkDragContext *drag_context, + gpointer user_data) +{ + GdMainView *self = user_data; + + if (self->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 (self->priv->button_press_item_path); + res = gtk_tree_model_get_iter (self->priv->model, + &iter, path); + if (res) + gtk_tree_model_get (self->priv->model, &iter, + GD_MAIN_COLUMN_ICON, &data, + -1); + + column_gtype = gtk_tree_model_get_column_type (self->priv->model, + GD_MAIN_COLUMN_ICON); + + if (column_gtype == CAIRO_GOBJECT_TYPE_SURFACE) + { + surface = copy_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 (self->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_main_view_get_counter_icon (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) +{ + GdkModifierType state; + + gtk_get_current_event_state (&state); + + if (self->priv->selection_mode || (state & GDK_CONTROL_MASK) != 0) + { + if (!self->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) +{ + GdMainViewGeneric *generic = get_generic (self); + gd_main_view_generic_set_model (generic, self->priv->model); +} + +static void +gd_main_view_apply_selection_mode (GdMainView *self) +{ + GdMainViewGeneric *generic = get_generic (self); + + gd_main_view_generic_set_selection_mode (generic, self->priv->selection_mode); + + if (!self->priv->selection_mode && + self->priv->model != NULL) + gd_main_view_unselect_all (self); +} + +static void +gd_main_view_rebuild (GdMainView *self) +{ + GtkStyleContext *context; + + if (self->priv->current_view != NULL) + gtk_widget_destroy (self->priv->current_view); + + if (self->priv->current_type == GD_MAIN_VIEW_ICON) + { + self->priv->current_view = gd_main_icon_view_new (); + g_signal_connect (self->priv->current_view, "item-activated", + G_CALLBACK (on_icon_view_item_activated), self); + } + else + { + self->priv->current_view = gd_main_list_view_new (); + g_signal_connect (self->priv->current_view, "row-activated", + G_CALLBACK (on_list_view_row_activated), self); + } + + context = gtk_widget_get_style_context (self->priv->current_view); + gtk_style_context_add_class (context, "content-view"); + + gtk_container_add (GTK_CONTAINER (self), self->priv->current_view); + + g_signal_connect (self->priv->current_view, "button-press-event", + G_CALLBACK (on_button_press_event), self); + g_signal_connect (self->priv->current_view, "button-release-event", + G_CALLBACK (on_button_release_event), self); + g_signal_connect (self->priv->current_view, "motion-notify-event", + G_CALLBACK (on_motion_event), self); + g_signal_connect_after (self->priv->current_view, "drag-begin", + G_CALLBACK (on_drag_begin), self); + g_signal_connect (self->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) +{ + if (type != self->priv->current_type) + { + self->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) +{ + return self->priv->current_type; +} + +void +gd_main_view_set_selection_mode (GdMainView *self, + gboolean selection_mode) +{ + if (selection_mode != self->priv->selection_mode) + { + self->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) +{ + return self->priv->selection_mode; +} + +/** + * gd_main_view_set_model: + * @self: + * @model: (allow-none): + * + */ +void +gd_main_view_set_model (GdMainView *self, + GtkTreeModel *model) +{ + if (model != self->priv->model) + { + if (self->priv->model) + g_signal_handlers_disconnect_by_func (self->priv->model, + on_row_deleted_cb, self); + + g_clear_object (&self->priv->model); + + if (model) + { + self->priv->model = g_object_ref (model); + g_signal_connect (self->priv->model, "row-deleted", + G_CALLBACK (on_row_deleted_cb), self); + } + else + { + self->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) +{ + return self->priv->model; +} + +/** + * gd_main_view_get_generic_view: + * @self: + * + * Returns: (transfer none): + */ +GtkWidget * +gd_main_view_get_generic_view (GdMainView *self) +{ + return self->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) +{ + GList *retval = NULL; + + gtk_tree_model_foreach (self->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/libgd/libgd/gd-main-view.h b/libgd/libgd/gd-main-view.h new file mode 100644 index 00000000..29b51654 --- /dev/null +++ b/libgd/libgd/gd-main-view.h @@ -0,0 +1,96 @@ +/* + * 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 + * + */ + +#ifndef __GD_MAIN_VIEW_H__ +#define __GD_MAIN_VIEW_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define GD_TYPE_MAIN_VIEW gd_main_view_get_type() + +#define GD_MAIN_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + GD_TYPE_MAIN_VIEW, GdMainView)) + +#define GD_MAIN_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + GD_TYPE_MAIN_VIEW, GdMainViewIface)) + +#define GD_IS_MAIN_VIEW(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + GD_TYPE_MAIN_VIEW)) + +#define GD_IS_MAIN_VIEW_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + GD_TYPE_MAIN_VIEW)) + +#define GD_MAIN_VIEW_GET_IFACE(obj) \ + (G_TYPE_INSTANCE_GET_INTERFACE ((obj), \ + GD_TYPE_MAIN_VIEW, GdMainViewIface)) + +typedef struct _GdMainView GdMainView; +typedef struct _GdMainViewClass GdMainViewClass; +typedef struct _GdMainViewPrivate GdMainViewPrivate; + +typedef enum { + GD_MAIN_VIEW_ICON, + GD_MAIN_VIEW_LIST +} GdMainViewType; + +struct _GdMainView { + GtkScrolledWindow parent; + + GdMainViewPrivate *priv; +}; + +struct _GdMainViewClass { + GtkScrolledWindowClass parent_class; +}; + +GType gd_main_view_get_type (void) G_GNUC_CONST; + +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/libgd/libgd/gd-margin-container.c b/libgd/libgd/gd-margin-container.c new file mode 100644 index 00000000..51bbdd12 --- /dev/null +++ b/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 + * + */ + +#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/libgd/libgd/gd-margin-container.h b/libgd/libgd/gd-margin-container.h new file mode 100644 index 00000000..3937ea75 --- /dev/null +++ b/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 + * + */ + +#ifndef _GD_MARGIN_CONTAINER_H +#define _GD_MARGIN_CONTAINER_H + +#include + +#include + +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/libgd/libgd/gd-notification.c b/libgd/libgd/gd-notification.c new file mode 100644 index 00000000..81534367 --- /dev/null +++ b/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 + * 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 ."; + */ + +#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/libgd/libgd/gd-notification.h b/libgd/libgd/gd-notification.h new file mode 100644 index 00000000..8efa191a --- /dev/null +++ b/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 + * 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 ."; + */ + +#ifndef _GD_NOTIFICATION_H_ +#define _GD_NOTIFICATION_H_ + +#include + +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/libgd/libgd/gd-revealer.c b/libgd/libgd/gd-revealer.c new file mode 100644 index 00000000..809cf7bb --- /dev/null +++ b/libgd/libgd/gd-revealer.c @@ -0,0 +1,744 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright (c) 2013 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: Alexander Larsson + * + */ + +#include +#include "gd-revealer.h" +#include + +enum { + PROP_0, + PROP_ORIENTATION, + PROP_TRANSITION_DURATION, + PROP_REVEAL_CHILD, + PROP_CHILD_REVEALED +}; + +#define FRAME_TIME_MSEC 17 /* 17 msec => 60 fps */ + +struct _GdRevealerPrivate { + GtkOrientation orientation; + gint transition_duration; + + GdkWindow* bin_window; + GdkWindow* view_window; + + gdouble current_pos; + gdouble source_pos; + gdouble target_pos; + + guint tick_id; + gint64 start_time; + gint64 end_time; +}; + +#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB +#define GD_REVEALER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GD_TYPE_REVEALER, GdRevealerPrivate)) + +static void gd_revealer_real_realize (GtkWidget *widget); +static void gd_revealer_real_unrealize (GtkWidget *widget); +static void gd_revealer_real_add (GtkContainer *widget, + GtkWidget *child); +static void gd_revealer_real_style_updated (GtkWidget *widget); +static void gd_revealer_real_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gd_revealer_real_map (GtkWidget *widget); +static void gd_revealer_real_unmap (GtkWidget *widget); +static gboolean gd_revealer_real_draw (GtkWidget *widget, + cairo_t *cr); +static void gd_revealer_real_get_preferred_height (GtkWidget *widget, + gint *minimum_height, + gint *natural_height); +static void gd_revealer_real_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height); +static void gd_revealer_real_get_preferred_width (GtkWidget *widget, + gint *minimum_width, + gint *natural_width); +static void gd_revealer_real_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width); + +G_DEFINE_TYPE(GdRevealer, gd_revealer, GTK_TYPE_BIN); + +static void +gd_revealer_init (GdRevealer *revealer) +{ + GdRevealerPrivate *priv; + + priv = GD_REVEALER_GET_PRIVATE (revealer); + revealer->priv = priv; + + priv->orientation = GTK_ORIENTATION_HORIZONTAL; + priv->transition_duration = 250; + priv->current_pos = 0.0; + priv->target_pos = 0.0; + + gtk_widget_set_has_window ((GtkWidget*) revealer, TRUE); + gtk_widget_set_redraw_on_allocate ((GtkWidget*) revealer, FALSE); +} + +static void +gd_revealer_finalize (GObject* obj) +{ + GdRevealer *revealer = GD_REVEALER (obj); + GdRevealerPrivate *priv = revealer->priv; + + if (priv->tick_id != 0) + gtk_widget_remove_tick_callback (GTK_WIDGET (revealer), priv->tick_id); + priv->tick_id = 0; + + G_OBJECT_CLASS (gd_revealer_parent_class)->finalize (obj); +} + +static void +gd_revealer_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GdRevealer *revealer = GD_REVEALER (object); + + switch (property_id) { + case PROP_ORIENTATION: + g_value_set_enum (value, gd_revealer_get_orientation (revealer)); + break; + case PROP_TRANSITION_DURATION: + g_value_set_int (value, gd_revealer_get_transition_duration (revealer)); + break; + case PROP_REVEAL_CHILD: + g_value_set_boolean (value, gd_revealer_get_reveal_child (revealer)); + break; + case PROP_CHILD_REVEALED: + g_value_set_boolean (value, gd_revealer_get_child_revealed (revealer)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gd_revealer_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GdRevealer *revealer = GD_REVEALER (object); + + switch (property_id) { + case PROP_ORIENTATION: + gd_revealer_set_orientation (revealer, g_value_get_enum (value)); + break; + case PROP_TRANSITION_DURATION: + gd_revealer_set_transition_duration (revealer, g_value_get_int (value)); + break; + case PROP_REVEAL_CHILD: + gd_revealer_set_reveal_child (revealer, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gd_revealer_class_init (GdRevealerClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->get_property = gd_revealer_get_property; + object_class->set_property = gd_revealer_set_property; + object_class->finalize = gd_revealer_finalize; + + widget_class->realize = gd_revealer_real_realize; + widget_class->unrealize = gd_revealer_real_unrealize; + widget_class->style_updated = gd_revealer_real_style_updated; + widget_class->size_allocate = gd_revealer_real_size_allocate; + widget_class->map = gd_revealer_real_map; + widget_class->unmap = gd_revealer_real_unmap; + widget_class->draw = gd_revealer_real_draw; + widget_class->get_preferred_height = gd_revealer_real_get_preferred_height; + widget_class->get_preferred_height_for_width = gd_revealer_real_get_preferred_height_for_width; + widget_class->get_preferred_width = gd_revealer_real_get_preferred_width; + widget_class->get_preferred_width_for_height = gd_revealer_real_get_preferred_width_for_height; + + container_class->add = gd_revealer_real_add; + + g_object_class_install_property (object_class, + PROP_ORIENTATION, + g_param_spec_enum ("orientation", "orientation", + "The orientation of the widget", + GTK_TYPE_ORIENTATION, + GTK_ORIENTATION_HORIZONTAL, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_TRANSITION_DURATION, + g_param_spec_int ("transition-duration", "Transition duration", + "The animation duration, in milliseconds", + G_MININT, G_MAXINT, + 250, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_REVEAL_CHILD, + g_param_spec_boolean ("reveal-child", "Reveal Child", + "Whether the container should reveal the child", + FALSE, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_CHILD_REVEALED, + g_param_spec_boolean ("child-revealed", "Child Revealed", + "Whether the child is revealed and the animation target reached", + FALSE, + G_PARAM_READABLE)); + + g_type_class_add_private (klass, sizeof (GdRevealerPrivate)); +} + + +GtkWidget * +gd_revealer_new (void) +{ + return g_object_new (GD_TYPE_REVEALER, NULL); +} + +static void +gd_revealer_get_child_allocation (GdRevealer *revealer, + GtkAllocation* allocation, + GtkAllocation* child_allocation) +{ + GtkWidget *child; + GdRevealerPrivate *priv; + + g_return_if_fail (revealer != NULL); + g_return_if_fail (allocation != NULL); + + priv = revealer->priv; + + child_allocation->x = 0; + child_allocation->y = 0; + child_allocation->width = allocation->width; + child_allocation->height = allocation->height; + + child = gtk_bin_get_child (GTK_BIN (revealer)); + if (child != NULL && gtk_widget_get_visible (child)) + { + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + gtk_widget_get_preferred_height_for_width (child, child_allocation->width, NULL, + &child_allocation->height); + else + gtk_widget_get_preferred_width_for_height (child, child_allocation->height, NULL, + &child_allocation->width); + } +} + +static void +gd_revealer_real_realize (GtkWidget *widget) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + GtkAllocation allocation; + GdkWindowAttr attributes = { 0 }; + GdkWindowAttributesType attributes_mask; + GtkAllocation child_allocation; + GtkWidget *child; + GtkStyleContext *context; + + 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 = + gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK; + attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL; + + priv->view_window = + gdk_window_new (gtk_widget_get_parent_window ((GtkWidget*) revealer), + &attributes, attributes_mask); + gtk_widget_set_window (widget, priv->view_window); + gtk_widget_register_window (widget, priv->view_window); + + gd_revealer_get_child_allocation (revealer, &allocation, &child_allocation); + + attributes.x = 0; + attributes.y = 0; + attributes.width = child_allocation.width; + attributes.height = child_allocation.height; + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + attributes.y = allocation.height - child_allocation.height; + else + attributes.x = allocation.width - child_allocation.width; + + priv->bin_window = + gdk_window_new (priv->view_window, &attributes, attributes_mask); + gtk_widget_register_window (widget, priv->bin_window); + + child = gtk_bin_get_child (GTK_BIN (revealer)); + if (child != NULL) + gtk_widget_set_parent_window (child, priv->bin_window); + + context = gtk_widget_get_style_context (widget); + gtk_style_context_set_background (context, priv->view_window); + gtk_style_context_set_background (context, priv->bin_window); + gdk_window_show (priv->bin_window); +} + + +static void +gd_revealer_real_unrealize (GtkWidget* widget) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + + gtk_widget_unregister_window (widget, priv->bin_window); + gdk_window_destroy (priv->bin_window); + priv->view_window = NULL; + + GTK_WIDGET_CLASS (gd_revealer_parent_class)->unrealize (widget); +} + + +static void +gd_revealer_real_add (GtkContainer* container, + GtkWidget* child) +{ + GdRevealer *revealer = GD_REVEALER (container); + GdRevealerPrivate *priv = revealer->priv; + + g_return_if_fail (child != NULL); + + gtk_widget_set_parent_window (child, priv->bin_window); + gtk_widget_set_child_visible (child, priv->current_pos != 0.0); + + GTK_CONTAINER_CLASS (gd_revealer_parent_class)->add (container, child); +} + + +static void +gd_revealer_real_style_updated (GtkWidget* widget) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + GtkStyleContext* context; + + GTK_WIDGET_CLASS (gd_revealer_parent_class)->style_updated (widget); + + if (gtk_widget_get_realized (widget)) + { + context = gtk_widget_get_style_context (widget); + gtk_style_context_set_background (context, priv->bin_window); + gtk_style_context_set_background (context, priv->view_window); + } +} + + +static void +gd_revealer_real_size_allocate (GtkWidget* widget, + GtkAllocation* allocation) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + GtkAllocation child_allocation; + GtkWidget *child; + gboolean window_visible; + int bin_x, bin_y; + + g_return_if_fail (allocation != NULL); + + gtk_widget_set_allocation (widget, allocation); + gd_revealer_get_child_allocation (revealer, allocation, &child_allocation); + + child = gtk_bin_get_child (GTK_BIN (revealer)); + if (child != NULL && + gtk_widget_get_visible (child)) + gtk_widget_size_allocate (child, &child_allocation); + + if (gtk_widget_get_realized (widget)) + { + if (gtk_widget_get_mapped (widget)) + { + window_visible = + allocation->width > 0 && allocation->height > 0; + + if (!window_visible && + gdk_window_is_visible (priv->view_window)) + gdk_window_hide (priv->view_window); + + if (window_visible && + !gdk_window_is_visible (priv->view_window)) + gdk_window_show (priv->view_window); + } + + gdk_window_move_resize (priv->view_window, + allocation->x, allocation->y, + allocation->width, allocation->height); + + bin_x = 0; + bin_y = 0; + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + bin_y = allocation->height - child_allocation.height; + else + bin_x = allocation->width - child_allocation.width; + + gdk_window_move_resize (priv->bin_window, + bin_x, bin_y, + child_allocation.width, child_allocation.height); + } +} + + +static void +gd_revealer_set_position (GdRevealer *revealer, + gdouble pos) +{ + GdRevealerPrivate *priv = revealer->priv; + gboolean new_visible; + GtkWidget *child; + + priv->current_pos = pos; + + /* We check target_pos here too, because we want to ensure we set + * child_visible immediately when starting a reveal operation + * otherwise the child widgets will not be properly realized + * after the reveal returns. + */ + new_visible = priv->current_pos != 0.0 || priv->target_pos != 0.0; + + child = gtk_bin_get_child (GTK_BIN (revealer)); + if (child != NULL && + new_visible != gtk_widget_get_child_visible (child)) + gtk_widget_set_child_visible (child, new_visible); + + gtk_widget_queue_resize (GTK_WIDGET (revealer)); + + if (priv->current_pos == priv->target_pos) + g_object_notify (G_OBJECT (revealer), "child-revealed"); +} + +static gdouble +ease_out_quad (gdouble t, gdouble d) +{ + gdouble p = t / d; + return ((-1.0) * p) * (p - 2); +} + +static void +gd_revealer_animate_step (GdRevealer *revealer, + gint64 now) +{ + GdRevealerPrivate *priv = revealer->priv; + gdouble t; + + t = 1.0; + if (now < priv->end_time) + t = (now - priv->start_time) / (double) (priv->end_time - priv->start_time); + t = ease_out_quad (t, 1.0); + + gd_revealer_set_position (revealer, + priv->source_pos + (t * (priv->target_pos - priv->source_pos))); +} + +static gboolean +gd_revealer_animate_cb (GdRevealer *revealer, + GdkFrameClock *frame_clock, + gpointer user_data) +{ + GdRevealerPrivate *priv = revealer->priv; + gint64 now; + + now = gdk_frame_clock_get_frame_time (frame_clock); + gd_revealer_animate_step (revealer, now); + if (priv->current_pos == priv->target_pos) + { + priv->tick_id = 0; + return FALSE; + } + + return TRUE; +} + +static void +gd_revealer_start_animation (GdRevealer *revealer, + gdouble target) +{ + GdRevealerPrivate *priv = revealer->priv; + GtkWidget *widget = GTK_WIDGET (revealer); + + if (priv->target_pos == target) + return; + + priv->target_pos = target; + g_object_notify (G_OBJECT (revealer), "reveal-child"); + + if (gtk_widget_get_mapped (widget)) + { + priv->source_pos = priv->current_pos; + priv->start_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget)); + priv->end_time = priv->start_time + (priv->transition_duration * 1000); + if (priv->tick_id == 0) + priv->tick_id = + gtk_widget_add_tick_callback (widget, (GtkTickCallback)gd_revealer_animate_cb, revealer, NULL); + gd_revealer_animate_step (revealer, priv->start_time); + } + else + { + gd_revealer_set_position (revealer, target); + } +} + + +static void +gd_revealer_stop_animation (GdRevealer *revealer) +{ + GdRevealerPrivate *priv = revealer->priv; + + priv->current_pos = priv->target_pos; + if (priv->tick_id != 0) + { + gtk_widget_remove_tick_callback (GTK_WIDGET (revealer), priv->tick_id); + priv->tick_id = 0; + } +} + + +static void +gd_revealer_real_map (GtkWidget *widget) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + GtkAllocation allocation; + + if (!gtk_widget_get_mapped (widget)) + { + gtk_widget_get_allocation (widget, &allocation); + + if (allocation.width > 0 && allocation.height > 0) + gdk_window_show (priv->view_window); + + gd_revealer_start_animation (revealer, priv->target_pos); + } + + GTK_WIDGET_CLASS (gd_revealer_parent_class)->map (widget); +} + +static void +gd_revealer_real_unmap (GtkWidget *widget) +{ + GdRevealer *revealer = GD_REVEALER (widget); + + GTK_WIDGET_CLASS (gd_revealer_parent_class)->unmap (widget); + + gd_revealer_stop_animation (revealer); +} + + +static gboolean +gd_revealer_real_draw (GtkWidget *widget, + cairo_t *cr) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + + if (gtk_cairo_should_draw_window (cr, priv->bin_window)) + GTK_WIDGET_CLASS (gd_revealer_parent_class)->draw (widget, cr); + + return TRUE; +} + +void +gd_revealer_set_reveal_child (GdRevealer *revealer, + gboolean setting) +{ + g_return_if_fail (GD_IS_REVEALER (revealer)); + + if (setting) + gd_revealer_start_animation (revealer, 1.0); + else + gd_revealer_start_animation (revealer, 0.0); +} + +gboolean +gd_revealer_get_reveal_child (GdRevealer *revealer) +{ + g_return_val_if_fail (GD_IS_REVEALER (revealer), FALSE); + + return revealer->priv->target_pos != 0.0; +} + +gboolean +gd_revealer_get_child_revealed (GdRevealer *revealer) +{ + gboolean animation_finished = (revealer->priv->target_pos == revealer->priv->current_pos); + gboolean reveal_child = gd_revealer_get_reveal_child (revealer); + + if (animation_finished) + return reveal_child; + else + return !reveal_child; +} + +/* These all report only the natural size, ignoring the minimal size, + * because its not really possible to allocate the right size during + * animation if the child size can change (without the child + * re-arranging itself during the animation). + */ + +static void +gd_revealer_real_get_preferred_height (GtkWidget* widget, + gint* minimum_height_out, + gint* natural_height_out) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + gint minimum_height; + gint natural_height; + + GTK_WIDGET_CLASS (gd_revealer_parent_class)->get_preferred_height (widget, &minimum_height, &natural_height); + + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + natural_height = round (natural_height * priv->current_pos); + + minimum_height = natural_height; + + if (minimum_height_out) + *minimum_height_out = minimum_height; + if (natural_height_out) + *natural_height_out = natural_height; +} + +static void +gd_revealer_real_get_preferred_height_for_width (GtkWidget* widget, + gint width, + gint* minimum_height_out, + gint* natural_height_out) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + gint minimum_height; + gint natural_height; + + GTK_WIDGET_CLASS (gd_revealer_parent_class)->get_preferred_height_for_width (widget, width, &minimum_height, &natural_height); + if (priv->orientation == GTK_ORIENTATION_HORIZONTAL) + natural_height = round (natural_height * priv->current_pos); + + minimum_height = natural_height; + + if (minimum_height_out) + *minimum_height_out = minimum_height; + if (natural_height_out) + *natural_height_out = natural_height; +} + +static void +gd_revealer_real_get_preferred_width (GtkWidget* widget, + gint* minimum_width_out, + gint* natural_width_out) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + gint minimum_width; + gint natural_width; + + GTK_WIDGET_CLASS (gd_revealer_parent_class)->get_preferred_width (widget, &minimum_width, &natural_width); + + if (priv->orientation == GTK_ORIENTATION_VERTICAL) + natural_width = round (natural_width * priv->current_pos); + + minimum_width = natural_width; + + if (minimum_width_out) + *minimum_width_out = minimum_width; + if (natural_width_out) + *natural_width_out = natural_width; +} + +static void +gd_revealer_real_get_preferred_width_for_height (GtkWidget* widget, + gint height, + gint* minimum_width_out, + gint* natural_width_out) +{ + GdRevealer *revealer = GD_REVEALER (widget); + GdRevealerPrivate *priv = revealer->priv; + gint minimum_width; + gint natural_width; + + GTK_WIDGET_CLASS (gd_revealer_parent_class)->get_preferred_width_for_height (widget, height, &minimum_width, &natural_width); + + if (priv->orientation == GTK_ORIENTATION_VERTICAL) + natural_width = round (natural_width * priv->current_pos); + + minimum_width = natural_width; + + if (minimum_width_out) + *minimum_width_out = minimum_width; + if (natural_width_out) + *natural_width_out = natural_width; +} + +GtkOrientation +gd_revealer_get_orientation (GdRevealer *revealer) +{ + g_return_val_if_fail (revealer != NULL, 0); + + return revealer->priv->orientation; +} + +void +gd_revealer_set_orientation (GdRevealer *revealer, + GtkOrientation value) +{ + g_return_if_fail (GD_IS_REVEALER (revealer)); + + revealer->priv->orientation = value; + g_object_notify (G_OBJECT (revealer), "orientation"); +} + +gint +gd_revealer_get_transition_duration (GdRevealer *revealer) +{ + g_return_val_if_fail (revealer != NULL, 0); + + return revealer->priv->transition_duration; +} + +void +gd_revealer_set_transition_duration (GdRevealer *revealer, + gint value) +{ + g_return_if_fail (GD_IS_REVEALER (revealer)); + + revealer->priv->transition_duration = value; + g_object_notify (G_OBJECT (revealer), "transition-duration"); +} diff --git a/libgd/libgd/gd-revealer.h b/libgd/libgd/gd-revealer.h new file mode 100644 index 00000000..5a3e3cc2 --- /dev/null +++ b/libgd/libgd/gd-revealer.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2013 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: Alexander Larsson + * + */ + +#ifndef __GD_REVEALER_H__ +#define __GD_REVEALER_H__ + +#include + +G_BEGIN_DECLS + + +#define GD_TYPE_REVEALER (gd_revealer_get_type ()) +#define GD_REVEALER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_REVEALER, GdRevealer)) +#define GD_REVEALER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_REVEALER, GdRevealerClass)) +#define GD_IS_REVEALER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_REVEALER)) +#define GD_IS_REVEALER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_REVEALER)) +#define GD_REVEALER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_REVEALER, GdRevealerClass)) + +typedef struct _GdRevealer GdRevealer; +typedef struct _GdRevealerClass GdRevealerClass; +typedef struct _GdRevealerPrivate GdRevealerPrivate; + +struct _GdRevealer { + GtkBin parent_instance; + GdRevealerPrivate * priv; +}; + +struct _GdRevealerClass { + GtkBinClass parent_class; +}; + + +GType gd_revealer_get_type (void) G_GNUC_CONST; +GtkWidget* gd_revealer_new (void); +gboolean gd_revealer_get_reveal_child (GdRevealer *revealer); +void gd_revealer_set_reveal_child (GdRevealer *revealer, + gboolean setting); +GtkOrientation gd_revealer_get_orientation (GdRevealer *revealer); +void gd_revealer_set_orientation (GdRevealer *revealer, + GtkOrientation value); +gint gd_revealer_get_transition_duration (GdRevealer *revealer); +void gd_revealer_set_transition_duration (GdRevealer *revealer, + gint duration_msec); + +gboolean gd_revealer_get_child_revealed (GdRevealer *revealer); + + +G_END_DECLS + +#endif diff --git a/libgd/libgd/gd-stack-switcher.c b/libgd/libgd/gd-stack-switcher.c new file mode 100644 index 00000000..df312794 --- /dev/null +++ b/libgd/libgd/gd-stack-switcher.c @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2013 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-stack-switcher.h" +#include "gd-header-button.h" + +struct _GdStackSwitcherPrivate +{ + GdStack *stack; + GHashTable *buttons; + gboolean in_child_changed; +}; + +enum { + PROP_0, + PROP_STACK +}; + +G_DEFINE_TYPE (GdStackSwitcher, gd_stack_switcher, GTK_TYPE_BOX); + +static void +gd_stack_switcher_init (GdStackSwitcher *switcher) +{ + GtkStyleContext *context; + GdStackSwitcherPrivate *priv; + + priv = G_TYPE_INSTANCE_GET_PRIVATE (switcher, GD_TYPE_STACK_SWITCHER, GdStackSwitcherPrivate); + switcher->priv = priv; + + priv->stack = NULL; + priv->buttons = g_hash_table_new (g_direct_hash, g_direct_equal); + + context = gtk_widget_get_style_context (GTK_WIDGET (switcher)); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_LINKED); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (switcher), GTK_ORIENTATION_HORIZONTAL); +} + +static void +clear_switcher (GdStackSwitcher *self) +{ + gtk_container_foreach (GTK_CONTAINER (self), (GtkCallback) gtk_widget_destroy, self); +} + +static void +on_button_clicked (GtkWidget *widget, + GdStackSwitcher *self) +{ + GtkWidget *child; + + if (!self->priv->in_child_changed) + { + child = g_object_get_data (G_OBJECT (widget), "stack-child"); + gd_stack_set_visible_child (self->priv->stack, child); + } +} + +static void +update_button (GdStackSwitcher *self, + GtkWidget *widget, + GtkWidget *button) +{ + char *title; + char *symbolic_icon_name; + + gtk_container_child_get (GTK_CONTAINER (self->priv->stack), widget, + "title", &title, + "symbolic-icon-name", &symbolic_icon_name, + NULL); + + gd_header_button_set_symbolic_icon_name (GD_HEADER_BUTTON (button), symbolic_icon_name); + gd_header_button_set_label (GD_HEADER_BUTTON (button), title); + + gtk_widget_set_visible (button, title != NULL || symbolic_icon_name != NULL); + + if (symbolic_icon_name != NULL) + gtk_widget_set_size_request (button, -1, -1); + else + gtk_widget_set_size_request (button, 100, -1); + + g_free (title); + g_free (symbolic_icon_name); +} + +static void +on_title_icon_updated (GtkWidget *widget, + GParamSpec *pspec, + GdStackSwitcher *self) + +{ + GtkWidget *button; + + button = g_hash_table_lookup (self->priv->buttons, widget); + update_button (self, widget, button); +} + +static void +on_position_updated (GtkWidget *widget, + GParamSpec *pspec, + GdStackSwitcher *self) +{ + GtkWidget *button; + gint position; + + button = g_hash_table_lookup (self->priv->buttons, widget); + + gtk_container_child_get (GTK_CONTAINER (self->priv->stack), widget, + "position", &position, + NULL); + + gtk_box_reorder_child (GTK_BOX (self), button, position); +} + +static void +add_child (GdStackSwitcher *self, + GtkWidget *widget) +{ + GtkWidget *button; + GList *group; + GtkStyleContext *context; + + button = gd_header_radio_button_new (); + update_button (self, widget, button); + + group = gtk_container_get_children (GTK_CONTAINER (self)); + if (group != NULL) + { + gtk_radio_button_join_group (GTK_RADIO_BUTTON (button), GTK_RADIO_BUTTON (group->data)); + g_list_free (group); + } + + gtk_container_add (GTK_CONTAINER (self), button); + + g_object_set_data (G_OBJECT (button), "stack-child", widget); + g_signal_connect (button, "clicked", G_CALLBACK (on_button_clicked), self); + g_signal_connect (widget, "child-notify::title", G_CALLBACK (on_title_icon_updated), self); + g_signal_connect (widget, "child-notify::symbolic-icon-name", G_CALLBACK (on_title_icon_updated), self); + g_signal_connect (widget, "child-notify::position", G_CALLBACK (on_position_updated), self); + + g_hash_table_insert (self->priv->buttons, widget, button); +} + +static void +foreach_stack (GtkWidget *widget, + GdStackSwitcher *self) +{ + add_child (self, widget); +} + +static void +populate_switcher (GdStackSwitcher *self) +{ + gtk_container_foreach (GTK_CONTAINER (self->priv->stack), (GtkCallback)foreach_stack, self); +} + +static void +on_child_changed (GtkWidget *widget, + GParamSpec *pspec, + GdStackSwitcher *self) +{ + GtkWidget *child; + GtkWidget *button; + + child = gd_stack_get_visible_child (GD_STACK (widget)); + button = g_hash_table_lookup (self->priv->buttons, child); + if (button != NULL) + { + self->priv->in_child_changed = TRUE; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE); + self->priv->in_child_changed = FALSE; + } +} + +static void +on_stack_child_added (GtkContainer *container, + GtkWidget *widget, + GdStackSwitcher *self) +{ + add_child (self, widget); +} + +static void +on_stack_child_removed (GtkContainer *container, + GtkWidget *widget, + GdStackSwitcher *self) +{ + GtkWidget *button; + + button = g_hash_table_lookup (self->priv->buttons, widget); + gtk_container_remove (GTK_CONTAINER (self), button); + g_hash_table_remove (self->priv->buttons, widget); +} + +static void +disconnect_stack_signals (GdStackSwitcher *switcher) +{ + GdStackSwitcherPrivate *priv = switcher->priv; + + g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_added, switcher); + + g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_removed, switcher); + + g_signal_handlers_disconnect_by_func (priv->stack, on_child_changed, switcher); + + g_signal_handlers_disconnect_by_func (priv->stack, disconnect_stack_signals, switcher); +} + +static void +connect_stack_signals (GdStackSwitcher *switcher) +{ + GdStackSwitcherPrivate *priv = switcher->priv; + + g_signal_connect_after (priv->stack, "add", + G_CALLBACK (on_stack_child_added), switcher); + g_signal_connect_after (priv->stack, "remove", + G_CALLBACK (on_stack_child_removed), switcher); + g_signal_connect (priv->stack, "notify::visible-child", + G_CALLBACK (on_child_changed), switcher); + + g_signal_connect_swapped (priv->stack, "destroy", + G_CALLBACK (disconnect_stack_signals), switcher); +} + +/** + * gd_stack_switcher_set_stack: + * @switcher: a #GdStackSwitcher + * @stack: (allow-none): a #GdStack + * + * Sets the stack to control. + * + **/ +void +gd_stack_switcher_set_stack (GdStackSwitcher *switcher, + GdStack *stack) +{ + GdStackSwitcherPrivate *priv; + + g_return_if_fail (GD_IS_STACK_SWITCHER (switcher)); + if (stack) + g_return_if_fail (GD_IS_STACK (stack)); + + priv = switcher->priv; + + if (priv->stack == stack) + return; + + if (priv->stack) + { + disconnect_stack_signals (switcher); + clear_switcher (switcher); + g_clear_object (&priv->stack); + } + + if (stack) + { + priv->stack = g_object_ref (stack); + populate_switcher (switcher); + connect_stack_signals (switcher); + } + + gtk_widget_queue_resize (GTK_WIDGET (switcher)); + + g_object_notify (G_OBJECT (switcher), "stack"); +} + +/** + * gd_stack_switcher_get_stack: + * @switcher: a #GdStackSwitcher + * + * Retrieves the stack. See + * gd_stack_switcher_set_stack(). + * + * Return value: (transfer none): the stack, or %NULL if + * none has been set explicitly. + **/ +GdStack * +gd_stack_switcher_get_stack (GdStackSwitcher *switcher) +{ + g_return_val_if_fail (GD_IS_STACK_SWITCHER (switcher), NULL); + + return switcher->priv->stack; +} + +static void +gd_stack_switcher_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdStackSwitcher *switcher = GD_STACK_SWITCHER (object); + GdStackSwitcherPrivate *priv = switcher->priv; + + switch (prop_id) + { + case PROP_STACK: + g_value_set_object (value, priv->stack); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gd_stack_switcher_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdStackSwitcher *switcher = GD_STACK_SWITCHER (object); + + switch (prop_id) + { + case PROP_STACK: + gd_stack_switcher_set_stack (switcher, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gd_stack_switcher_dispose (GObject *object) +{ + GdStackSwitcher *switcher = GD_STACK_SWITCHER (object); + + gd_stack_switcher_set_stack (switcher, NULL); + + G_OBJECT_CLASS (gd_stack_switcher_parent_class)->dispose (object); +} + +static void +gd_stack_switcher_class_init (GdStackSwitcherClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->get_property = gd_stack_switcher_get_property; + object_class->set_property = gd_stack_switcher_set_property; + object_class->dispose = gd_stack_switcher_dispose; + + g_object_class_install_property (object_class, + PROP_STACK, + g_param_spec_object ("stack", + "Stack", + "Stack", + GD_TYPE_STACK, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT | + G_PARAM_STATIC_STRINGS)); + + g_type_class_add_private (object_class, sizeof (GdStackSwitcherPrivate)); +} + +GtkWidget * +gd_stack_switcher_new (void) +{ + return GTK_WIDGET (g_object_new (GD_TYPE_STACK_SWITCHER, NULL)); +} diff --git a/libgd/libgd/gd-stack-switcher.h b/libgd/libgd/gd-stack-switcher.h new file mode 100644 index 00000000..57e99e89 --- /dev/null +++ b/libgd/libgd/gd-stack-switcher.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2013 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_STACK_SWITCHER_H__ +#define __GD_STACK_SWITCHER_H__ + +#include +#include "gd-stack.h" + +G_BEGIN_DECLS + +#define GD_TYPE_STACK_SWITCHER (gd_stack_switcher_get_type ()) +#define GD_STACK_SWITCHER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_STACK_SWITCHER, GdStackSwitcher)) +#define GD_STACK_SWITCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_STACK_SWITCHER, GdStackSwitcherClass)) +#define GD_IS_STACK_SWITCHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_STACK_SWITCHER)) +#define GD_IS_STACK_SWITCHER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_STACK_SWITCHER)) +#define GD_STACK_SWITCHER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_STACK_SWITCHER, GdStackSwitcherClass)) + +typedef struct _GdStackSwitcher GdStackSwitcher; +typedef struct _GdStackSwitcherPrivate GdStackSwitcherPrivate; +typedef struct _GdStackSwitcherClass GdStackSwitcherClass; + +struct _GdStackSwitcher +{ + GtkBox widget; + + /*< private >*/ + GdStackSwitcherPrivate *priv; +}; + +struct _GdStackSwitcherClass +{ + GtkBoxClass parent_class; + + /* Padding for future expansion */ + void (*_gd_reserved1) (void); + void (*_gd_reserved2) (void); + void (*_gd_reserved3) (void); + void (*_gd_reserved4) (void); +}; + +GType gd_stack_switcher_get_type (void) G_GNUC_CONST; +GtkWidget *gd_stack_switcher_new (void); +void gd_stack_switcher_set_stack (GdStackSwitcher *switcher, + GdStack *stack); +GdStack *gd_stack_switcher_get_stack (GdStackSwitcher *switcher); + +G_END_DECLS + +#endif /* __GD_STACK_SWITCHER_H__ */ diff --git a/libgd/libgd/gd-stack.c b/libgd/libgd/gd-stack.c new file mode 100644 index 00000000..98872b60 --- /dev/null +++ b/libgd/libgd/gd-stack.c @@ -0,0 +1,1419 @@ + + + +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* + * Copyright (c) 2013 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: Alexander Larsson + * + */ + +#include +#include "gd-stack.h" +#include +#include + +/* TODO: + * more transiton types (slides) + * filter events out events to the last_child widget during transitions + */ + +enum { + PROP_0, + PROP_HOMOGENEOUS, + PROP_VISIBLE_CHILD, + PROP_VISIBLE_CHILD_NAME, + PROP_TRANSITION_DURATION, + PROP_TRANSITION_TYPE, +}; + +enum +{ + CHILD_PROP_0, + CHILD_PROP_NAME, + CHILD_PROP_TITLE, + CHILD_PROP_SYMBOLIC_ICON_NAME, + CHILD_PROP_POSITION +}; + +typedef struct _GdStackChildInfo GdStackChildInfo; + +struct _GdStackChildInfo { + GtkWidget *widget; + char *name; + char *title; + char *symbolic_icon_name; +}; + +struct _GdStackPrivate { + GList *children; + + GdkWindow* bin_window; + GdkWindow* view_window; + + GdStackChildInfo *visible_child; + + gboolean homogeneous; + + GdStackTransitionType transition_type; + gint transition_duration; + + GdStackChildInfo *last_visible_child; + cairo_surface_t *last_visible_surface; + GtkAllocation last_visible_surface_allocation; + gdouble transition_pos; + + guint tick_id; + gint64 start_time; + gint64 end_time; +}; + +#define GTK_PARAM_READWRITE G_PARAM_READWRITE|G_PARAM_STATIC_NAME|G_PARAM_STATIC_NICK|G_PARAM_STATIC_BLURB +#define GD_STACK_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GD_TYPE_STACK, GdStackPrivate)) + +static void gd_stack_add (GtkContainer *widget, + GtkWidget *child); +static void gd_stack_remove (GtkContainer *widget, + GtkWidget *child); +static void gd_stack_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data); +static void gd_stack_compute_expand (GtkWidget *widget, + gboolean *hexpand, + gboolean *vexpand); +static void gd_stack_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gboolean gd_stack_draw (GtkWidget *widget, + cairo_t *cr); +static void gd_stack_get_preferred_height (GtkWidget *widget, + gint *minimum_height, + gint *natural_height); +static void gd_stack_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height); +static void gd_stack_get_preferred_width (GtkWidget *widget, + gint *minimum_width, + gint *natural_width); +static void gd_stack_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width); +static void gd_stack_finalize (GObject *obj); +static void gd_stack_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gd_stack_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gd_stack_get_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void gd_stack_set_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gd_stack_unschedule_ticks (GdStack *stack); +static int get_bin_window_x (GdStack *stack, + GtkAllocation *allocation); + +G_DEFINE_TYPE(GdStack, gd_stack, GTK_TYPE_CONTAINER); + +static void +gd_stack_init (GdStack *stack) +{ + stack->priv = GD_STACK_GET_PRIVATE (stack); + + gtk_widget_set_has_window ((GtkWidget*) stack, TRUE); + gtk_widget_set_redraw_on_allocate ((GtkWidget*) stack, TRUE); +} + +static void +gd_stack_finalize (GObject* obj) +{ + GdStack *stack = GD_STACK (obj); + GdStackPrivate *priv = stack->priv; + + gd_stack_unschedule_ticks (stack); + + if (priv->last_visible_surface != NULL) + cairo_surface_destroy (priv->last_visible_surface); + + G_OBJECT_CLASS (gd_stack_parent_class)->finalize (obj); +} + +static void +gd_stack_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GdStack *stack = GD_STACK (object); + GdStackPrivate *priv = stack->priv; + + switch (property_id) + { + case PROP_HOMOGENEOUS: + g_value_set_boolean (value, priv->homogeneous); + break; + case PROP_VISIBLE_CHILD: + g_value_set_object (value, priv->visible_child); + break; + case PROP_VISIBLE_CHILD_NAME: + g_value_set_string (value, gd_stack_get_visible_child_name (stack)); + break; + case PROP_TRANSITION_DURATION: + g_value_set_int (value, gd_stack_get_transition_duration (stack)); + break; + case PROP_TRANSITION_TYPE: + g_value_set_int (value, gd_stack_get_transition_type (stack)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gd_stack_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GdStack *stack = GD_STACK (object); + + switch (property_id) + { + case PROP_HOMOGENEOUS: + gd_stack_set_homogeneous (stack, g_value_get_boolean (value)); + break; + case PROP_VISIBLE_CHILD: + gd_stack_set_visible_child (stack, g_value_get_object (value)); + break; + case PROP_VISIBLE_CHILD_NAME: + gd_stack_set_visible_child_name (stack, g_value_get_string (value)); + break; + case PROP_TRANSITION_DURATION: + gd_stack_set_transition_duration (stack, g_value_get_int (value)); + break; + case PROP_TRANSITION_TYPE: + gd_stack_set_transition_type (stack, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gd_stack_realize (GtkWidget *widget) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + GtkAllocation allocation; + GdkWindowAttr attributes = { 0 }; + GdkWindowAttributesType attributes_mask; + GdStackChildInfo *info; + GList *l; + + 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 = + gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK; + attributes_mask = (GDK_WA_X | GDK_WA_Y) | GDK_WA_VISUAL; + + priv->view_window = + gdk_window_new (gtk_widget_get_parent_window ((GtkWidget*) stack), + &attributes, attributes_mask); + gtk_widget_set_window (widget, priv->view_window); + gtk_widget_register_window (widget, priv->view_window); + + attributes.x = get_bin_window_x (stack, &allocation); + attributes.y = 0; + attributes.width = allocation.width; + attributes.height = allocation.height; + + priv->bin_window = + gdk_window_new (priv->view_window, &attributes, attributes_mask); + gtk_widget_register_window (widget, priv->bin_window); + + for (l = priv->children; l != NULL; l = l->next) + { + info = l->data; + + gtk_widget_set_parent_window (info->widget, priv->bin_window); + } + + gdk_window_show (priv->bin_window); +} + +static void +gd_stack_unrealize (GtkWidget* widget) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + + gtk_widget_unregister_window (widget, priv->bin_window); + gdk_window_destroy (priv->bin_window); + priv->view_window = NULL; + + GTK_WIDGET_CLASS (gd_stack_parent_class)->unrealize (widget); +} + +static void +gd_stack_class_init (GdStackClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); + GtkContainerClass *container_class = GTK_CONTAINER_CLASS (klass); + + object_class->get_property = gd_stack_get_property; + object_class->set_property = gd_stack_set_property; + object_class->finalize = gd_stack_finalize; + + widget_class->size_allocate = gd_stack_size_allocate; + widget_class->draw = gd_stack_draw; + widget_class->realize = gd_stack_realize; + widget_class->unrealize = gd_stack_unrealize; + widget_class->get_preferred_height = gd_stack_get_preferred_height; + widget_class->get_preferred_height_for_width = gd_stack_get_preferred_height_for_width; + widget_class->get_preferred_width = gd_stack_get_preferred_width; + widget_class->get_preferred_width_for_height = gd_stack_get_preferred_width_for_height; + widget_class->compute_expand = gd_stack_compute_expand; + + container_class->add = gd_stack_add; + container_class->remove = gd_stack_remove; + container_class->forall = gd_stack_forall; + container_class->set_child_property = gd_stack_set_child_property; + container_class->get_child_property = gd_stack_get_child_property; + /*container_class->get_path_for_child = gd_stack_get_path_for_child; */ + gtk_container_class_handle_border_width (container_class); + + g_object_class_install_property (object_class, + PROP_HOMOGENEOUS, + g_param_spec_boolean ("homogeneous", + "Homogeneous", + "Homogeneous sizing", + TRUE, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_VISIBLE_CHILD, + g_param_spec_object ("visible-child", + "Visible child", + "The widget currently visible in the stack", + GTK_TYPE_WIDGET, + GTK_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_VISIBLE_CHILD_NAME, + g_param_spec_string ("visible-child-name", + "Name of visible child", + "The name of the widget currently visible in the stack", + NULL, + GTK_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_TRANSITION_DURATION, + g_param_spec_int ("transition-duration", + "Transition duration", + "The animation duration, in milliseconds", + G_MININT, G_MAXINT, + 200, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, + PROP_TRANSITION_TYPE, + g_param_spec_int ("transition-type", + "Transition type", + "The type of animation used to transition", + GD_STACK_TRANSITION_TYPE_NONE, + G_MAXINT, + GD_STACK_TRANSITION_TYPE_NONE, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + gtk_container_class_install_child_property (container_class, CHILD_PROP_NAME, + g_param_spec_string ("name", + "Name", + "The name of the child page", + NULL, + GTK_PARAM_READWRITE)); + + gtk_container_class_install_child_property (container_class, CHILD_PROP_TITLE, + g_param_spec_string ("title", + "Title", + "The title of the child page", + NULL, + GTK_PARAM_READWRITE)); + + gtk_container_class_install_child_property (container_class, CHILD_PROP_SYMBOLIC_ICON_NAME, + g_param_spec_string ("symbolic-icon-name", + "Symbolic icon name", + "The symbolic icon name of the child page", + NULL, + GTK_PARAM_READWRITE)); + + gtk_container_class_install_child_property (container_class, CHILD_PROP_POSITION, + g_param_spec_int ("position", + "Position", + "The index of the child in the parent", + -1, G_MAXINT, 0, + GTK_PARAM_READWRITE)); + + g_type_class_add_private (klass, sizeof (GdStackPrivate)); +} + + +GtkWidget * +gd_stack_new (void) +{ + return g_object_new (GD_TYPE_STACK, NULL); +} + +static GdStackChildInfo * +find_child_info_for_widget (GdStack *stack, + GtkWidget *child) +{ + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *info; + GList *l; + + for (l = priv->children; l != NULL; l = l->next) + { + info = l->data; + if (info->widget == child) + return info; + } + + return NULL; +} + +static void +reorder_child (GdStack *stack, + GtkWidget *child, + gint position) +{ + GdStackPrivate *priv; + GList *l; + GList *old_link = NULL; + GList *new_link = NULL; + GdStackChildInfo *child_info; + gint num = 0; + + priv = stack->priv; + + l = priv->children; + + /* Loop to find the old position and link of child, new link of child and + * total number of children. new_link will be NULL if the child should be + * moved to the end (in case of position being < 0 || >= num) + */ + while (l && (new_link == NULL || old_link == NULL)) + { + /* Record the new position if found */ + if (position == num) + new_link = l; + + if (old_link == NULL) + { + GdStackChildInfo *info; + info = l->data; + + /* Keep trying to find the current position and link location of the + child */ + if (info->widget == child) + { + old_link = l; + child_info = info; + } + } + + l = g_list_next (l); + num++; + } + + g_return_if_fail (old_link != NULL); + + if (old_link == new_link || (g_list_next (old_link) == NULL && new_link == NULL)) + return; + + priv->children = g_list_delete_link (priv->children, old_link); + priv->children = g_list_insert_before (priv->children, new_link, child_info); + + gtk_widget_child_notify (child, "position"); +} + +static void +gd_stack_get_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GdStack *stack = GD_STACK (container); + GdStackChildInfo *info; + GList *list; + guint i; + + info = find_child_info_for_widget (stack, child); + if (info == NULL) + { + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + return; + } + + switch (property_id) + { + case CHILD_PROP_NAME: + g_value_set_string (value, info->name); + break; + + case CHILD_PROP_TITLE: + g_value_set_string (value, info->title); + break; + + case CHILD_PROP_SYMBOLIC_ICON_NAME: + g_value_set_string (value, info->symbolic_icon_name); + break; + + case CHILD_PROP_POSITION: + i = 0; + for (list = stack->priv->children; list != NULL; list = g_list_next (list)) + { + if (info == list->data) + break; + ++i; + } + g_value_set_int (value, i); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } +} + +static void +gd_stack_set_child_property (GtkContainer *container, + GtkWidget *child, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GdStack *stack = GD_STACK (container); + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *info; + + info = find_child_info_for_widget (stack, child); + if (info == NULL) + { + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + return; + } + + switch (property_id) + { + case CHILD_PROP_NAME: + g_free (info->name); + info->name = g_value_dup_string (value); + + gtk_container_child_notify (container, child, "name"); + + if (priv->visible_child == info) + g_object_notify (G_OBJECT (stack), "visible-child-name"); + + break; + + case CHILD_PROP_TITLE: + g_free (info->title); + info->title = g_value_dup_string (value); + gtk_container_child_notify (container, child, "title"); + break; + + case CHILD_PROP_SYMBOLIC_ICON_NAME: + g_free (info->symbolic_icon_name); + info->symbolic_icon_name = g_value_dup_string (value); + gtk_container_child_notify (container, child, "symbolic-icon-name"); + break; + + case CHILD_PROP_POSITION: + reorder_child (stack, child, g_value_get_int (value)); + break; + + default: + GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); + break; + } +} + +/* From clutter-easing.c, based on Robert Penner's + * infamous easing equations, MIT license. */ +static double +ease_out_cubic (double t) +{ + double p = t - 1; + return p * p * p + 1; +} + +static int +get_bin_window_x (GdStack *stack, GtkAllocation *allocation) +{ + GdStackPrivate *priv = stack->priv; + int x = 0; + + if (priv->transition_pos < 1.0) + { + if (priv->transition_type == GD_STACK_TRANSITION_TYPE_SLIDE_LEFT) + x = allocation->width * (1 - ease_out_cubic (priv->transition_pos)); + if (priv->transition_type == GD_STACK_TRANSITION_TYPE_SLIDE_RIGHT) + x = -allocation->width * (1 - ease_out_cubic (priv->transition_pos)); + } + + return x; +} + +static gboolean +gd_stack_set_transition_position (GdStack *stack, + gdouble pos) +{ + GdStackPrivate *priv = stack->priv; + gboolean done; + + priv->transition_pos = pos; + gtk_widget_queue_draw (GTK_WIDGET (stack)); + + if (priv->bin_window != NULL && + (priv->transition_type == GD_STACK_TRANSITION_TYPE_SLIDE_LEFT || + priv->transition_type == GD_STACK_TRANSITION_TYPE_SLIDE_RIGHT)) + { + GtkAllocation allocation; + gtk_widget_get_allocation (GTK_WIDGET (stack), &allocation); + gdk_window_move (priv->bin_window, + get_bin_window_x (stack, &allocation), 0); + } + + done = pos >= 1.0; + + if (done || priv->last_visible_surface != NULL) + { + if (priv->last_visible_child) + { + gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE); + priv->last_visible_child = NULL; + } + } + + if (done) + { + if (priv->last_visible_surface != NULL) + { + cairo_surface_destroy (priv->last_visible_surface); + priv->last_visible_surface = NULL; + } + + gtk_widget_queue_resize (GTK_WIDGET (stack)); + } + + return done; +} + +static gboolean +gd_stack_transition_cb (GdStack *stack, + GdkFrameClock *frame_clock, + gpointer user_data) +{ + GdStackPrivate *priv = stack->priv; + gint64 now; + gdouble t; + + now = gdk_frame_clock_get_frame_time (frame_clock); + + t = 1.0; + if (now < priv->end_time) + t = (now - priv->start_time) / (double) (priv->end_time - priv->start_time); + + /* Finish animation early if not mapped anymore */ + if (!gtk_widget_get_mapped (GTK_WIDGET (stack))) + t = 1.0; + + if (gd_stack_set_transition_position (stack, t)) + { + gtk_widget_set_opacity (GTK_WIDGET (stack), 1.0); + priv->tick_id = 0; + + return FALSE; + } + + return TRUE; +} + +static void +gd_stack_schedule_ticks (GdStack *stack) +{ + GdStackPrivate *priv = stack->priv; + + if (priv->tick_id == 0) + { + priv->tick_id = + gtk_widget_add_tick_callback (GTK_WIDGET (stack), (GtkTickCallback)gd_stack_transition_cb, stack, NULL); + } +} + +static void +gd_stack_unschedule_ticks (GdStack *stack) +{ + GdStackPrivate *priv = stack->priv; + + if (priv->tick_id != 0) + { + gtk_widget_remove_tick_callback (GTK_WIDGET (stack), priv->tick_id); + priv->tick_id = 0; + } +} + +static void +gd_stack_start_transition (GdStack *stack) +{ + GdStackPrivate *priv = stack->priv; + GtkWidget *widget = GTK_WIDGET (stack); + gboolean animations_enabled; + + g_object_get (gtk_widget_get_settings (widget), + "gtk-enable-animations", &animations_enabled, + NULL); + + if (gtk_widget_get_mapped (widget) && + animations_enabled && + priv->transition_type != GD_STACK_TRANSITION_TYPE_NONE && + priv->last_visible_child != NULL) + { + gtk_widget_set_opacity (widget, 0.999); + + priv->transition_pos = 0.0; + priv->start_time = gdk_frame_clock_get_frame_time (gtk_widget_get_frame_clock (widget)); + priv->end_time = priv->start_time + (priv->transition_duration * 1000); + gd_stack_schedule_ticks (stack); + } + else + { + gd_stack_unschedule_ticks (stack); + gd_stack_set_transition_position (stack, 1.0); + } +} + +static void +set_visible_child (GdStack *stack, + GdStackChildInfo *child_info) +{ + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *info; + GtkWidget *widget = GTK_WIDGET (stack); + GList *l; + + /* If none, pick first visible */ + if (child_info == NULL) + { + for (l = priv->children; l != NULL; l = l->next) + { + info = l->data; + if (gtk_widget_get_visible (info->widget)) + { + child_info = info; + break; + } + } + } + + if (child_info == priv->visible_child) + return; + + if (priv->last_visible_child) + gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE); + priv->last_visible_child = NULL; + + if (priv->last_visible_surface != NULL) + cairo_surface_destroy (priv->last_visible_surface); + priv->last_visible_surface = NULL; + + if (priv->visible_child && priv->visible_child->widget) + { + if (gtk_widget_is_visible (widget)) + priv->last_visible_child = priv->visible_child; + else + gtk_widget_set_child_visible (priv->visible_child->widget, FALSE); + } + + priv->visible_child = child_info; + + if (child_info) + gtk_widget_set_child_visible (child_info->widget, TRUE); + + gtk_widget_queue_resize (GTK_WIDGET (stack)); + gtk_widget_queue_draw (GTK_WIDGET (stack)); + + g_object_notify (G_OBJECT (stack), "visible-child"); + g_object_notify (G_OBJECT (stack), "visible-child-name"); + + gd_stack_start_transition (stack); +} + +static void +stack_child_visibility_notify_cb (GObject *obj, + GParamSpec *pspec, + gpointer user_data) +{ + GdStack *stack = GD_STACK (user_data); + GdStackPrivate *priv = stack->priv; + GtkWidget *child = GTK_WIDGET (obj); + GdStackChildInfo *child_info; + + child_info = find_child_info_for_widget (stack, child); + + if (priv->visible_child == NULL && + gtk_widget_get_visible (child)) + set_visible_child (stack, child_info); + else if (priv->visible_child == child_info && + !gtk_widget_get_visible (child)) + set_visible_child (stack, NULL); + + if (child_info == priv->last_visible_child) + { + gtk_widget_set_child_visible (priv->last_visible_child->widget, FALSE); + priv->last_visible_child = NULL; + } +} + +void +gd_stack_add_titled (GdStack *stack, + GtkWidget *child, + const char *name, + const char *title) +{ + g_return_if_fail (GD_IS_STACK (stack)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + gtk_container_add_with_properties (GTK_CONTAINER (stack), + child, + "name", name, + "title", title, + NULL); +} + +void +gd_stack_add_named (GdStack *stack, + GtkWidget *child, + const char *name) +{ + g_return_if_fail (GD_IS_STACK (stack)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + gtk_container_add_with_properties (GTK_CONTAINER (stack), + child, + "name", name, + NULL); +} + +static void +gd_stack_add (GtkContainer *container, + GtkWidget *child) +{ + GdStack *stack = GD_STACK (container); + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *child_info; + + g_return_if_fail (child != NULL); + + child_info = g_slice_new (GdStackChildInfo); + child_info->widget = child; + child_info->name = NULL; + child_info->title = NULL; + child_info->symbolic_icon_name = NULL; + + priv->children = g_list_append (priv->children, child_info); + + gtk_widget_set_parent_window (child, priv->bin_window); + gtk_widget_set_parent (child, GTK_WIDGET (stack)); + + g_signal_connect (child, "notify::visible", + G_CALLBACK (stack_child_visibility_notify_cb), stack); + + gtk_widget_child_notify (child, "position"); + + if (priv->visible_child == NULL && + gtk_widget_get_visible (child)) + set_visible_child (stack, child_info); + else + gtk_widget_set_child_visible (child, FALSE); + + if (priv->homogeneous || priv->visible_child == child_info) + gtk_widget_queue_resize (GTK_WIDGET (stack)); +} + +static void +gd_stack_remove (GtkContainer *container, + GtkWidget *child) +{ + GdStack *stack = GD_STACK (container); + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *child_info; + gboolean was_visible; + + child_info = find_child_info_for_widget (stack, child); + if (child_info == NULL) + return; + + priv->children = g_list_remove (priv->children, child_info); + + g_signal_handlers_disconnect_by_func (child, + stack_child_visibility_notify_cb, + stack); + + was_visible = gtk_widget_get_visible (child); + + child_info->widget = NULL; + + if (priv->visible_child == child_info) + set_visible_child (stack, NULL); + + if (priv->last_visible_child == child_info) + priv->last_visible_child = NULL; + + gtk_widget_unparent (child); + + g_free (child_info->name); + g_free (child_info->title); + g_free (child_info->symbolic_icon_name); + g_slice_free (GdStackChildInfo, child_info); + + if (priv->homogeneous && was_visible) + gtk_widget_queue_resize (GTK_WIDGET (stack)); +} + +void +gd_stack_set_homogeneous (GdStack *stack, + gboolean homogeneous) +{ + GdStackPrivate *priv; + + g_return_if_fail (GD_IS_STACK (stack)); + + priv = stack->priv; + + homogeneous = !!homogeneous; + + if (priv->homogeneous == homogeneous) + return; + + priv->homogeneous = homogeneous; + + if (gtk_widget_get_visible (GTK_WIDGET(stack))) + gtk_widget_queue_resize (GTK_WIDGET (stack)); + + g_object_notify (G_OBJECT (stack), "homogeneous"); +} + +gboolean +gd_stack_get_homogeneous (GdStack *stack) +{ + g_return_val_if_fail (GD_IS_STACK (stack), FALSE); + + return stack->priv->homogeneous; +} + +gint +gd_stack_get_transition_duration (GdStack *stack) +{ + g_return_val_if_fail (GD_IS_STACK (stack), 0); + + return stack->priv->transition_duration; +} + +void +gd_stack_set_transition_duration (GdStack *stack, + gint value) +{ + g_return_if_fail (GD_IS_STACK (stack)); + + stack->priv->transition_duration = value; + g_object_notify (G_OBJECT (stack), "transition-duration"); +} + +GdStackTransitionType +gd_stack_get_transition_type (GdStack *stack) +{ + g_return_val_if_fail (GD_IS_STACK (stack), GD_STACK_TRANSITION_TYPE_NONE); + + return stack->priv->transition_type; +} + +void +gd_stack_set_transition_type (GdStack *stack, + GdStackTransitionType value) +{ + g_return_if_fail (GD_IS_STACK (stack)); + + stack->priv->transition_type = value; + g_object_notify (G_OBJECT (stack), "transition-type"); +} + +/** + * gd_stack_get_visible_child: + * @stack: a #GdStack + * + * Gets the currently visible child of the #GdStack, or %NULL if the + * there are no visible children. The returned widget does not have a reference + * added, so you do not need to unref it. + * + * Return value: (transfer none): pointer to child of the #GdStack + **/ +GtkWidget * +gd_stack_get_visible_child (GdStack *stack) +{ + g_return_val_if_fail (GD_IS_STACK (stack), NULL); + + return stack->priv->visible_child ? stack->priv->visible_child->widget : NULL; +} + +const char * +gd_stack_get_visible_child_name (GdStack *stack) +{ + g_return_val_if_fail (GD_IS_STACK (stack), NULL); + + if (stack->priv->visible_child) + return stack->priv->visible_child->name; + + return NULL; +} + +void +gd_stack_set_visible_child (GdStack *stack, + GtkWidget *child) +{ + GdStackChildInfo *child_info; + + g_return_if_fail (GD_IS_STACK (stack)); + g_return_if_fail (GTK_IS_WIDGET (child)); + + child_info = find_child_info_for_widget (stack, child); + if (child_info == NULL) + return; + + if (gtk_widget_get_visible (child_info->widget)) + set_visible_child (stack, child_info); +} + +void +gd_stack_set_visible_child_name (GdStack *stack, + const char *name) +{ + GdStackPrivate *priv; + GdStackChildInfo *child_info, *info; + GList *l; + + g_return_if_fail (GD_IS_STACK (stack)); + g_return_if_fail (name != NULL); + + priv = stack->priv; + + child_info = NULL; + for (l = priv->children; l != NULL; l = l->next) + { + info = l->data; + if (info->name != NULL && + strcmp (info->name, name) == 0) + { + child_info = info; + break; + } + } + + if (child_info != NULL && gtk_widget_get_visible (child_info->widget)) + set_visible_child (stack, child_info); +} + +static void +gd_stack_forall (GtkContainer *container, + gboolean include_internals, + GtkCallback callback, + gpointer callback_data) +{ + GdStack *stack = GD_STACK (container); + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *child_info; + GList *l; + + l = priv->children; + while (l) + { + child_info = l->data; + l = l->next; + + (* callback) (child_info->widget, callback_data); + } +} + +static void +gd_stack_compute_expand (GtkWidget *widget, + gboolean *hexpand_p, + gboolean *vexpand_p) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + gboolean hexpand, vexpand; + GdStackChildInfo *child_info; + GtkWidget *child; + GList *l; + + hexpand = FALSE; + vexpand = FALSE; + for (l = priv->children; l != NULL; l = l->next) + { + child_info = l->data; + child = child_info->widget; + + if (!hexpand && + gtk_widget_compute_expand (child, GTK_ORIENTATION_HORIZONTAL)) + hexpand = TRUE; + + if (!vexpand && + gtk_widget_compute_expand (child, GTK_ORIENTATION_VERTICAL)) + vexpand = TRUE; + + if (hexpand && vexpand) + break; + } + + *hexpand_p = hexpand; + *vexpand_p = vexpand; +} + +static void +gd_stack_draw_crossfade (GtkWidget *widget, + cairo_t *cr) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + + if (priv->last_visible_surface) + { + cairo_set_source_surface (cr, priv->last_visible_surface, + priv->last_visible_surface_allocation.x, + priv->last_visible_surface_allocation.y); + cairo_set_operator (cr, CAIRO_OPERATOR_ADD); + cairo_paint_with_alpha (cr, MAX (1.0 - priv->transition_pos, 0)); + } + + cairo_push_group (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + gtk_container_propagate_draw (GTK_CONTAINER (stack), + priv->visible_child->widget, + cr); + cairo_pop_group_to_source (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_ADD); + cairo_paint_with_alpha (cr, priv->transition_pos); +} + +static void +gd_stack_draw_slide (GtkWidget *widget, + cairo_t *cr) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + GtkAllocation allocation; + int x = 0; + + gtk_widget_get_allocation (widget, &allocation); + + x = get_bin_window_x (stack, &allocation); + + if (priv->transition_type == GD_STACK_TRANSITION_TYPE_SLIDE_LEFT) + x -= allocation.width; + if (priv->transition_type == GD_STACK_TRANSITION_TYPE_SLIDE_RIGHT) + x += allocation.width; + + if (priv->last_visible_surface) + { + cairo_save (cr); + cairo_set_source_surface (cr, priv->last_visible_surface, x, 0); + cairo_paint (cr); + cairo_restore (cr); + } + + gtk_container_propagate_draw (GTK_CONTAINER (stack), + priv->visible_child->widget, + cr); +} + +static gboolean +gd_stack_draw (GtkWidget *widget, + cairo_t *cr) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + cairo_t *pattern_cr; + + if (priv->visible_child && + gtk_cairo_should_draw_window (cr, priv->bin_window)) + { + if (priv->transition_pos < 1.0) + { + if (priv->last_visible_surface == NULL && + priv->last_visible_child != NULL) + { + gtk_widget_get_allocation (priv->last_visible_child->widget, + &priv->last_visible_surface_allocation); + priv->last_visible_surface = + gdk_window_create_similar_surface (gtk_widget_get_window (widget), + CAIRO_CONTENT_COLOR_ALPHA, + priv->last_visible_surface_allocation.width, + priv->last_visible_surface_allocation.height); + pattern_cr = cairo_create (priv->last_visible_surface); + /* We don't use propagate_draw here, because we don't want to apply + the bin_window offset */ + gtk_widget_draw (priv->last_visible_child->widget, pattern_cr); + cairo_destroy (pattern_cr); + } + + switch (priv->transition_type) + { + case GD_STACK_TRANSITION_TYPE_CROSSFADE: + gd_stack_draw_crossfade (widget, cr); + break; + case GD_STACK_TRANSITION_TYPE_SLIDE_LEFT: + case GD_STACK_TRANSITION_TYPE_SLIDE_RIGHT: + gd_stack_draw_slide (widget, cr); + break; + default: + g_assert_not_reached (); + } + + } + else + gtk_container_propagate_draw (GTK_CONTAINER (stack), + priv->visible_child->widget, + cr); + } + + return TRUE; +} + +static void +gd_stack_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + GtkAllocation child_allocation; + + g_return_if_fail (allocation != NULL); + + gtk_widget_set_allocation (widget, allocation); + + child_allocation = *allocation; + child_allocation.x = 0; + child_allocation.y = 0; + + if (priv->last_visible_child) + gtk_widget_size_allocate (priv->last_visible_child->widget, &child_allocation); + + if (priv->visible_child) + gtk_widget_size_allocate (priv->visible_child->widget, &child_allocation); + + if (gtk_widget_get_realized (widget)) + { + gdk_window_move_resize (priv->view_window, + allocation->x, allocation->y, + allocation->width, allocation->height); + gdk_window_move_resize (priv->bin_window, + get_bin_window_x (stack, allocation), 0, + allocation->width, allocation->height); + } +} + +static void +gd_stack_get_preferred_height (GtkWidget *widget, + gint *minimum_height, + gint *natural_height) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *child_info; + GtkWidget *child; + gint child_min, child_nat; + GList *l; + + *minimum_height = 0; + *natural_height = 0; + + for (l = priv->children; l != NULL; l = l->next) + { + child_info = l->data; + child = child_info->widget; + + if (!priv->homogeneous && + (priv->visible_child != child_info && + priv->last_visible_child != child_info)) + continue; + if (gtk_widget_get_visible (child)) + { + gtk_widget_get_preferred_height (child, &child_min, &child_nat); + + *minimum_height = MAX (*minimum_height, child_min); + *natural_height = MAX (*natural_height, child_nat); + } + } + + if (priv->last_visible_surface != NULL) + { + *minimum_height = MAX (*minimum_height, priv->last_visible_surface_allocation.height); + *natural_height = MAX (*natural_height, priv->last_visible_surface_allocation.height); + } +} + +static void +gd_stack_get_preferred_height_for_width (GtkWidget* widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *child_info; + GtkWidget *child; + gint child_min, child_nat; + GList *l; + + *minimum_height = 0; + *natural_height = 0; + + for (l = priv->children; l != NULL; l = l->next) + { + child_info = l->data; + child = child_info->widget; + + if (!priv->homogeneous && + (priv->visible_child != child_info && + priv->last_visible_child != child_info)) + continue; + if (gtk_widget_get_visible (child)) + { + gtk_widget_get_preferred_height_for_width (child, width, &child_min, &child_nat); + + *minimum_height = MAX (*minimum_height, child_min); + *natural_height = MAX (*natural_height, child_nat); + } + } + + if (priv->last_visible_surface != NULL) + { + *minimum_height = MAX (*minimum_height, priv->last_visible_surface_allocation.height); + *natural_height = MAX (*natural_height, priv->last_visible_surface_allocation.height); + } +} + +static void +gd_stack_get_preferred_width (GtkWidget *widget, + gint *minimum_width, + gint *natural_width) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *child_info; + GtkWidget *child; + gint child_min, child_nat; + GList *l; + + *minimum_width = 0; + *natural_width = 0; + + for (l = priv->children; l != NULL; l = l->next) + { + child_info = l->data; + child = child_info->widget; + + if (!priv->homogeneous && + (priv->visible_child != child_info && + priv->last_visible_child != child_info)) + continue; + if (gtk_widget_get_visible (child)) + { + gtk_widget_get_preferred_width (child, &child_min, &child_nat); + + *minimum_width = MAX (*minimum_width, child_min); + *natural_width = MAX (*natural_width, child_nat); + } + } + + if (priv->last_visible_surface != NULL) + { + *minimum_width = MAX (*minimum_width, priv->last_visible_surface_allocation.width); + *natural_width = MAX (*natural_width, priv->last_visible_surface_allocation.width); + } +} + +static void +gd_stack_get_preferred_width_for_height (GtkWidget* widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + GdStack *stack = GD_STACK (widget); + GdStackPrivate *priv = stack->priv; + GdStackChildInfo *child_info; + GtkWidget *child; + gint child_min, child_nat; + GList *l; + + *minimum_width = 0; + *natural_width = 0; + + for (l = priv->children; l != NULL; l = l->next) + { + child_info = l->data; + child = child_info->widget; + + if (!priv->homogeneous && + (priv->visible_child != child_info && + priv->last_visible_child != child_info)) + continue; + if (gtk_widget_get_visible (child)) + { + gtk_widget_get_preferred_width_for_height (child, height, &child_min, &child_nat); + + *minimum_width = MAX (*minimum_width, child_min); + *natural_width = MAX (*natural_width, child_nat); + } + } + + if (priv->last_visible_surface != NULL) + { + *minimum_width = MAX (*minimum_width, priv->last_visible_surface_allocation.width); + *natural_width = MAX (*natural_width, priv->last_visible_surface_allocation.width); + } +} diff --git a/libgd/libgd/gd-stack.h b/libgd/libgd/gd-stack.h new file mode 100644 index 00000000..07544556 --- /dev/null +++ b/libgd/libgd/gd-stack.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2013 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: Alexander Larsson + * + */ + +#ifndef __GD_STACK_H__ +#define __GD_STACK_H__ + +#include + +G_BEGIN_DECLS + + +#define GD_TYPE_STACK (gd_stack_get_type ()) +#define GD_STACK(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GD_TYPE_STACK, GdStack)) +#define GD_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GD_TYPE_STACK, GdStackClass)) +#define GD_IS_STACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GD_TYPE_STACK)) +#define GD_IS_STACK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GD_TYPE_STACK)) +#define GD_STACK_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GD_TYPE_STACK, GdStackClass)) + +typedef struct _GdStack GdStack; +typedef struct _GdStackClass GdStackClass; +typedef struct _GdStackPrivate GdStackPrivate; + +typedef enum { + GD_STACK_TRANSITION_TYPE_NONE, + GD_STACK_TRANSITION_TYPE_CROSSFADE, + GD_STACK_TRANSITION_TYPE_SLIDE_RIGHT, + GD_STACK_TRANSITION_TYPE_SLIDE_LEFT +} GdStackTransitionType; + +struct _GdStack { + GtkContainer parent_instance; + GdStackPrivate *priv; +}; + +struct _GdStackClass { + GtkContainerClass parent_class; +}; + +GType gd_stack_get_type (void) G_GNUC_CONST; + +GtkWidget * gd_stack_new (void); +void gd_stack_add_named (GdStack *stack, + GtkWidget *child, + const char *name); +void gd_stack_add_titled (GdStack *stack, + GtkWidget *child, + const char *name, + const char *title); +void gd_stack_set_visible_child (GdStack *stack, + GtkWidget *child); +GtkWidget * gd_stack_get_visible_child (GdStack *stack); +void gd_stack_set_visible_child_name (GdStack *stack, + const char *name); +const char * gd_stack_get_visible_child_name (GdStack *stack); +void gd_stack_set_homogeneous (GdStack *stack, + gboolean homogeneous); +gboolean gd_stack_get_homogeneous (GdStack *stack); +void gd_stack_set_transition_duration (GdStack *stack, + gint transition_duration); +gint gd_stack_get_transition_duration (GdStack *stack); +void gd_stack_set_transition_type (GdStack *stack, + GdStackTransitionType type); +GdStackTransitionType gd_stack_get_transition_type (GdStack *stack); + +G_END_DECLS + +#endif diff --git a/libgd/libgd/gd-styled-text-renderer.c b/libgd/libgd/gd-styled-text-renderer.c new file mode 100644 index 00000000..50a315e2 --- /dev/null +++ b/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 + * + */ + +#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/libgd/libgd/gd-styled-text-renderer.h b/libgd/libgd/gd-styled-text-renderer.h new file mode 100644 index 00000000..fc1995b1 --- /dev/null +++ b/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 + * + */ + +#ifndef _GD_STYLED_TEXT_RENDERER_H +#define _GD_STYLED_TEXT_RENDERER_H + +#include + +#include + +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/libgd/libgd/gd-tagged-entry-default.css b/libgd/libgd/gd-tagged-entry-default.css new file mode 100644 index 00000000..f11fb80f --- /dev/null +++ b/libgd/libgd/gd-tagged-entry-default.css @@ -0,0 +1,34 @@ +@define-color entry_tag_bg #3465a4; +@define-color entry_tag_fg #ffffff; + +.documents-entry-tag { + background-image: none; + background-color: @entry_tag_bg; + color: @entry_tag_fg; + + border-radius: 4px; + border-width: 0; + + margin: 2px; + padding: 4px; +} + +.documents-entry-tag:hover { + background-color: shade(@entry_tag_bg, 1.10); + color: @entry_tag_fg; +} + +.documents-entry-tag.button, +.documents-entry-tag.button:hover, +.documents-entry-tag.button:active, +.documents-entry-tag.button:active:hover { + background-color: transparent; + background-image: none; + box-shadow: none; + border-image: none; + border-width: 0; +} + +.documents-entry-tag.button:hover { + color: shade(@entry_tag_bg, 2.10); +} diff --git a/libgd/libgd/gd-tagged-entry.c b/libgd/libgd/gd-tagged-entry.c new file mode 100644 index 00000000..1eae2b76 --- /dev/null +++ b/libgd/libgd/gd-tagged-entry.c @@ -0,0 +1,1263 @@ +/* + * 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 + * + */ + +#include "gd-tagged-entry.h" + +#include +#include + +#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_add_default_style (void) +{ + GtkCssProvider *provider; + GResource *do_not_optimize_away_get_resource G_GNUC_UNUSED; + + provider = gtk_css_provider_new (); + do_not_optimize_away_get_resource = gd_tagged_entry_get_resource (); + gtk_css_provider_load_from_resource + (provider, "/org/gnome/libgd/tagged-entry/default.css"); + + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + g_object_unref (provider); +} + +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); + + gd_tagged_entry_add_default_style (); + + 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.", "documents-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/libgd/libgd/gd-tagged-entry.gresource.xml b/libgd/libgd/gd-tagged-entry.gresource.xml new file mode 100644 index 00000000..10b530c1 --- /dev/null +++ b/libgd/libgd/gd-tagged-entry.gresource.xml @@ -0,0 +1,6 @@ + + + + gd-tagged-entry-default.css + + diff --git a/libgd/libgd/gd-tagged-entry.h b/libgd/libgd/gd-tagged-entry.h new file mode 100644 index 00000000..ba9f6731 --- /dev/null +++ b/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 + * + */ + +#ifndef __GD_TAGGED_ENTRY_H__ +#define __GD_TAGGED_ENTRY_H__ + +#include + +#include + +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/libgd/libgd/gd-toggle-pixbuf-renderer.c b/libgd/libgd/gd-toggle-pixbuf-renderer.c new file mode 100644 index 00000000..978022b4 --- /dev/null +++ b/libgd/libgd/gd-toggle-pixbuf-renderer.c @@ -0,0 +1,269 @@ +/* + * 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 + * + */ + +#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; + + gtk_paint_spinner (gtk_widget_get_style (widget), + cr, + GTK_STATE_FLAG_ACTIVE, + widget, + NULL, + (guint) self->priv->pulse - 1, + x, y, + width, height); +} + +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/libgd/libgd/gd-toggle-pixbuf-renderer.h b/libgd/libgd/gd-toggle-pixbuf-renderer.h new file mode 100644 index 00000000..fe54cf46 --- /dev/null +++ b/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 + * + */ + +#ifndef _GD_TOGGLE_PIXBUF_RENDERER_H +#define _GD_TOGGLE_PIXBUF_RENDERER_H + +#include + +#include + +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/libgd/libgd/gd-two-lines-renderer.c b/libgd/libgd/gd-two-lines-renderer.c new file mode 100644 index 00000000..9840527e --- /dev/null +++ b/libgd/libgd/gd-two-lines-renderer.c @@ -0,0 +1,628 @@ +/* + * 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 + * + */ + +#include "gd-two-lines-renderer.h" +#include + +#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; + GdkRGBA rgba = {0.0, 0.0, 0.0, 0.0}; + PangoAttrList *layout_attr; + PangoAttribute *attr_color; + + 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, + "color", &rgba, + 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 color */ + rgba.red = CLAMP(1.0 - ((1.0 - rgba.red) * SUBTITLE_DIM_PERCENTAGE), 0.0, 1.0); + rgba.green = CLAMP(1.0 - ((1.0 - rgba.green) * SUBTITLE_DIM_PERCENTAGE), 0.0, 1.0); + rgba.blue = CLAMP(1.0 - ((1.0 - rgba.blue) * SUBTITLE_DIM_PERCENTAGE), 0.0, 1.0); + + layout_attr = pango_attr_list_new (); + attr_color = pango_attr_foreground_new (rgba.red * 65535, + rgba.green * 65535, + rgba.blue * 65535); + pango_attr_list_insert (layout_attr, attr_color); + 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; + + 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 += 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) +{ + GdTwoLinesRenderer *self = GD_TWO_LINES_RENDERER (cell); + gint x_offset, x_offset_1, x_offset_2, y_offset; + PangoLayout *layout_one, *layout_two; + + /* fetch common information */ + 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, + &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; + + g_clear_object (&layout_one); + g_clear_object (&layout_two); +} + +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/libgd/libgd/gd-two-lines-renderer.h b/libgd/libgd/gd-two-lines-renderer.h new file mode 100644 index 00000000..23bf70f0 --- /dev/null +++ b/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 + * + */ + +#ifndef _GD_TWO_LINES_RENDERER_H +#define _GD_TWO_LINES_RENDERER_H + +#include + +#include + +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/libgd/libgd/gd-types-catalog.c b/libgd/libgd/gd-types-catalog.c new file mode 100644 index 00000000..e66d6c03 --- /dev/null +++ b/libgd/libgd/gd-types-catalog.c @@ -0,0 +1,142 @@ +/* + * 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 + * + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "gd-types-catalog.h" + +#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_MAIN_TOOLBAR +# include "gd-main-toolbar.h" +#endif + +#ifdef LIBGD_HEADER_BAR +# include "gd-header-bar.h" +#endif + +#ifdef LIBGD__HEADER_BUTTON +# include "gd-header-button.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 + +#ifdef LIBGD_REVEALER +# include "gd-revealer.h" +#endif + +#ifdef LIBGD_STACK +# include "gd-stack.h" +# include "gd-stack-switcher.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__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_MAIN_TOOLBAR + g_type_ensure (GD_TYPE_MAIN_TOOLBAR); +#endif + +#ifdef LIBGD_HEADER_BAR + g_type_ensure (GD_TYPE_HEADER_BAR); +#endif + +#ifdef LIBGD__HEADER_BUTTON + g_type_ensure (GD_TYPE_HEADER_SIMPLE_BUTTON); + g_type_ensure (GD_TYPE_HEADER_MENU_BUTTON); + g_type_ensure (GD_TYPE_HEADER_TOGGLE_BUTTON); + g_type_ensure (GD_TYPE_HEADER_RADIO_BUTTON); +#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 + +#ifdef LIBGD_REVEALER + g_type_ensure (GD_TYPE_REVEALER); +#endif + +#ifdef LIBGD_STACK + g_type_ensure (GD_TYPE_STACK); + g_type_ensure (GD_TYPE_STACK_SWITCHER); +#endif +} + diff --git a/libgd/libgd/gd-types-catalog.h b/libgd/libgd/gd-types-catalog.h new file mode 100644 index 00000000..fc99416b --- /dev/null +++ b/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 + +G_BEGIN_DECLS + +void gd_ensure_types (void); + +G_END_DECLS + +#endif /* __GD_TYPES_CATALOG_H__ */ diff --git a/libgd/libgd/gd.h b/libgd/libgd/gd.h new file mode 100644 index 00000000..26526f2d --- /dev/null +++ b/libgd/libgd/gd.h @@ -0,0 +1,92 @@ +/* + * 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__ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include + +G_BEGIN_DECLS + +#include + +#ifdef LIBGD_GTK_HACKS +# include +# include +#endif + +#ifdef LIBGD__VIEW_COMMON +# include +# include +# include +#endif + +#ifdef LIBGD_MAIN_ICON_VIEW +# include +# include +#endif + +#ifdef LIBGD_MAIN_LIST_VIEW +# include +#endif + +#ifdef LIBGD_MAIN_VIEW +# include +#endif + +#ifdef LIBGD_MAIN_TOOLBAR +# include +#endif + +#ifdef LIBGD_HEADER_BAR +# include +#endif + +#ifdef LIBGD__HEADER_BUTTON +# include +#endif + +#ifdef LIBGD_MARGIN_CONTAINER +# include +#endif + +#ifdef LIBGD_TAGGED_ENTRY +# include +#endif + +#ifdef LIBGD_NOTIFICATION +# include +#endif + +#ifdef LIBGD_REVEALER +# include +#endif + +#ifdef LIBGD_STACK +# include +# include +#endif + +G_END_DECLS + +#endif /* __GD_H__ */ diff --git a/libgd/test-header-bar.c b/libgd/test-header-bar.c new file mode 100644 index 00000000..986b8cc6 --- /dev/null +++ b/libgd/test-header-bar.c @@ -0,0 +1,71 @@ +#include +#include + +static void +on_switch_clicked (GtkWidget *button, + GdHeaderBar *bar) +{ + GtkWidget *image = NULL; + static gboolean use_custom = TRUE; + + if (use_custom) + { + image = gtk_image_new_from_icon_name ("face-wink-symbolic", GTK_ICON_SIZE_MENU); + use_custom = FALSE; + } + else + { + use_custom = TRUE; + } + + gd_header_bar_set_custom_title (bar, image); +} + +gint +main (gint argc, + gchar ** argv) +{ + GtkWidget *window, *bar, *box, *button; + + gtk_init (&argc, &argv); + + if (g_getenv ("RTL")) + gtk_widget_set_default_direction (GTK_TEXT_DIR_RTL); + else + gtk_widget_set_default_direction (GTK_TEXT_DIR_LTR); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request (window, 300, 300); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (window), box); + + bar = gd_header_bar_new (); + gtk_box_pack_start (GTK_BOX (box), bar, FALSE, TRUE, 0); + + gd_header_bar_set_title (GD_HEADER_BAR (bar), "Title Title Title Title Title Title"); + gd_header_bar_set_subtitle (GD_HEADER_BAR (bar), "Subtitle Subtitle Subtitle Subtitle Subtitle Subtitle"); + button = gtk_button_new_with_label ("Switch"); + gtk_widget_set_valign (button, GTK_ALIGN_CENTER); + gtk_style_context_add_class (gtk_widget_get_style_context (button), GTK_STYLE_CLASS_RAISED); + gd_header_bar_pack_start (GD_HEADER_BAR (bar), button); + g_signal_connect (button, "clicked", G_CALLBACK (on_switch_clicked), bar); + + button = gtk_button_new_with_label ("Done"); + gtk_widget_set_valign (button, GTK_ALIGN_CENTER); + gtk_style_context_add_class (gtk_widget_get_style_context (button), "suggested-action"); + + gd_header_bar_pack_end (GD_HEADER_BAR (bar), button); + + button = gtk_button_new_with_label ("Almost"); + gtk_widget_set_valign (button, GTK_ALIGN_CENTER); + gtk_style_context_add_class (gtk_widget_get_style_context (button), GTK_STYLE_CLASS_RAISED); + gd_header_bar_pack_end (GD_HEADER_BAR (bar), button); + + gtk_widget_show_all (window); + gtk_main (); + + gtk_widget_destroy (window); + + return 0; +} diff --git a/libgd/test-revealer.c b/libgd/test-revealer.c new file mode 100644 index 00000000..c95f947a --- /dev/null +++ b/libgd/test-revealer.c @@ -0,0 +1,33 @@ +#include +#include + +gint +main (gint argc, + gchar ** argv) +{ + GtkWidget *window, *revealer, *box, *widget, *entry; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request (window, 300, 300); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + widget = gtk_toggle_button_new_with_label ("Revealed"); + gtk_container_add (GTK_CONTAINER (box), widget); + gtk_container_add (GTK_CONTAINER (window), box); + + revealer = gd_revealer_new (); + entry = gtk_entry_new (); + gtk_container_add (GTK_CONTAINER (revealer), entry); + gtk_container_add (GTK_CONTAINER (box), revealer); + + g_object_bind_property (widget, "active", revealer, "reveal-child", 0); + + gtk_widget_show_all (window); + gtk_main (); + + gtk_widget_destroy (window); + + return 0; +} diff --git a/libgd/test-stack.c b/libgd/test-stack.c new file mode 100644 index 00000000..574a97c2 --- /dev/null +++ b/libgd/test-stack.c @@ -0,0 +1,177 @@ +#include +#include +#include + +GtkWidget *stack; +GtkWidget *switcher; +GtkWidget *w1; + +static void +set_visible_child (GtkWidget *button, gpointer data) +{ + gd_stack_set_visible_child (GD_STACK (stack), GTK_WIDGET (data)); +} + +static void +set_visible_child_name (GtkWidget *button, gpointer data) +{ + gd_stack_set_visible_child_name (GD_STACK (stack), (const char *)data); +} + +static void +toggle_homogeneous (GtkWidget *button, gpointer data) +{ + gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); + gd_stack_set_homogeneous (GD_STACK (stack), active); +} + +static void +toggle_icon_name (GtkWidget *button, gpointer data) +{ + gboolean active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); + gtk_container_child_set (GTK_CONTAINER (stack), w1, + "symbolic-icon-name", active ? "edit-find-symbolic" : NULL, + NULL); +} + +static void +toggle_transitions (GtkWidget *combo, gpointer data) +{ + int id = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); + gd_stack_set_transition_type (GD_STACK (stack), id); +} + +gint +main (gint argc, + gchar ** argv) +{ + GtkWidget *window, *box, *button, *hbox, *combo; + GtkWidget *w2, *w3; + GtkListStore* store; + GtkWidget *tree_view; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkWidget *scrolled_win; + int i; + GtkTreeIter iter; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_size_request (window, 300, 300); + + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add (GTK_CONTAINER (window), box); + + switcher = gd_stack_switcher_new (); + gtk_box_pack_start (GTK_BOX (box), switcher, FALSE, FALSE, 0); + + stack = gd_stack_new (); + + /* Make transitions longer so we can see that they work */ + gd_stack_set_transition_duration (GD_STACK (stack), 500); + + gtk_widget_set_halign (stack, GTK_ALIGN_START); + gtk_container_add (GTK_CONTAINER (box), stack); + + gd_stack_switcher_set_stack (GD_STACK_SWITCHER (switcher), GD_STACK (stack)); + + w1 = gtk_text_view_new (); + gtk_text_buffer_set_text (gtk_text_view_get_buffer (GTK_TEXT_VIEW (w1)), + "This is a\nTest\nBalh!", -1); + + gtk_container_add_with_properties (GTK_CONTAINER (stack), w1, + "name", "1", + "title", "1", + NULL); + + w2 = gtk_button_new_with_label ("Gazoooooooooooooooonk"); + gtk_container_add (GTK_CONTAINER (stack), w2); + gtk_container_child_set (GTK_CONTAINER (stack), w2, + "name", "2", + "title", "2", + NULL); + + + scrolled_win = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_widget_set_size_request (scrolled_win, 100, 200); + + + store = gtk_list_store_new (1, G_TYPE_STRING); + + for (i = 0; i < 40; i++) + gtk_list_store_insert_with_values (store, &iter, i, 0, "Testvalule", -1); + + tree_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); + + gtk_container_add (GTK_CONTAINER (scrolled_win), tree_view); + w3 = scrolled_win; + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes ("Target", renderer, + "text", 0, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + gd_stack_add_titled (GD_STACK (stack), w3, "3", "3"); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_container_add (GTK_CONTAINER (box), hbox); + + button = gtk_button_new_with_label ("1"); + gtk_container_add (GTK_CONTAINER (hbox), button); + g_signal_connect (button, "clicked", (GCallback) set_visible_child, w1); + + button = gtk_button_new_with_label ("2"); + gtk_container_add (GTK_CONTAINER (hbox), button); + g_signal_connect (button, "clicked", (GCallback) set_visible_child, w2); + + button = gtk_button_new_with_label ("3"); + gtk_container_add (GTK_CONTAINER (hbox), button); + g_signal_connect (button, "clicked", (GCallback) set_visible_child, w3); + + button = gtk_button_new_with_label ("1"); + gtk_container_add (GTK_CONTAINER (hbox), button); + g_signal_connect (button, "clicked", (GCallback) set_visible_child_name, (gpointer) "1"); + + button = gtk_button_new_with_label ("2"); + gtk_container_add (GTK_CONTAINER (hbox), button); + g_signal_connect (button, "clicked", (GCallback) set_visible_child_name, (gpointer) "2"); + + button = gtk_button_new_with_label ("3"); + gtk_container_add (GTK_CONTAINER (hbox), button); + g_signal_connect (button, "clicked", (GCallback) set_visible_child_name, (gpointer) "3"); + + button = gtk_check_button_new_with_label ("homogeneous"); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), + gd_stack_get_homogeneous (GD_STACK (stack))); + gtk_container_add (GTK_CONTAINER (hbox), button); + g_signal_connect (button, "clicked", (GCallback) toggle_homogeneous, NULL); + + button = gtk_toggle_button_new_with_label ("Add symbolic icon"); + g_signal_connect (button, "toggled", (GCallback) toggle_icon_name, NULL); + gtk_container_add (GTK_CONTAINER (hbox), button); + + combo = gtk_combo_box_text_new (); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), + "NONE"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), + "CROSSFADE"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), + "SLIDE_RIGHT"); + gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (combo), + "SLIDE_LEFT"); + gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0); + + gtk_container_add (GTK_CONTAINER (hbox), combo); + g_signal_connect (combo, "changed", (GCallback) toggle_transitions, NULL); + + gtk_widget_show_all (window); + gtk_main (); + + gtk_widget_destroy (window); + + return 0; +} diff --git a/libgd/test-tagged-entry.c b/libgd/test-tagged-entry.c new file mode 100644 index 00000000..0f583d9b --- /dev/null +++ b/libgd/test-tagged-entry.c @@ -0,0 +1,111 @@ +#include +#include + +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; +} diff --git a/panels/Makefile.am b/panels/Makefile.am index 625b238d..76d80dd9 100644 --- a/panels/Makefile.am +++ b/panels/Makefile.am @@ -26,6 +26,12 @@ else disabled_panels += wacom endif +if BUILD_ONLINE_ACCOUNTS +enabled_panels += online-accounts +else +disabled_panels += online-accounts +endif + SUBDIRS = common $(enabled_panels) DIST_SUBDIRS = $(SUBDIRS) $(disabled_panels) diff --git a/panels/online-accounts/Makefile.am b/panels/online-accounts/Makefile.am new file mode 100644 index 00000000..c8c5cf55 --- /dev/null +++ b/panels/online-accounts/Makefile.am @@ -0,0 +1,47 @@ +cappletname = online-accounts + +SUBDIRS = icons + +AM_CPPFLAGS = \ + $(PANEL_CFLAGS) \ + $(ONLINE_ACCOUNTS_PANEL_CFLAGS) \ + -I$(top_srcdir)/libgd \ + -DCINNAMONCC_DATA_DIR="\"$(pkgdatadir)\"" + $(NULL) + +ccpanelsdir = $(PANELS_DIR) +ccpanels_LTLIBRARIES = libonline-accounts.la + +BUILT_SOURCES = \ + cc-online-accounts-resources.c \ + cc-online-accounts-resources.h + +libonline_accounts_la_SOURCES = \ + $(BUILT_SOURCES) \ + online-accounts-module.c \ + cc-online-accounts-panel.c \ + cc-online-accounts-panel.h + +libonline_accounts_la_LIBADD = \ + $(PANEL_LIBS) \ + $(ONLINE_ACCOUNTS_PANEL_LIBS) \ + $(top_builddir)/libgd/libgd.la + +libonline_accounts_la_LDFLAGS = $(PANEL_LDFLAGS) + +resource_files = $(shell glib-compile-resources --sourcedir=$(srcdir) --generate-dependencies $(srcdir)/online-accounts.gresource.xml) +cc-online-accounts-resources.c: online-accounts.gresource.xml $(resource_files) + $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-source --c-name cc_online_accounts $< +cc-online-accounts-resources.h: online-accounts.gresource.xml $(resource_files) + $(AM_V_GEN) glib-compile-resources --target=$@ --sourcedir=$(srcdir) --generate-header --c-name cc_online_accounts $< + +@INTLTOOL_DESKTOP_RULE@ + +desktopdir = $(datadir)/applications +desktop_in_files = cinnamon-online-accounts-panel.desktop.in +desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) + +CLEANFILES = $(desktop_in_files) $(desktop_DATA) $(BUILT_SOURCES) +EXTRA_DIST = $(resource_files) online-accounts.gresource.xml + +-include $(top_srcdir)/git.mk diff --git a/panels/online-accounts/cc-online-accounts-panel.c b/panels/online-accounts/cc-online-accounts-panel.c new file mode 100644 index 00000000..7ab955ea --- /dev/null +++ b/panels/online-accounts/cc-online-accounts-panel.c @@ -0,0 +1,984 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011, 2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: David Zeuthen + */ + +#include "config.h" + +#include +#include +#include + +#define GOA_API_IS_SUBJECT_TO_CHANGE +#include +#define GOA_BACKEND_API_IS_SUBJECT_TO_CHANGE +#include + +#include "cc-online-accounts-panel.h" +#include "cc-online-accounts-resources.h" + +#include "shell/list-box-helper.h" + +struct _CcGoaPanel +{ + CcPanel parent_instance; + + GoaClient *client; + GoaObject *active_object; + GoaObject *removed_object; + + GtkWidget *accounts_frame; + GtkWidget *accounts_listbox; + GtkWidget *edit_account_dialog; + GtkWidget *edit_account_headerbar; + GtkWidget *more_providers_row; + GtkWidget *new_account_vbox; + GtkWidget *notification_label; + GtkWidget *notification_revealer; + GtkWidget *offline_label; + GtkWidget *providers_listbox; + GtkWidget *remove_account_button; + GtkWidget *stack; + GtkWidget *accounts_vbox; + + guint remove_account_timeout_id; +}; + +static gboolean on_edit_account_dialog_delete_event (CcGoaPanel *self); + +static void on_listbox_row_activated (CcGoaPanel *self, + GtkListBoxRow *activated_row); + +static void fill_accounts_listbox (CcGoaPanel *self); + +static void on_account_added (GoaClient *client, + GoaObject *object, + gpointer user_data); + +static void on_account_changed (GoaClient *client, + GoaObject *object, + gpointer user_data); + +static void on_account_removed (GoaClient *client, + GoaObject *object, + gpointer user_data); + +static void select_account_by_id (CcGoaPanel *panel, + const gchar *account_id); + +static void get_all_providers_cb (GObject *source, + GAsyncResult *res, + gpointer user_data); + +static void show_page_account (CcGoaPanel *panel, + GoaObject *object); + +static void on_remove_button_clicked (CcGoaPanel *self); + +static void on_notification_closed (GtkButton *button, + CcGoaPanel *self); + +static void on_undo_button_clicked (GtkButton *button, + CcGoaPanel *self); + +CC_PANEL_REGISTER (CcGoaPanel, cc_goa_panel); + +enum { + PROP_0, + PROP_PARAMETERS +}; + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +reset_headerbar (CcGoaPanel *self) +{ + gtk_header_bar_set_title (GTK_HEADER_BAR (self->edit_account_headerbar), NULL); + gtk_header_bar_set_subtitle (GTK_HEADER_BAR (self->edit_account_headerbar), NULL); + gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (self->edit_account_headerbar), TRUE); + + /* Remove any leftover widgets */ + gtk_container_foreach (GTK_CONTAINER (self->edit_account_headerbar), + (GtkCallback) gtk_widget_destroy, + NULL); + +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +add_provider_row (CcGoaPanel *self, + GoaProvider *provider) +{ + GIcon *icon; + GoaProviderFeatures features; + GtkWidget *image; + GtkWidget *label; + GtkWidget *row; + GtkWidget *row_grid; + gchar *markup; + gchar *name; + + row = gtk_list_box_row_new (); + + row_grid = gtk_grid_new (); + gtk_orientable_set_orientation (GTK_ORIENTABLE (row_grid), GTK_ORIENTATION_HORIZONTAL); + gtk_grid_set_column_spacing (GTK_GRID (row_grid), 6); + gtk_container_add (GTK_CONTAINER (row), row_grid); + + if (provider == NULL) + { + g_object_set_data (G_OBJECT (row), "goa-provider", NULL); + icon = g_themed_icon_new_with_default_fallbacks ("goa-account"); + name = g_strdup (C_("Online Account", "Other")); + } + else + { + g_object_set_data_full (G_OBJECT (row), "goa-provider", g_object_ref (provider), g_object_unref); + icon = goa_provider_get_provider_icon (provider, NULL); + name = goa_provider_get_provider_name (provider, NULL); + } + + image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG); + gtk_container_add (GTK_CONTAINER (row_grid), image); + g_object_set (image, "margin", 6, NULL); + + markup = g_strdup_printf ("%s", name); + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), markup); + gtk_container_add (GTK_CONTAINER (row_grid), label); + + /* Check if the row should be shown initially */ + features = goa_provider_get_provider_features (provider); + + if ((features & GOA_PROVIDER_FEATURE_BRANDED) != 0) + gtk_widget_show_all (row); + + gtk_container_add (GTK_CONTAINER (self->providers_listbox), row); + + g_free (markup); + g_free (name); + g_object_unref (icon); +} + +static gint +sort_providers_func (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer user_data) +{ + GoaProvider *a_provider, *b_provider; + CcGoaPanel *self; + gboolean a_branded, b_branded; + + self = user_data; + + if (a == GTK_LIST_BOX_ROW (self->more_providers_row)) + return 1; + else if (b == GTK_LIST_BOX_ROW (self->more_providers_row)) + return -1; + + a_provider = g_object_get_data (G_OBJECT (a), "goa-provider"); + b_provider = g_object_get_data (G_OBJECT (b), "goa-provider"); + + a_branded = (goa_provider_get_provider_features (a_provider) & GOA_PROVIDER_FEATURE_BRANDED) != 0; + b_branded = (goa_provider_get_provider_features (b_provider) & GOA_PROVIDER_FEATURE_BRANDED) != 0; + + if (a_branded != b_branded) + { + if (a_branded) + return -1; + else + return 1; + } + + return gtk_list_box_row_get_index (b) - gtk_list_box_row_get_index (a); +} + +static void +show_non_branded_providers (CcGoaPanel *self) +{ + GList *children, *l; + + children = gtk_container_get_children (GTK_CONTAINER (self->providers_listbox)); + + for (l = children; l != NULL; l = l->next) + { + GoaProvider *provider = g_object_get_data (l->data, "goa-provider"); + + if (!provider) + continue; + + if ((goa_provider_get_provider_features (provider) & GOA_PROVIDER_FEATURE_BRANDED) == 0) + gtk_widget_show_all (l->data); + } + + gtk_widget_hide (self->more_providers_row); + + g_list_free (children); +} + +static void +add_account (CcGoaPanel *self, + GoaProvider *provider, + GVariant *preseed) +{ + GoaObject *object; + GError *error; + + error = NULL; + + gtk_container_foreach (GTK_CONTAINER (self->new_account_vbox), + (GtkCallback) gtk_widget_destroy, + NULL); + + reset_headerbar (self); + + /* Move to the new account page */ + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "new-account"); + + /* Reset the dialog size */ + gtk_window_resize (GTK_WINDOW (self->edit_account_dialog), 1, 1); + + /* This spins gtk_dialog_run() */ + object = goa_provider_add_account (provider, + self->client, + GTK_DIALOG (self->edit_account_dialog), + GTK_BOX (self->new_account_vbox), + &error); + + if (preseed) + goa_provider_set_preseed_data (provider, preseed); + + if (object == NULL) + gtk_widget_hide (self->edit_account_dialog); + else + show_page_account (self, object); +} + +static void +on_provider_row_activated (CcGoaPanel *self, + GtkListBoxRow *activated_row) +{ + GoaProvider *provider; + + /* Show More row */ + if (activated_row == GTK_LIST_BOX_ROW (self->more_providers_row)) + { + show_non_branded_providers (self); + return; + } + + provider = g_object_get_data (G_OBJECT (activated_row), "goa-provider"); + + add_account (self, provider, NULL); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static gint +sort_func (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer user_data) +{ + GoaObject *a_obj, *b_obj; + GoaAccount *a_account, *b_account; + + a_obj = g_object_get_data (G_OBJECT (a), "goa-object"); + a_account = goa_object_peek_account (a_obj); + + b_obj = g_object_get_data (G_OBJECT (b), "goa-object"); + b_account = goa_object_peek_account (b_obj); + + return g_strcmp0 (goa_account_get_id (a_account), goa_account_get_id (b_account)); +} + +static void +command_add (CcGoaPanel *panel, + GVariant *parameters) +{ + GVariant *v, *preseed = NULL; + GoaProvider *provider = NULL; + const gchar *provider_name = NULL; + + g_assert (panel != NULL); + g_assert (parameters != NULL); + + switch (g_variant_n_children (parameters)) + { + case 3: + g_variant_get_child (parameters, 2, "v", &preseed); + case 2: + g_variant_get_child (parameters, 1, "v", &v); + if (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)) + provider_name = g_variant_get_string (v, NULL); + else + g_warning ("Wrong type for the second argument (provider name) GVariant, expected 's' but got '%s'", + (gchar *)g_variant_get_type (v)); + g_variant_unref (v); + break; + default: + g_warning ("Unexpected parameters found, ignore request"); + goto out; + } + + if (provider_name != NULL) + { + provider = goa_provider_get_for_provider_type (provider_name); + if (provider == NULL) + { + g_warning ("Unable to get a provider for type '%s'", provider_name); + goto out; + } + + add_account (panel, provider, preseed); + } + +out: + g_clear_object (&provider); + g_clear_pointer (&preseed, g_variant_unref); +} + +static void +cc_goa_panel_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_PARAMETERS: + { + GVariant *parameters, *v; + const gchar *first_arg = NULL; + + parameters = g_value_get_variant (value); + if (parameters == NULL) + return; + + if (g_variant_n_children (parameters) > 0) + { + g_variant_get_child (parameters, 0, "v", &v); + if (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)) + first_arg = g_variant_get_string (v, NULL); + else + g_warning ("Wrong type for the second argument GVariant, expected 's' but got '%s'", + (gchar *)g_variant_get_type (v)); + g_variant_unref (v); + } + + if (g_strcmp0 (first_arg, "add") == 0) + command_add (CC_GOA_PANEL (object), parameters); + else if (first_arg != NULL) + select_account_by_id (CC_GOA_PANEL (object), first_arg); + + return; + } + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cc_goa_panel_dispose (GObject *object) +{ + CcGoaPanel *panel = CC_GOA_PANEL (object); + + /* Must be destroyed in dispose, not finalize. */ + g_clear_pointer (&panel->edit_account_dialog, gtk_widget_destroy); + + G_OBJECT_CLASS (cc_goa_panel_parent_class)->dispose (object); +} + +static void +cc_goa_panel_finalize (GObject *object) +{ + CcGoaPanel *panel = CC_GOA_PANEL (object); + + g_clear_object (&panel->client); + + G_OBJECT_CLASS (cc_goa_panel_parent_class)->finalize (object); +} + +static void +cc_goa_panel_init (CcGoaPanel *panel) +{ + GError *error; + GNetworkMonitor *monitor; + + g_resources_register (cc_online_accounts_get_resource ()); + + gtk_widget_init_template (GTK_WIDGET (panel)); + + gtk_list_box_set_header_func (GTK_LIST_BOX (panel->accounts_listbox), + cc_list_box_update_header_func, + NULL, + NULL); + gtk_list_box_set_sort_func (GTK_LIST_BOX (panel->accounts_listbox), + sort_func, + panel, + NULL); + + gtk_list_box_set_header_func (GTK_LIST_BOX (panel->providers_listbox), + cc_list_box_update_header_func, + NULL, + NULL); + gtk_list_box_set_sort_func (GTK_LIST_BOX (panel->providers_listbox), + sort_providers_func, + panel, + NULL); + + monitor = g_network_monitor_get_default(); + + g_object_bind_property (monitor, "network-available", + panel->offline_label, "visible", + G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN); + + g_object_bind_property (monitor, "network-available", + panel->providers_listbox, "sensitive", + G_BINDING_SYNC_CREATE); + + /* TODO: probably want to avoid _sync() ... */ + error = NULL; + panel->client = goa_client_new_sync (NULL /* GCancellable */, &error); + if (panel->client == NULL) + { + g_warning ("Error getting a GoaClient: %s (%s, %d)", + error->message, g_quark_to_string (error->domain), error->code); + gtk_widget_set_sensitive (GTK_WIDGET (panel), FALSE); + g_error_free (error); + return; + } + + g_signal_connect (panel->client, + "account-added", + G_CALLBACK (on_account_added), + panel); + + g_signal_connect (panel->client, + "account-changed", + G_CALLBACK (on_account_changed), + panel); + + g_signal_connect (panel->client, + "account-removed", + G_CALLBACK (on_account_removed), + panel); + + fill_accounts_listbox (panel); + goa_provider_get_all (get_all_providers_cb, panel); + + gtk_widget_show (GTK_WIDGET (panel)); +} + +static const char * +cc_goa_panel_get_help_uri (CcPanel *panel) +{ + return "help:gnome-help/accounts"; +} + +static void +cc_goa_panel_constructed (GObject *object) +{ + CcGoaPanel *self = CC_GOA_PANEL (object); + GtkWindow *parent; + + /* Setup account editor dialog */ + parent = GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self)))); + + gtk_window_set_transient_for (GTK_WINDOW (self->edit_account_dialog), parent); + + G_OBJECT_CLASS (cc_goa_panel_parent_class)->constructed (object); +} + +static void +cc_goa_panel_class_init (CcGoaPanelClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + + panel_class->get_help_uri = cc_goa_panel_get_help_uri; + + object_class->set_property = cc_goa_panel_set_property; + object_class->finalize = cc_goa_panel_finalize; + object_class->constructed = cc_goa_panel_constructed; + object_class->dispose = cc_goa_panel_dispose; + + g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters"); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/online-accounts/online-accounts.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, accounts_frame); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, accounts_listbox); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, accounts_vbox); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, edit_account_dialog); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, edit_account_headerbar); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, more_providers_row); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, new_account_vbox); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, notification_label); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, notification_revealer); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, offline_label); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, providers_listbox); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, remove_account_button); + gtk_widget_class_bind_template_child (widget_class, CcGoaPanel, stack); + + gtk_widget_class_bind_template_callback (widget_class, on_edit_account_dialog_delete_event); + gtk_widget_class_bind_template_callback (widget_class, on_listbox_row_activated); + gtk_widget_class_bind_template_callback (widget_class, on_notification_closed); + gtk_widget_class_bind_template_callback (widget_class, on_provider_row_activated); + gtk_widget_class_bind_template_callback (widget_class, on_remove_button_clicked); + gtk_widget_class_bind_template_callback (widget_class, on_undo_button_clicked); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +show_page_nothing_selected (CcGoaPanel *panel) +{ +} + +static void +show_page_account (CcGoaPanel *panel, + GoaObject *object) +{ + GList *children; + GList *l; + GoaProvider *provider; + GoaAccount *account; + gboolean is_locked; + const gchar *provider_name; + const gchar *provider_type; + gchar *title; + + provider = NULL; + + panel->active_object = object; + reset_headerbar (panel); + + /* Move to the account editor page */ + gtk_stack_set_visible_child_name (GTK_STACK (panel->stack), "editor"); + + /* Out with the old */ + children = gtk_container_get_children (GTK_CONTAINER (panel->accounts_vbox)); + for (l = children; l != NULL; l = l->next) + gtk_container_remove (GTK_CONTAINER (panel->accounts_vbox), GTK_WIDGET (l->data)); + g_list_free (children); + + account = goa_object_peek_account (object); + + is_locked = goa_account_get_is_locked (account); + gtk_widget_set_visible (panel->remove_account_button, !is_locked); + + provider_type = goa_account_get_provider_type (account); + provider = goa_provider_get_for_provider_type (provider_type); + + if (provider != NULL) + { + goa_provider_show_account (provider, + panel->client, + object, + GTK_BOX (panel->accounts_vbox), + NULL, + NULL); + } + + provider_name = goa_account_get_provider_name (account); + /* translators: This is the title of the "Show Account" dialog. The + * %s is the name of the provider. e.g., 'Google'. */ + title = g_strdup_printf (_("%s Account"), provider_name); + gtk_header_bar_set_title (GTK_HEADER_BAR (panel->edit_account_headerbar), title); + g_free (title); + + /* Reset the dialog size */ + gtk_window_resize (GTK_WINDOW (panel->edit_account_dialog), 1, 1); + + gtk_widget_show_all (panel->accounts_vbox); + gtk_widget_show (panel->edit_account_dialog); + + g_clear_object (&provider); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +select_account_by_id (CcGoaPanel *panel, + const gchar *account_id) +{ + GList *children, *l; + + children = gtk_container_get_children (GTK_CONTAINER (panel->accounts_listbox)); + + for (l = children; l != NULL; l = l->next) + { + GoaAccount *account; + GoaObject *row_object; + + row_object = g_object_get_data (l->data, "goa-object"); + account = goa_object_peek_account (row_object); + + if (g_strcmp0 (goa_account_get_id (account), account_id) == 0) + { + show_page_account (panel, row_object); + break; + } + } + + g_list_free (children); +} + +static gboolean +on_edit_account_dialog_delete_event (CcGoaPanel *self) +{ + self->active_object = NULL; + gtk_widget_hide (self->edit_account_dialog); + return TRUE; +} + +static void +on_listbox_row_activated (CcGoaPanel *self, + GtkListBoxRow *activated_row) +{ + GoaObject *object; + + object = g_object_get_data (G_OBJECT (activated_row), "goa-object"); + show_page_account (self, object); +} + +static void +fill_accounts_listbox (CcGoaPanel *self) +{ + GList *accounts, *l; + + accounts = goa_client_get_accounts (self->client); + + if (accounts == NULL) + { + show_page_nothing_selected (self); + } + else + { + for (l = accounts; l != NULL; l = l->next) + on_account_added (self->client, l->data, self); + } + + g_list_free_full (accounts, g_object_unref); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +typedef void (*RowForAccountCallback) (CcGoaPanel *self, GtkWidget *row, GList *other_rows); + +static void +hide_row_for_account (CcGoaPanel *self, GtkWidget *row, GList *other_rows) +{ + gtk_widget_hide (row); + gtk_widget_set_visible (self->accounts_frame, other_rows != NULL); +} + +static void +remove_row_for_account (CcGoaPanel *self, GtkWidget *row, GList *other_rows) +{ + gtk_widget_destroy (row); + gtk_widget_set_visible (self->accounts_frame, other_rows != NULL); +} + +static void +show_row_for_account (CcGoaPanel *self, GtkWidget *row, GList *other_rows) +{ + gtk_widget_show (row); + gtk_widget_show (self->accounts_frame); +} + +static void +modify_row_for_account (CcGoaPanel *self, + GoaObject *object, + RowForAccountCallback callback) +{ + GList *children, *l; + + children = gtk_container_get_children (GTK_CONTAINER (self->accounts_listbox)); + + for (l = children; l != NULL; l = l->next) + { + GoaObject *row_object; + + row_object = g_object_get_data (G_OBJECT (l->data), "goa-object"); + if (row_object == object) + { + GtkWidget *row = GTK_WIDGET (l->data); + + children = g_list_remove_link (children, l); + callback (self, row, children); + g_list_free (l); + break; + } + } + + g_list_free (children); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +on_account_added (GoaClient *client, + GoaObject *object, + gpointer user_data) +{ + CcGoaPanel *self = user_data; + GtkWidget *row, *icon, *label, *box; + GoaAccount *account; + GError *error; + GIcon *gicon; + gchar* title = NULL; + + account = goa_object_peek_account (object); + + /* The main grid */ + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_show (box); + + /* The provider icon */ + icon = gtk_image_new (); + + error = NULL; + gicon = g_icon_new_for_string (goa_account_get_provider_icon (account), &error); + if (error != NULL) + { + g_warning ("Error creating GIcon for account: %s (%s, %d)", + error->message, + g_quark_to_string (error->domain), + error->code); + + g_clear_error (&error); + } + else + { + gtk_image_set_from_gicon (GTK_IMAGE (icon), gicon, GTK_ICON_SIZE_DIALOG); + } + + g_object_set (icon, "margin", 6, NULL); + + gtk_container_add (GTK_CONTAINER (box), icon); + + /* The name of the provider */ + title = g_strdup_printf ("%s\n%s", + goa_account_get_provider_name (account), + goa_account_get_presentation_identity (account)); + + label = g_object_new (GTK_TYPE_LABEL, + "ellipsize", PANGO_ELLIPSIZE_END, + "label", title, + "xalign", 0.0, + "use-markup", TRUE, + "hexpand", TRUE, + NULL); + gtk_container_add (GTK_CONTAINER (box), label); + + /* "Needs attention" icon */ + icon = gtk_image_new_from_icon_name ("dialog-warning-symbolic", GTK_ICON_SIZE_BUTTON); + gtk_widget_set_no_show_all (icon, TRUE); + g_object_set (icon, "margin_end", 30, NULL); + g_object_bind_property (goa_object_peek_account (object), + "attention-needed", + icon, + "visible", + G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE); + gtk_container_add (GTK_CONTAINER (box), icon); + + /* The row */ + row = gtk_list_box_row_new (); + g_object_set_data (G_OBJECT (row), "goa-object", object); + gtk_container_add (GTK_CONTAINER (row), box); + + /* Add to the listbox */ + gtk_container_add (GTK_CONTAINER (self->accounts_listbox), row); + gtk_widget_show_all (row); + gtk_widget_show (self->accounts_frame); + + g_clear_pointer (&title, g_free); + g_clear_object (&gicon); +} + +static void +on_account_changed (GoaClient *client, + GoaObject *object, + gpointer user_data) +{ + CcGoaPanel *panel = CC_GOA_PANEL (user_data); + + if (panel->active_object != object) + return; + + show_page_account (panel, panel->active_object); +} + +static void +on_account_removed (GoaClient *client, + GoaObject *object, + gpointer user_data) +{ + CcGoaPanel *self = user_data; + modify_row_for_account (self, object, remove_row_for_account); +} + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +get_all_providers_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + CcGoaPanel *self = user_data; + GList *providers; + GList *l; + + providers = NULL; + if (!goa_provider_get_all_finish (&providers, res, NULL)) + return; + + for (l = providers; l != NULL; l = l->next) + { + GoaProvider *provider; + provider = GOA_PROVIDER (l->data); + + add_provider_row (self, provider); + } + + g_list_free_full (providers, g_object_unref); +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +static void +cancel_notification_timeout (CcGoaPanel *self) +{ + if (self->remove_account_timeout_id == 0) + return; + + g_source_remove (self->remove_account_timeout_id); + + self->remove_account_timeout_id = 0; +} + +static void +remove_account_cb (GoaAccount *account, + GAsyncResult *res, + gpointer user_data) +{ + CcGoaPanel *panel = CC_GOA_PANEL (user_data); + GError *error; + + error = NULL; + if (!goa_account_call_remove_finish (account, res, &error)) + { + GtkWidget *dialog; + dialog = gtk_message_dialog_new (GTK_WINDOW (cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (panel)))), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + _("Error removing account")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + "%s", + error->message); + gtk_widget_show_all (dialog); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + g_error_free (error); + } + g_object_unref (panel); +} + +static void +on_notification_closed (GtkButton *button, + CcGoaPanel *self) +{ + goa_account_call_remove (goa_object_peek_account (self->removed_object), + NULL, /* GCancellable */ + (GAsyncReadyCallback) remove_account_cb, + g_object_ref (self)); + + gtk_revealer_set_reveal_child (GTK_REVEALER (self->notification_revealer), FALSE); + + cancel_notification_timeout (self); + self->removed_object = NULL; +} + +static void +on_undo_button_clicked (GtkButton *button, + CcGoaPanel *self) +{ + /* Simply show the account row and hide the notification */ + modify_row_for_account (self, self->removed_object, show_row_for_account); + gtk_revealer_set_reveal_child (GTK_REVEALER (self->notification_revealer), FALSE); + + cancel_notification_timeout (self); + self->removed_object = NULL; +} + +static gboolean +on_remove_account_timeout (gpointer user_data) +{ + on_notification_closed (NULL, user_data); + return G_SOURCE_REMOVE; +} + +static void +on_remove_button_clicked (CcGoaPanel *panel) +{ + GoaAccount *account; + gchar *label; + + if (panel->active_object == NULL) + return; + + if (panel->removed_object != NULL) + on_notification_closed (NULL, panel); + + panel->removed_object = panel->active_object; + panel->active_object = NULL; + + account = goa_object_peek_account (panel->removed_object); + /* Translators: The %s is the username (eg., debarshi.ray@gmail.com + * or rishi). + */ + label = g_strdup_printf (_("%s removed"), goa_account_get_presentation_identity (account)); + gtk_label_set_markup (GTK_LABEL (panel->notification_label), label); + gtk_revealer_set_reveal_child (GTK_REVEALER (panel->notification_revealer), TRUE); + + modify_row_for_account (panel, panel->removed_object, hide_row_for_account); + gtk_widget_hide (panel->edit_account_dialog); + + panel->remove_account_timeout_id = g_timeout_add_seconds (10, on_remove_account_timeout, panel); + + g_free (label); +} + + +/* ---------------------------------------------------------------------------------------------------- */ + +void +cc_goa_panel_register (GIOModule *module) +{ + bindtextdomain (GETTEXT_PACKAGE, "/usr/share/locale"); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + cc_goa_panel_register_type (G_TYPE_MODULE (module)); + g_io_extension_point_implement (CC_SHELL_PANEL_EXTENSION_POINT, + CC_TYPE_GOA_PANEL, + "online-accounts", 0); +} diff --git a/panels/online-accounts/cc-online-accounts-panel.h b/panels/online-accounts/cc-online-accounts-panel.h new file mode 100644 index 00000000..aaded68f --- /dev/null +++ b/panels/online-accounts/cc-online-accounts-panel.h @@ -0,0 +1,65 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * Copyright (C) 2011 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, see . + * + * Author: David Zeuthen + */ + +#ifndef __GOA_PANEL_H__ +#define __GOA_PANEL_H__ + +#include + +G_BEGIN_DECLS + +#define CC_TYPE_GOA_PANEL (cc_goa_panel_get_type ()) + +#define CC_GOA_PANEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + CC_TYPE_GOA_PANEL, CcGoaPanel)) + +#define CC_GOA_PANEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + CC_TYPE_GOA_PANEL, CcGoaPanelClass)) + +#define CC_IS_GOA_PANEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + CC_TYPE_GOA_PANEL)) + +#define CC_IS_GOA_PANEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + CC_TYPE_GOA_PANEL)) + +#define CC_GOA_PANEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + CC_TYPE_GOA_PANEL, CcGoaPanelClass)) + +typedef struct _CcGoaPanel CcGoaPanel; +typedef struct _CcGoaPanelClass CcGoaPanelClass; +typedef struct _CcGoaPanelPrivate CcGoaPanelPrivate; + +struct _CcGoaPanelClass +{ + CcPanelClass parent_class; +}; + +GType cc_goa_panel_get_type (void) G_GNUC_CONST; + +void cc_goa_panel_register (GIOModule *module); + +G_END_DECLS + +#endif /* __GOA_PANEL_H__ */ diff --git a/panels/online-accounts/cinnamon-online-accounts-panel.desktop.in.in b/panels/online-accounts/cinnamon-online-accounts-panel.desktop.in.in new file mode 100644 index 00000000..ae82aaee --- /dev/null +++ b/panels/online-accounts/cinnamon-online-accounts-panel.desktop.in.in @@ -0,0 +1,14 @@ +[Desktop Entry] +# Translators: Add soft hyphens to your translations so that the icon view won't clip your translations. See https://bugzilla.gnome.org/show_bug.cgi?id=647087#c13 for details +_Name=Online Accounts +_Comment=Connect to your online accounts and decide what to use them for +Exec=cinnamon-settings online-accounts +Icon=cs-online-accounts +Terminal=false +Type=Application +StartupNotify=true +Categories=GTK;Settings;DesktopSettings; +OnlyShowIn=X-Cinnamon; +# Translators: those are keywords for the online-accounts control-center panel +# For ReadItLater and Pocket, see http://en.wikipedia.org/wiki/Pocket_(application) +_Keywords=Google;Facebook;Twitter;Yahoo;Web;Online;Chat;Calendar;Mail;Contact;ownCloud;Kerberos;IMAP;SMTP;Pocket;ReadItLater; diff --git a/panels/online-accounts/icons/16x16/Makefile.am b/panels/online-accounts/icons/16x16/Makefile.am new file mode 100644 index 00000000..a9054c6e --- /dev/null +++ b/panels/online-accounts/icons/16x16/Makefile.am @@ -0,0 +1,14 @@ +NULL = + +icondir = $(datadir)/icons/hicolor/16x16/apps +icon_DATA = \ + cs-online-accounts.png \ + $(NULL) + +EXTRA_DIST = \ + $(icon_DATA) + +clean-local : + rm -f *~ + +-include $(top_srcdir)/git.mk diff --git a/panels/online-accounts/icons/16x16/cs-online-accounts.png b/panels/online-accounts/icons/16x16/cs-online-accounts.png new file mode 100644 index 0000000000000000000000000000000000000000..79af92c3eed3cdeb5fbd0695cc462c62349a03b5 GIT binary patch literal 917 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 X4C9XQLB5cC00000NkvXXu0mjfA8*rI literal 0 HcmV?d00001 diff --git a/panels/online-accounts/icons/256x256/Makefile.am b/panels/online-accounts/icons/256x256/Makefile.am new file mode 100644 index 00000000..0676d2b3 --- /dev/null +++ b/panels/online-accounts/icons/256x256/Makefile.am @@ -0,0 +1,14 @@ +NULL = + +icondir = $(datadir)/icons/hicolor/256x256/apps +icon_DATA = \ + cs-online-accounts.png \ + $(NULL) + +EXTRA_DIST = \ + $(icon_DATA) + +clean-local : + rm -f *~ + +-include $(top_srcdir)/git.mk diff --git a/panels/online-accounts/icons/256x256/cs-online-accounts.png b/panels/online-accounts/icons/256x256/cs-online-accounts.png new file mode 100644 index 0000000000000000000000000000000000000000..4ee2d0fa42e8850c6f660780dcc0376090b92b62 GIT binary patch literal 42185 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 literal 0 HcmV?d00001 diff --git a/panels/online-accounts/icons/32x32/Makefile.am b/panels/online-accounts/icons/32x32/Makefile.am new file mode 100644 index 00000000..950c992a --- /dev/null +++ b/panels/online-accounts/icons/32x32/Makefile.am @@ -0,0 +1,14 @@ +NULL = + +icondir = $(datadir)/icons/hicolor/32x32/apps +icon_DATA = \ + cs-online-accounts.png \ + $(NULL) + +EXTRA_DIST = \ + $(icon_DATA) + +clean-local : + rm -f *~ + +-include $(top_srcdir)/git.mk diff --git a/panels/online-accounts/icons/32x32/cs-online-accounts.png b/panels/online-accounts/icons/32x32/cs-online-accounts.png new file mode 100644 index 0000000000000000000000000000000000000000..302f2d3453fe58d4c8146a6945f9c7578021f8b8 GIT binary patch literal 2170 zcmV-=2!;2FP)q5`uuL0)-|dC51F;uHaxN5Zl=C z-Ldy-uXlE4&YV8PToOvfOT`N#jdV0R-^_Q;`Okm;5lSgO%S-!Azy7(v|4TrC_|$#> zR}lXA(cPE3hWes_RSH#8Qtm6izyGW2KB@nGzuI}e;g!2x*NqEZFDTfn3w@VRYX7ev z=-T(G1@3)h`yySbryJwpD-yL4Z&N(1`@9BEzIH-6rn~m>A720GbD8z)_ZWqUnNC!B z!mV{a-W|0V&)Ce&7E3ysEd+aA!Cf1F{`IMkavxiQwd-G86ZNb9YdafO-Ed_~prNix zk5>grREN0iyqIv18}55_XY;w7TPq(52ej~IZ6PYl3@&R6azQdiFyQeuCPHP)mPD=% z>h#^c?!`MkQDDu_w%!)6@@~3$_0mvlQ;lxu8nI}Q*|~WRpPV4z^ARf#8Ln2^aMm<} zm@Sm`kL!n!?h88!?zxM`r989_{RND#LK*D<9BX2 zKTr_~5sP@KD$|KpM5tL*N3yYjNJRxL@p4Sd{_>rVJ{5YOz2}$PFZLM16D>2&nw5ZI zQ&U-n?LcibL~BDeYj3(Z5HQ$q%L7l=0enDMw%Bs}buGd2vLJ?`p`_$k-w0(9ljg<* z;UMJZOq6TWP#y3N^}dV*Xl}nk`@y;&X#louv+++GdAp|vpV!Z(O`BM|b}g}3H5wKppHHhc&m4S3olxZ?hm5?pAPw=jb%$FPN!JjTF}~%Q{1qs1ILBw>_N5r#O`+h3~#t~ zXGzU#-T-({giGD9rGg*!v>qR=2Ub3xK3j)^V070M5 zoa3d;6J}y2Pr&B^pkz7Z3MT1^_wah#5RQvklJ4_QZEXj*=NDU?j!SC*$Y!(Tayf)J zvlVn9v8)n8KyxC-3)|n|lJ*2I?Kw%wv{o1Zg<{FOvpdH{O$tZNqpKjZOA%wuP z9p>gOvbj92>mpr=l=g=?z%h|hB5)BxfDnjL>+{Gt|_^2>AU--N4Xvq;$yVvqZxl(s>g{Y1o!^*Z`2y*)x;MURPEg z@o6~Za#t_ckMk^jFtQFcztM^#v5;rQ0_Mfiy|$Hf)wWF zaUHujuR03esq7Fif9cvtBUS-j+^Fe z>L(-nelvEK0KoXZja!G`8+>l_Uk_SnUR=GLC5bp!El={5&Sl(w(`rr(=aJdLUSJt; z9?%Z7=hEqyj~zRXBOM&+pp?t-@EAo^LAlu(M139kT!HEI92Je79PJrUhYlagPWEiyDT|o{z&pSYFmP5yB!p-J8iAyy zd6t(gUVfFoqAuYL$0EWLG?a8yy8oZO^HV)<0)4EU}bGx!|F>qul`rT_o{07*qoM6N<$g3!zowEzGB literal 0 HcmV?d00001 diff --git a/panels/online-accounts/icons/48x48/Makefile.am b/panels/online-accounts/icons/48x48/Makefile.am new file mode 100644 index 00000000..62b9bdf3 --- /dev/null +++ b/panels/online-accounts/icons/48x48/Makefile.am @@ -0,0 +1,14 @@ +NULL = + +icondir = $(datadir)/icons/hicolor/48x48/apps +icon_DATA = \ + cs-online-accounts.png \ + $(NULL) + +EXTRA_DIST = \ + $(icon_DATA) + +clean-local : + rm -f *~ + +-include $(top_srcdir)/git.mk diff --git a/panels/online-accounts/icons/48x48/cs-online-accounts.png b/panels/online-accounts/icons/48x48/cs-online-accounts.png new file mode 100644 index 0000000000000000000000000000000000000000..7756b52858b9dce9c6d0fdfa570b6d4b7fa07eee GIT binary patch literal 3747 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. + * + */ + +#include +#include "cc-online-accounts-panel.h" +#include + +void +g_io_module_load (GIOModule *module) +{ + /* register the panel */ + cc_goa_panel_register (module); +} + +void +g_io_module_unload (GIOModule *module) +{ +} diff --git a/panels/online-accounts/online-accounts.gresource.xml b/panels/online-accounts/online-accounts.gresource.xml new file mode 100644 index 00000000..b428394d --- /dev/null +++ b/panels/online-accounts/online-accounts.gresource.xml @@ -0,0 +1,6 @@ + + + + online-accounts.ui + + diff --git a/panels/online-accounts/online-accounts.ui b/panels/online-accounts/online-accounts.ui new file mode 100644 index 00000000..b9a85dc8 --- /dev/null +++ b/panels/online-accounts/online-accounts.ui @@ -0,0 +1,282 @@ + + + + + + False + dialog + 1 + False + True + + + + True + False + True + + + + + False + vertical + 0 + + + True + False + crossfade + False + + + True + False + + + new-account + + + + + True + False + 24 + 24 + vertical + + + True + False + True + vertical + 18 + 18 + 18 + + + + + True + True + end + end + Remove Account + + + + + + + editor + + + + + + + +