diff options
43 files changed, 5780 insertions, 401 deletions
diff --git a/.bzr-builddeb/default.conf b/.bzr-builddeb/default.conf new file mode 100644 index 0000000..6c96a98 --- /dev/null +++ b/.bzr-builddeb/default.conf @@ -0,0 +1,2 @@ +[BUILDDEB] +split = True diff --git a/configure.ac b/configure.ac index faf9344..ad525d7 100644 --- a/configure.ac +++ b/configure.ac @@ -1,9 +1,9 @@ # # shamelessly stolen from clutter-gtk # -m4_define([ido_major_version], [12]) +m4_define([ido_major_version], [13]) m4_define([ido_minor_version], [10]) -m4_define([ido_micro_version], [2]) +m4_define([ido_micro_version], [1]) m4_define([ido_api_version], [ido_major_version.ido_minor_version]) @@ -26,7 +26,7 @@ AC_CONFIG_HEADERS([config.h]) AC_CONFIG_SRCDIR([src/libido.h]) AC_CONFIG_MACRO_DIR([m4]) -AM_INIT_AUTOMAKE([1.11 foreign]) +AM_INIT_AUTOMAKE([check-news 1.11 foreign]) AM_SILENT_RULES([yes]) @@ -79,11 +79,11 @@ AC_FUNC_MMAP AC_CHECK_FUNCS([memset munmap strcasecmp strdup]) AC_CHECK_LIBM -GLIB_REQUIRED_VERSION=2.32.0 -GTK_REQUIRED_VERSION=3.4.0 +GIO_REQUIRED_VERSION=2.37.0 +GTK_REQUIRED_VERSION=3.8.2 PKG_CHECK_MODULES(GTK,[gtk+-3.0 >= $GTK_REQUIRED_VERSION - glib-2.0 >= $GLIB_REQUIRED_VERSION]) + gio-2.0 >= $GIO_REQUIRED_VERSION]) AC_SUBST(GTK_CFLAGS) AC_SUBST(GTK_LIBS) @@ -123,10 +123,17 @@ AC_SUBST(COVERAGE_CFLAGS) AC_SUBST(COVERAGE_CXXFLAGS) AC_SUBST(COVERAGE_LDFLAGS) +dnl = GObject Introspection =================================================== + +GOBJECT_INTROSPECTION_CHECK([0.6.7]) + +dnl = Vala API Generation ===================================================== + +AC_PATH_PROG([VALA_API_GEN], [vapigen]) + dnl = Google Test Framework =================================================== -m4_include([m4/gtest.m4]) -CHECK_GTEST +dnl xorg-gtest also provides gtest. CHECK_XORG_GTEST dnl = GTK Doc Check =========================================================== @@ -152,6 +159,7 @@ echo " ===============================" echo "" echo " Prefix : ${prefix}" echo " gcov : ${use_gcov}" +echo " introspection: ${enable_introspection}" echo "" echo " Documentation: ${enable_gtk_doc}" echo "" diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..7250455 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,394 @@ +ido (13.10.0daily13.06.19-0ubuntu1) saucy; urgency=low + + [ Ubuntu daily release ] + * debian/*symbols: auto-update new symbols to released version + + [ Charles Kerr ] + * adds the ido widgets needed for indicator-datetime. + http://bazaar.launchpad.net/~charlesk/indicator- + datetime/gmenuify/view/head:/README documents the actions & + menuitems. + * Better handling of IdoMenuItem construction from GMenuItems, better + public API documentation. + + [ Jeremy Bicha ] + * Depend on valac instead of valac-0.16 for easier transitions Have + libido3-0.1-dev depend on gir1.2-ido3-0.1 Drop unnecessary gir + build-depends. + + [ Lars Uebernickel ] + * Add support for creating scale menu items from a menu model. + + [ Ubuntu daily release ] + * Automatic snapshot from revision 132 + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 19 Jun 2013 02:02:27 +0000 + +ido (13.10.0daily13.06.07-0ubuntu1) saucy; urgency=low + + [ Lars Uebernickel ] + * New upstream release + + [ Ubuntu daily release ] + * debian/*symbols: auto-update new symbols to released version + * Automatic snapshot from revision 127 + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 07 Jun 2013 02:02:40 +0000 + +ido (12.10.3daily13.03.01-0ubuntu1) raring; urgency=low + + [ Mathieu Trudel-Lapierre ] + * Fix build against libgtest/libxorg-gtest failing due to paths to gtest + source being incorrect. (LP: #1112775) + * Jenkins build failure on libgtest_xorg "undefined reference to + symbol '_XDefaultError'" (LP: #1126353) + + [ Automatic PS uploader ] + * Automatic snapshot from revision 125 + + -- Automatic PS uploader <ps-jenkins@lists.canonical.com> Fri, 01 Mar 2013 02:02:29 +0000 + +ido (12.10.3daily13.01.11-0ubuntu1) raring; urgency=low + + [ Mathieu Trudel-Lapierre ] + * debian/control: + - Update to match style with other indicator stack packages: use trailing + commas at the end of dependency lists. + - Add gnome-common to Build-Depends. + - Reorganize Build-Depends: move libxorg-gtest-dev up to be consistent with + other indicator stack packages. + - List libgtest-dev explicitly in Build-Depends. + - Add Vcs-Bzr, Vcs-Browser fields with a notice to uploaders. + - Add xvfb to Build-Depends. + * debian/rules: + - Add and export DPKG_GENSYMBOLS_CHECK_LEVEL instead of passing -c4 to + dh_makeshlibs. + - Override dh_autoreconf to call autogen.sh and not run configure... + - Drop the override for dh_makeshlibs. + - Override dh_auto_test to run them through xvfb-run. + + [ Didier Roche ] + * Automatic snapshot from revision 118 (bootstrap) + + [ Allan LeSage ] + * debian/rules: + - Remove xvfb auto_test override which caused Jenkins failures. + * debian/control: + - Remove xvfb from Build-Depends. + + [ Robert Ancell ] + * Add GObject introspection support to IDO (LP: #582985) + + [ Automatic PS uploader ] + * Automatic snapshot from revision 122 + + -- Automatic PS uploader <ps-jenkins@lists.canonical.com> Fri, 11 Jan 2013 02:02:27 +0000 + +ido (12.10.2-0ubuntu1) quantal; urgency=low + + * New upstream release + - fix escape key behavior in calendar menuitem (LP: #953757) + + -- Didier Roche <didrocks@ubuntu.com> Thu, 20 Sep 2012 18:27:59 +0200 + +ido (12.10.1-0ubuntu1) quantal; urgency=low + + * New upstream release: + - fix inconsistent slider behavior (lp: #953757) + + -- Sebastien Bacher <seb128@ubuntu.com> Tue, 28 Aug 2012 13:49:28 +0200 + +ido (12.10.0-0ubuntu2) quantal; urgency=low + + * debian/control: + - build-depends on libxorg-gtest-dev + * debian/rules: + - run the tests + + -- Sebastien Bacher <seb128@ubuntu.com> Wed, 22 Aug 2012 11:21:58 +0200 + +ido (12.10.0-0ubuntu1) quantal; urgency=low + + * New upstream release: + - new widget GtkSwitchMenuItem for indicator-sync + * Drop gtk2 build, the new version use widgets that are not available + in the old gtk + * debian/rules: turn off "make check" run until the required components + get through mir promotion + * Updated packaging, use dh9 rather than cdbs, set up for multiarch + + -- Sebastien Bacher <seb128@ubuntu.com> Wed, 22 Aug 2012 11:06:25 +0200 + +ido (0.3.4-0ubuntu2) quantal-proposed; urgency=low + + * Use AC_CHECK_LIBM to get -lm now that gtk doesn't include it + + -- Ken VanDine <ken.vandine@canonical.com> Tue, 24 Jul 2012 10:47:16 -0400 + +ido (0.3.4-0ubuntu1) precise; urgency=low + + * New upstream release. + + -- Ken VanDine <ken.vandine@canonical.com> Wed, 21 Mar 2012 16:14:13 -0400 + +ido (0.3.3-0ubuntu2) precise; urgency=low + + * src/idoscalemenuitem.c + - fix regression that broke mousewheel operations on the + idoscale (LP: #954440) + + -- Ken VanDine <ken.vandine@canonical.com> Wed, 14 Mar 2012 16:31:04 -0400 + +ido (0.3.3-0ubuntu1) precise; urgency=low + + * New upstream release. + - add style class "menubar" to the offscreen proxy, this fixes the + white background drawn in the sound menu menu with the current + gtk3/light-themes (LP: #925700) + - fall back on gdk_screen_get_system_visual() if RGBA isn't + available (LP: #867649) + * debian/libido3-0.1-0.symbols, debian/libido-0.1-0.symbols + - refreshed + + -- Ken VanDine <ken.vandine@canonical.com> Tue, 13 Mar 2012 17:00:19 -0400 + +ido (0.3.2-0ubuntu1) precise; urgency=low + + * New upstream release. + + -- Sebastien Bacher <seb128@ubuntu.com> Mon, 12 Mar 2012 19:29:38 +0100 + +ido (0.3.1-0ubuntu1) precise; urgency=low + + * New upstream release. + * configured.ac: drop Werror to workaround gtk3 deprecations warnings. + + -- Sebastien Bacher <seb128@ubuntu.com> Tue, 13 Dec 2011 18:07:15 +0100 + +ido (0.3.0-0ubuntu4) oneiric; urgency=low + + * src/idoscalemenuitem.c + - Fixed FTBFS on armel (LP: #866039) + + -- Ken VanDine <ken.vandine@canonical.com> Wed, 05 Oct 2011 14:45:16 -0400 + +ido (0.3.0-0ubuntu3) oneiric; urgency=low + + * src/idorange.c, src/idorange.h, src/idoscalemenuitem.c + - Fixed FTBFS on armel (LP: #866039) + + -- Ken VanDine <ken.vandine@canonical.com> Wed, 05 Oct 2011 13:14:23 -0400 + +ido (0.3.0-0ubuntu2) oneiric; urgency=low + + * src/idorange.c + - cherry picked fix from Michal Hruby for a crasher in + IdoRange (LP: #865122) + + -- Ken VanDine <ken.vandine@canonical.com> Mon, 03 Oct 2011 11:08:59 -0400 + +ido (0.3.0-0ubuntu1) oneiric; urgency=low + + * New upstream release. + + -- Ken VanDine <ken.vandine@canonical.com> Wed, 28 Sep 2011 15:21:37 -0400 + +ido (0.2.93-0ubuntu1) oneiric; urgency=low + + * New upstream release. + - Cannot click on Calendar to select day, month or year (LP: #807509) + * debian/rules + - removed deprecated/unused simple-patchsys.mk + + -- Ken VanDine <ken.vandine@canonical.com> Mon, 19 Sep 2011 22:57:05 -0700 + +ido (0.2.92-0ubuntu1) oneiric; urgency=low + + * New upstream release: + - Fix the slider soudmenu bug (LP: #804009) + * debian/libido3-0.1-0.symbols: + - updated with new symbols + + -- Didier Roche <didrocks@ubuntu.com> Wed, 14 Sep 2011 08:04:39 +0200 + +ido (0.2.90-0ubuntu1) oneiric; urgency=low + + * New upstream release. + - Added GTK3 support + - Clicking a date of previous or next month does not select the right + date (LP: #768956) + * -debian/patches/10-gtk3.patch merged upstream + * debian/control + - Bump standards version to 3.9.2 + + -- Ken VanDine <ken.vandine@canonical.com> Tue, 21 Jun 2011 16:06:48 -0400 + +ido (0.2.2-0ubuntu2) oneiric; urgency=low + + * debian/patches/10-gtk3.patch: + - Allow building against either GTK+ 2 or 3 + * debian/rules, debian/control, debian/*.install, debian/*.symbols: + - Enable dual build and add new packages for the GTK+ 3 libraries + * COPYING.LGPL.2.1, autogen.sh, gtk-doc.make: + - Drop extra files that our packing was adding + + -- Michael Terry <mterry@ubuntu.com> Mon, 20 Jun 2011 09:44:30 -0400 + +ido (0.2.2-0ubuntu1) natty; urgency=low + + * New upstream release. + - Add signals for when the date is clicked and double clicked + - Fixed API regression from last release + * debian/patches/10-backport-calendar-api.patch + - Dropped, merged upstream + + -- Ken VanDine <ken.vandine@canonical.com> Wed, 16 Mar 2011 14:34:43 -0400 + +ido (0.2.1-0ubuntu2) natty; urgency=low + + * debian/patches/10-backport-calendar-api.patch: + - Backport a new ido_calendar_menu_item_get_display_options() call + for use by indicator-datetime + * debian/libido-0.1-0.symbols: + - Add new function + + -- Michael Terry <mterry@ubuntu.com> Mon, 28 Feb 2011 08:03:23 -0500 + +ido (0.2.1-0ubuntu1) natty; urgency=low + + * New upstream release. + * -debian/patches/01_fix-add-needed-linking.patch + - Upstream + * debian/libido-0.1-0.symbols + - Added symbols + * src/idocalendarmenuitem.{c,h} + - Added back missing symbol to prevent ABI breakage, submitted back + upstream + + -- Ken VanDine <ken.vandine@canonical.com> Thu, 24 Feb 2011 14:32:26 -0500 + +ido (0.1.14-0ubuntu2) natty; urgency=low + + * debian/patches/01_fix-add-needed-linking.patch: + Fix linking of the examples with "ld --add-needed" (lp: #698205) + + -- Michael Bienia <geser@ubuntu.com> Thu, 06 Jan 2011 18:39:43 +0100 + +ido (0.1.14-0ubuntu1) maverick; urgency=low + + * New upstream release. + - Updates for the entry needed for indicator-me changes (LP: #552745) + + -- Ken VanDine <ken.vandine@canonical.com> Thu, 16 Sep 2010 11:27:10 -0400 + +ido (0.1.13-0ubuntu1) maverick; urgency=low + + * New upstream release. + - Each key press selects the entry text (LP: #635370) + + -- Ken VanDine <ken.vandine@canonical.com> Tue, 14 Sep 2010 15:33:02 -0400 + +ido (0.1.11-0ubuntu2) maverick; urgency=low + + * Backport upstream commit to fix armel build + + -- Sebastien Bacher <seb128@ubuntu.com> Thu, 12 Aug 2010 17:37:56 +0200 + +ido (0.1.11-0ubuntu1) maverick; urgency=low + + * New upstream release. + * debian/control + - bump build depends for libgtk2.0-dev to >= 2.21.5-1ubuntu3 + + -- Ken VanDine <ken.vandine@canonical.com> Thu, 29 Jul 2010 18:06:59 -0400 + +ido (0.1.10-0ubuntu1) maverick; urgency=low + + * New upstream release. + - Added ido_timeline_set_progress API + - Added new calendar menu widget + * debian/libido-0.1-0.symbols + - Added new symbols + + -- Ken VanDine <ken.vandine@canonical.com> Thu, 22 Jul 2010 18:08:13 +0200 + +ido (0.1.9-0ubuntu1) maverick; urgency=low + + * New upstream release. + * debian/libido-0.1-0.symbols + - new version update + + -- Ken VanDine <ken.vandine@canonical.com> Thu, 15 Jul 2010 11:03:44 -0400 + +ido (0.1.8-0ubuntu1) maverick; urgency=low + + * New upstream versions: + - Add IdoMessageDialog + * debian/libido-0.1-0.symbols: + - new version update + + -- Sebastien Bacher <seb128@ubuntu.com> Thu, 08 Jul 2010 22:59:45 +0200 + +ido (0.1.6-0ubuntu1) lucid-proposed; urgency=low + + * New upstream version fixing indicator-sound theming issues (lp: #532234) + + -- Sebastien Bacher <seb128@ubuntu.com> Tue, 08 Jun 2010 19:39:32 +0200 + +ido (0.1.5-0ubuntu1) lucid; urgency=low + + * New upstream version: + - Navigation with keyboard stuck in the text field (indicator-me) + (lp: #537608) + - Pressing the Return key doesn't activate the menuitem + - After right clicking in text field, closing Me menu is difficult + * debian/control: + - update gtk requirement + + -- Sebastien Bacher <seb128@ubuntu.com> Wed, 24 Mar 2010 12:32:12 +0100 + +ido (0.1.4-0ubuntu1) lucid; urgency=low + + * New upstream version: + - Don't forward the event to the entry if the user hit the Esc key. + - Fix for grab/release signals. If you grab the slider and then release it + while the mouse is outside the menu, it was not emitting + the released signal. + - Fix keyboard navigation issues in the entry widget, fixes bug #533284. + + -- Sebastien Bacher <seb128@ubuntu.com> Thu, 11 Mar 2010 18:29:11 +0100 + +ido (0.1.3-0ubuntu1) lucid; urgency=low + + * New upstream version + + -- Ken VanDine <ken.vandine@canonical.com> Thu, 04 Mar 2010 17:03:49 -0500 + +ido (0.1.2-0ubuntu1) lucid; urgency=low + + * New upstream version + + -- Sebastien Bacher <seb128@ubuntu.com> Thu, 18 Feb 2010 13:36:09 +0100 + +ido (0.1.1-0ubuntu1) lucid; urgency=low + + * New upstream release. + * debian/patches/arm_ftbfs.patch + - removed, merged upstream + * debian/libido-0.1-0.symbols + - added new symbols + + -- Ken VanDine <ken.vandine@canonical.com> Thu, 11 Feb 2010 17:14:58 -0500 + +ido (0.1.0-0ubuntu2) lucid; urgency=low + + * debian/patches/arm_ftbfs.patch + - Fix FTBFS on arm + + -- Ken VanDine <ken.vandine@canonical.com> Fri, 05 Feb 2010 14:35:55 -0800 + +ido (0.1.0-0ubuntu1) lucid; urgency=low + + * Initial release + + -- Ken VanDine <ken.vandine@canonical.com> Thu, 04 Feb 2010 18:22:44 -0800 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..a1ee62e --- /dev/null +++ b/debian/control @@ -0,0 +1,63 @@ +Source: ido +Section: libs +Priority: optional +Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> +Build-Depends: debhelper (>= 9), + dh-autoreconf, + gnome-common, + libxorg-gtest-dev, + libxi-dev, + libx11-dev, + libgtest-dev, + libglib2.0-dev (>=2.14.0), + libgtk-3-dev (>= 3.8.2-0ubuntu2), + gtk-doc-tools, + gobject-introspection, + libgirepository1.0-dev, + valac (>= 0.16) +Standards-Version: 3.9.3 +Homepage: https://launchpad.net/ido +# If you aren't a member of ~indicator-applet-developers but need to upload +# packaging changes, just go ahead. ~indicator-applet-developers will notice +# and sync up the code again. +Vcs-Bzr: https://code.launchpad.net/~indicator-applet-developers/ido/trunk.13.04 +Vcs-Browser: https://bazaar.launchpad.net/~indicator-applet-developers/ido/trunk.13.04/files + +Package: libido3-0.1-0 +Section: libs +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, +Pre-Depends: ${misc:Pre-Depends}, +Multi-Arch: same +Description: Shared library providing extra gtk menu items for display in + system indicators + . + This package contains shared libraries to be used by GTK+ 3 applications. + +Package: libido3-0.1-dev +Section: libdevel +Architecture: any +Depends: ${shlibs:Depends}, + ${misc:Depends}, + gir1.2-ido3-0.1 (= ${binary:Version}), + libido3-0.1-0 (= ${binary:Version}), + pkg-config, + libglib2.0-dev (>=2.14.0), + libgtk-3-dev (>= 3.8.2-0ubuntu2), +Description: Shared library providing extra gtk menu items for display in + system indicators + . + This package contains files that are needed to build GTK+ 3 applications. + +Package: gir1.2-ido3-0.1 +Section: introspection +Architecture: any +Depends: ${misc:Depends}, + libido3-0.1-0 (>= ${binary:Version}), + ${gir:Depends} +Description: Typelib file for libido3-0.1 + Shared library providing extra gtk menu items for display in system indicators. + . + This package can be used by other packages using the GIRepository format to + generate dynamic bindings for libido3-0.1. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..6a33b7b --- /dev/null +++ b/debian/copyright @@ -0,0 +1,38 @@ +This package was debianized by Ken VanDine <ken.vandine@canonical.com> on +Tue, 02 Feb 2010 12:55:15 -0800. + +It was downloaded from <http://launchpad.net/ido/> + +Upstream Author: + + Cody Russell <cody.russell@canonical.com> + +Copyright: + + Copyright (C) 2010 Canonical Ltd. + +License: + + This program is free software: you can redistribute it and/or modify it + under the terms of either or both of the following licenses: + + 1) the GNU Lesser General Public License version 3, as published by the + Free Software Foundation; and/or + 2) the GNU Lesser General Public License version 2.1, as published by + the Free Software Foundation. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranties of + MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR + PURPOSE. See the applicable version of the GNU Lesser General Public + License for more details. + + You should have received a copy of both the GNU Lesser General Public + License version 3 and version 2.1 along with this program. If not, see + <http://www.gnu.org/licenses/> + +On Debian systems, the complete text of the GNU Lesser General Public License +can be found in `/usr/share/common-licenses/LGPL-3' and `/usr/share/common-licenses/LGPL-2.1' + +The Debian packaging is (C) 2010, Canonical Ltd. and +is licensed under the GPLv3, see `/usr/share/common-licenses/GPL-3'. diff --git a/debian/gir1.2-ido3-0.1.install b/debian/gir1.2-ido3-0.1.install new file mode 100644 index 0000000..cbe4832 --- /dev/null +++ b/debian/gir1.2-ido3-0.1.install @@ -0,0 +1 @@ +usr/lib/*/girepository-1.0 /usr/lib diff --git a/debian/libido3-0.1-0.install b/debian/libido3-0.1-0.install new file mode 100644 index 0000000..6b5dd83 --- /dev/null +++ b/debian/libido3-0.1-0.install @@ -0,0 +1 @@ +usr/lib/*/libido3-*.so.* diff --git a/debian/libido3-0.1-0.symbols b/debian/libido3-0.1-0.symbols new file mode 100644 index 0000000..d253c59 --- /dev/null +++ b/debian/libido3-0.1-0.symbols @@ -0,0 +1,94 @@ +libido3-0.1.so.0 libido3-0.1-0 #MINVER# + ido_action_helper_activate@Base 13.10.0daily13.06.19 + ido_action_helper_change_action_state@Base 13.10.0daily13.06.19 + ido_action_helper_get_action_target@Base 13.10.0daily13.06.19 + ido_action_helper_get_type@Base 13.10.0daily13.06.19 + ido_action_helper_get_widget@Base 13.10.0daily13.06.19 + ido_action_helper_new@Base 13.10.0daily13.06.19 + ido_appointment_menu_item_get_type@Base 13.10.0daily13.06.19 + ido_appointment_menu_item_new@Base 13.10.0daily13.06.19 + ido_appointment_menu_item_new_from_model@Base 13.10.0daily13.06.19 + ido_appointment_menu_item_set_color@Base 13.10.0daily13.06.19 + ido_appointment_menu_item_set_format@Base 13.10.0daily13.06.19 + ido_appointment_menu_item_set_summary@Base 13.10.0daily13.06.19 + ido_appointment_menu_item_set_time@Base 13.10.0daily13.06.19 + ido_calendar_menu_item_clear_marks@Base 0.2.1 + ido_calendar_menu_item_get_calendar@Base 0.1.10 + ido_calendar_menu_item_get_date@Base 0.2.1 + ido_calendar_menu_item_get_display_options@Base 0.2.1 + ido_calendar_menu_item_get_type@Base 0.1.10 + ido_calendar_menu_item_mark_day@Base 0.2.1 + ido_calendar_menu_item_new@Base 0.1.10 + ido_calendar_menu_item_new_from_model@Base 13.10.0daily13.06.19 + ido_calendar_menu_item_set_date@Base 0.2.2 + ido_calendar_menu_item_set_display_options@Base 0.2.1 + ido_calendar_menu_item_unmark_day@Base 0.2.1 + ido_entry_menu_item_get_entry@Base 0.1.0 + ido_entry_menu_item_get_type@Base 0.1.0 + ido_entry_menu_item_new@Base 0.1.0 + ido_init@Base 13.10.0daily13.06.19 + ido_location_menu_item_get_type@Base 13.10.0daily13.06.19 + ido_location_menu_item_new@Base 13.10.0daily13.06.19 + ido_location_menu_item_new_from_model@Base 13.10.0daily13.06.19 + ido_location_menu_item_set_format@Base 13.10.0daily13.06.19 + ido_location_menu_item_set_name@Base 13.10.0daily13.06.19 + ido_location_menu_item_set_timezone@Base 13.10.0daily13.06.19 + ido_menu_item_factory_get_type@Base 13.10.0daily13.06.19 + ido_message_dialog_get_type@Base 0.1.8 + ido_message_dialog_new@Base 0.1.8 + ido_message_dialog_new_with_markup@Base 0.1.8 + ido_range_get_type@Base 0.1.9 + ido_range_new@Base 0.1.9 + ido_range_style_get_type@Base 0.1.9 + ido_scale_menu_item_get_primary_image@Base 0.1.1 + ido_scale_menu_item_get_primary_label@Base 0.1.9 + ido_scale_menu_item_get_scale@Base 0.1.0 + ido_scale_menu_item_get_secondary_image@Base 0.1.1 + ido_scale_menu_item_get_secondary_label@Base 0.1.9 + ido_scale_menu_item_get_style@Base 0.1.9 + ido_scale_menu_item_get_type@Base 0.1.0 + ido_scale_menu_item_new@Base 0.1.0 + ido_scale_menu_item_new_from_model@Base 13.10.0daily13.06.19 + ido_scale_menu_item_new_with_range@Base 0.1.0 + ido_scale_menu_item_primary_clicked@Base 0.3.3 + ido_scale_menu_item_secondary_clicked@Base 0.3.3 + ido_scale_menu_item_set_primary_label@Base 0.1.9 + ido_scale_menu_item_set_secondary_label@Base 0.1.9 + ido_scale_menu_item_set_style@Base 0.1.9 + ido_scale_menu_item_style_get_type@Base 0.1.9 + ido_switch_menu_item_get_content_area@Base 12.10.0 + ido_switch_menu_item_get_type@Base 12.10.0 + ido_switch_menu_item_new@Base 12.10.0 + ido_timeline_calculate_progress@Base 0.1.8 + ido_timeline_direction_get_type@Base 0.1.8 + ido_timeline_get_direction@Base 0.1.8 + ido_timeline_get_duration@Base 0.1.8 + ido_timeline_get_fps@Base 0.1.8 + ido_timeline_get_loop@Base 0.1.8 + ido_timeline_get_progress@Base 0.1.8 + ido_timeline_get_screen@Base 0.1.8 + ido_timeline_get_type@Base 0.1.8 + ido_timeline_is_running@Base 0.1.8 + ido_timeline_new@Base 0.1.8 + ido_timeline_new_for_screen@Base 0.1.8 + ido_timeline_pause@Base 0.1.8 + ido_timeline_progress_type_get_type@Base 0.1.8 + ido_timeline_rewind@Base 0.1.8 + ido_timeline_set_direction@Base 0.1.8 + ido_timeline_set_duration@Base 0.1.8 + ido_timeline_set_fps@Base 0.1.8 + ido_timeline_set_loop@Base 0.1.8 + ido_timeline_set_progress@Base 0.1.10 + ido_timeline_set_screen@Base 0.1.8 + ido_timeline_start@Base 0.1.8 + ido_user_menu_item_get_type@Base 13.10.0daily13.06.19 + ido_user_menu_item_new@Base 13.10.0daily13.06.19 + ido_user_menu_item_new_from_model@Base 13.10.0daily13.06.19 + ido_user_menu_item_set_current_user@Base 13.10.0daily13.06.19 + ido_user_menu_item_set_icon@Base 13.10.0daily13.06.19 + ido_user_menu_item_set_label@Base 13.10.0daily13.06.19 + ido_user_menu_item_set_logged_in@Base 13.10.0daily13.06.19 + ido_media_player_menu_item_get_type@Base 0replaceme + ido_media_player_menu_item_new_from_model@Base 0replaceme + ido_playback_menu_item_get_type@Base 0replaceme + ido_playback_menu_item_new_from_model@Base 0replaceme diff --git a/debian/libido3-0.1-dev.install b/debian/libido3-0.1-dev.install new file mode 100644 index 0000000..a3208de --- /dev/null +++ b/debian/libido3-0.1-dev.install @@ -0,0 +1,5 @@ +usr/include/libido3-* +usr/lib/*/pkgconfig/libido3-* +usr/lib/*/libido3-*.so +usr/share/vala +usr/share/gir-1.0 diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..c05f4c4 --- /dev/null +++ b/debian/rules @@ -0,0 +1,13 @@ +#!/usr/bin/make -f + +export DPKG_GENSYMBOLS_CHECK_LEVEL = 4 + +%: + dh $@ --with autoreconf + +override_dh_autoreconf: + NOCONFIGURE=1 dh_autoreconf ./autogen.sh + +override_dh_install: + find debian/tmp/usr/lib -name *.la -delete + dh_install --fail-missing diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..1597c3c --- /dev/null +++ b/debian/watch @@ -0,0 +1,2 @@ +version=3 +http://launchpad.net/ido/+download .*/ido-([0-9.]+)\.tar\.gz diff --git a/example/menus.c b/example/menus.c index 5687b8e..da2103a 100644 --- a/example/menus.c +++ b/example/menus.c @@ -4,6 +4,7 @@ #include "idocalendarmenuitem.h" #include "idoentrymenuitem.h" #include "idoswitchmenuitem.h" +#include "idousermenuitem.h" #include "config.h" static void @@ -18,6 +19,28 @@ slider_released (GtkWidget *widget, gpointer user_data) g_print ("released\n"); } +static GtkWidget * +create_user_menu (const char * username, + const char * filename, + gboolean is_logged_in, + gboolean is_active) +{ + GtkWidget * ret; + GFile * file = g_file_new_for_path (filename); + GIcon * icon = g_file_icon_new (file); + + ret = g_object_new (IDO_USER_MENU_ITEM_TYPE, + "label", username, + "icon", icon, + "is-logged-in", is_logged_in, + "is-current-user", is_active, + NULL); + + g_object_unref (icon); + g_object_unref (file); + return ret; +} + int main (int argc, char *argv[]) { @@ -92,10 +115,31 @@ main (int argc, char *argv[]) image = ido_scale_menu_item_get_secondary_image (IDO_SCALE_MENU_ITEM (menuitem)); gtk_image_set_from_stock (GTK_IMAGE (image), GTK_STOCK_OPEN, GTK_ICON_SIZE_MENU); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); - g_signal_connect (menuitem, "slider-grabbed", G_CALLBACK (slider_grabbed), NULL); g_signal_connect (menuitem, "slider-released", G_CALLBACK (slider_released), NULL); + /** + *** Users + **/ + + menuitem = create_user_menu ("Guest", NULL, FALSE, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + menuitem = ido_user_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + menuitem = create_user_menu ("Bobby Fischer", "/usr/share/pixmaps/faces/chess.jpg", FALSE, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + menuitem = create_user_menu ("Linus Torvalds", "/usr/share/pixmaps/faces/penguin.jpg", TRUE, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + menuitem = create_user_menu ("Mark Shuttleworth", "/usr/share/pixmaps/faces/astronaut.jpg", TRUE, TRUE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + + menuitem = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + /* Add the menubar */ gtk_menu_shell_append (GTK_MENU_SHELL (menubar), root); diff --git a/m4/gtest.m4 b/m4/gtest.m4 deleted file mode 100644 index 2de334c..0000000 --- a/m4/gtest.m4 +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright (C) 2012 Canonical, Ltd. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice (including the next -# paragraph) shall be included in all copies or substantial portions of the -# Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Checks whether the gtest source is available on the system. Allows for -# adjusting the include and source path. Sets have_gtest=yes if the source is -# present. Sets GTEST_CPPFLAGS and GTEST_SOURCE to the preprocessor flags and -# source location respectively. -AC_DEFUN([CHECK_GTEST], -[ - AC_ARG_WITH([gtest-include-path], - [AS_HELP_STRING([--with-gtest-include-path], - [location of the Google test headers])], - [GTEST_CPPFLAGS="-I$withval"]) - - AC_ARG_WITH([gtest-source-path], - [AS_HELP_STRING([--with-gtest-source-path], - [location of the Google test sources, defaults to /usr/src/gtest])], - [GTEST_SOURCE="$withval"], - [GTEST_SOURCE="/usr/src/gtest"]) - - GTEST_CPPFLAGS="$GTEST_CPPFLAGS -I$GTEST_SOURCE" - - AC_LANG_PUSH([C++]) - - tmp_CPPFLAGS="$CPPFLAGS" - CPPFLAGS="$CPPFLAGS $GTEST_CPPFLAGS" - - AC_CHECK_HEADER([gtest/gtest.h]) - - CPPFLAGS="$tmp_CPPFLAGS" - - AC_LANG_POP - - AC_CHECK_FILES([$GTEST_SOURCE/src/gtest-all.cc] - [$GTEST_SOURCE/src/gtest_main.cc], - [have_gtest_source=yes], - [have_gtest_source=no]) - - AS_IF([test "x$ac_cv_header_gtest_gtest_h" = xyes -a \ - "x$have_gtest_source" = xyes], - [have_gtest=yes] - [AC_SUBST(GTEST_CPPFLAGS)] - [AC_SUBST(GTEST_SOURCE)], - [have_gtest=no]) -]) # CHECK_GTEST diff --git a/src/Makefile.am b/src/Makefile.am index 017874f..2791964 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,3 +1,5 @@ +CLEANFILES = + VER=3 lib_LTLIBRARIES = libido3-0.1.la @@ -15,8 +17,14 @@ sources_h = \ idorange.h \ idoscalemenuitem.h \ idoswitchmenuitem.h \ + idousermenuitem.h \ + idoappointmentmenuitem.h \ + idolocationmenuitem.h \ idotimeline.h \ - libido.h + libido.h \ + idoactionhelper.h \ + idomediaplayermenuitem.h \ + idoplaybackmenuitem.h EXTRA_DIST = \ ido.list \ @@ -57,6 +65,7 @@ AM_CFLAGS = \ $(COVERAGE_CFLAGS) libido_0_1_la_SOURCES = \ + libido.c \ idotypebuiltins.c \ idocalendarmenuitem.c \ idoentrymenuitem.c \ @@ -64,7 +73,14 @@ libido_0_1_la_SOURCES = \ idorange.c \ idoscalemenuitem.c \ idoswitchmenuitem.c \ - idotimeline.c + idotimeline.c \ + idomenuitemfactory.c \ + idoactionhelper.c \ + idousermenuitem.c \ + idomediaplayermenuitem.c \ + idoplaybackmenuitem.c \ + idoappointmentmenuitem.c \ + idolocationmenuitem.c libido3_0_1_la_SOURCES = $(libido_0_1_la_SOURCES) @@ -94,3 +110,54 @@ DISTCLEANFILES = \ idotypebuiltins.h \ idotypebuiltins.c +-include $(INTROSPECTION_MAKEFILE) +INTROSPECTION_GIRS = +INTROSPECTION_SCANNER_ARGS = \ + --symbol-prefix=ido \ + --warn-all \ + --identifier-prefix=Ido + +if HAVE_INTROSPECTION + +Ido3-0.1.gir: libido3-0.1.la +Ido3_0_1_gir_INCLUDES = Gtk-3.0 +Ido3_0_1_gir_CFLAGS = +Ido3_0_1_gir_LIBS = libido3-0.1.la +Ido3_0_1_gir_FILES = \ + idocalendarmenuitem.h \ + idoentrymenuitem.h \ + idomessagedialog.h \ + idorange.h \ + idoscalemenuitem.h \ + idoswitchmenuitem.h \ + idotimeline.h \ + $(libido_0_1_la_SOURCES) +Ido3_0_1_gir_NAMESPACE = Ido3 +Ido3_0_1_gir_VERSION = 0.1 +Ido3_0_1_gir_SCANNER_FLAGS = $(INTROSPECTION_SCANNER_ARGS) + +INTROSPECTION_GIRS += Ido3-0.1.gir + +girdir = $(datadir)/gir-1.0 +gir_DATA = $(INTROSPECTION_GIRS) + +typelibdir = $(libdir)/girepository-1.0 +typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) + +CLEANFILES += $(gir_DATA) $(typelib_DATA) + +endif + +if HAVE_INTROSPECTION + +vapidir = $(datadir)/vala/vapi +vapi_DATA = Ido3-0.1.vapi + +Ido3-0.1.vapi: Ido3-0.1.gir + $(VALA_API_GEN) --library=Ido3-0.1 \ + --pkg gtk+-3.0 \ + $< + +CLEANFILES += $(vapi_DATA) + +endif diff --git a/src/idoactionhelper.c b/src/idoactionhelper.c new file mode 100644 index 0000000..f0e300b --- /dev/null +++ b/src/idoactionhelper.c @@ -0,0 +1,428 @@ +/* +* Copyright 2013 Canonical Ltd. +* +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License version 3, as published +* by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, but +* WITHOUT ANY WARRANTY; without even the implied warranties of +* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authors: +* Lars Uebernickel <lars.uebernickel@canonical.com> +*/ + +#include "idoactionhelper.h" + +typedef GObjectClass IdoActionHelperClass; + +struct _IdoActionHelper +{ + GObject parent; + + GtkWidget *widget; + GActionGroup *actions; + gchar *action_name; + GVariant *action_target; +}; + +G_DEFINE_TYPE (IdoActionHelper, ido_action_helper, G_TYPE_OBJECT) + +enum +{ + PROP_0, + PROP_WIDGET, + PROP_ACTION_GROUP, + PROP_ACTION_NAME, + PROP_ACTION_TARGET, + NUM_PROPERTIES +}; + +enum +{ + ACTION_STATE_CHANGED, + NUM_SIGNALS +}; + +static GParamSpec *properties[NUM_PROPERTIES]; +static guint signals[NUM_SIGNALS]; + +static void +ido_action_helper_action_added (GActionGroup *actions, + const gchar *action_name, + gpointer user_data) +{ + IdoActionHelper *helper = user_data; + gboolean enabled; + GVariant *state; + + if (!g_str_equal (action_name, helper->action_name)) + return; + + if (g_action_group_query_action (actions, action_name, + &enabled, NULL, NULL, NULL, &state)) + { + gtk_widget_set_sensitive (helper->widget, enabled); + + if (state) + { + g_signal_emit (helper, signals[ACTION_STATE_CHANGED], 0, state); + g_variant_unref (state); + } + } + else + { + gtk_widget_set_sensitive (helper->widget, FALSE); + } +} + +static void +ido_action_helper_action_removed (GActionGroup *action_group, + gchar *action_name, + gpointer user_data) +{ + IdoActionHelper *helper = user_data; + + if (g_str_equal (action_name, helper->action_name)) + gtk_widget_set_sensitive (helper->widget, FALSE); +} + +static void +ido_action_helper_action_enabled_changed (GActionGroup *action_group, + gchar *action_name, + gboolean enabled, + gpointer user_data) +{ + IdoActionHelper *helper = user_data; + + if (g_str_equal (action_name, helper->action_name)) + gtk_widget_set_sensitive (helper->widget, enabled); +} + +static void +ido_action_helper_action_state_changed (GActionGroup *action_group, + gchar *action_name, + GVariant *value, + gpointer user_data) +{ + IdoActionHelper *helper = user_data; + + if (g_str_equal (action_name, helper->action_name)) + g_signal_emit (helper, signals[ACTION_STATE_CHANGED], 0, value); +} + +static gboolean +call_action_added (gpointer user_data) +{ + IdoActionHelper *helper = user_data; + + ido_action_helper_action_added (helper->actions, helper->action_name, helper); + + return G_SOURCE_REMOVE; +} + +static void +ido_action_helper_constructed (GObject *object) +{ + IdoActionHelper *helper = IDO_ACTION_HELPER (object); + + g_signal_connect (helper->actions, "action-added", + G_CALLBACK (ido_action_helper_action_added), helper); + g_signal_connect (helper->actions, "action-removed", + G_CALLBACK (ido_action_helper_action_removed), helper); + g_signal_connect (helper->actions, "action-enabled-changed", + G_CALLBACK (ido_action_helper_action_enabled_changed), helper); + g_signal_connect (helper->actions, "action-state-changed", + G_CALLBACK (ido_action_helper_action_state_changed), helper); + + if (g_action_group_has_action (helper->actions, helper->action_name)) + { + /* call action_added in an idle, so that we don't fire the + * state-changed signal during construction (nobody could have + * connected by then). + */ + g_idle_add (call_action_added, helper); + } + + G_OBJECT_CLASS (ido_action_helper_parent_class)->constructed (object); +} + +static void +ido_action_helper_get_property (GObject *object, + guint id, + GValue *value, + GParamSpec *pspec) +{ + IdoActionHelper *helper = IDO_ACTION_HELPER (object); + + switch (id) + { + case PROP_WIDGET: + g_value_set_object (value, helper->widget); + break; + + case PROP_ACTION_GROUP: + g_value_set_object (value, helper->actions); + break; + + case PROP_ACTION_NAME: + g_value_set_string (value, helper->action_name); + break; + + case PROP_ACTION_TARGET: + g_value_set_variant (value, helper->action_target); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec); + } +} + +static void +ido_action_helper_set_property (GObject *object, + guint id, + const GValue *value, + GParamSpec *pspec) +{ + IdoActionHelper *helper = IDO_ACTION_HELPER (object); + + switch (id) + { + case PROP_WIDGET: /* construct-only */ + helper->widget = g_value_dup_object (value); + break; + + case PROP_ACTION_GROUP: /* construct-only */ + helper->actions = g_value_dup_object (value); + break; + + case PROP_ACTION_NAME: /* construct-only */ + helper->action_name = g_value_dup_string (value); + break; + + case PROP_ACTION_TARGET: /* construct-only */ + helper->action_target = g_value_dup_variant (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec); + } +} + +static void +ido_action_helper_finalize (GObject *object) +{ + IdoActionHelper *helper = IDO_ACTION_HELPER (object); + + g_object_unref (helper->widget); + + g_signal_handlers_disconnect_by_data (helper->actions, helper); + g_object_unref (helper->actions); + + g_free (helper->action_name); + + if (helper->action_target) + g_variant_unref (helper->action_target); + + G_OBJECT_CLASS (ido_action_helper_parent_class)->finalize (object); +} + +static void +ido_action_helper_class_init (IdoActionHelperClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->constructed = ido_action_helper_constructed; + object_class->get_property = ido_action_helper_get_property; + object_class->set_property = ido_action_helper_set_property; + object_class->finalize = ido_action_helper_finalize; + + /** + * IdoActionHelper::action-state-changed: + * @helper: the #IdoActionHelper watching the action + * @state: the new state of the action + * + * Emitted when the widget must be updated from the action's state, + * which happens every time the action appears in the group and when + * the action changes its state. + */ + signals[ACTION_STATE_CHANGED] = g_signal_new ("action-state-changed", + IDO_TYPE_ACTION_HELPER, + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VARIANT, + G_TYPE_NONE, 1, G_TYPE_VARIANT); + + /** + * IdoActionHelper:widget: + * + * The widget that is associated with this action helper. The action + * helper updates the widget's "sensitive" property to reflect whether + * the action #IdoActionHelper:action-name exists in + * #IdoActionHelper:action-group. + */ + properties[PROP_WIDGET] = g_param_spec_object ("widget", "", "", + GTK_TYPE_WIDGET, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * IdoActionHelper:action-group: + * + * The action group that eventually contains the action that + * #IdoActionHelper:widget should be bound to. + */ + properties[PROP_ACTION_GROUP] = g_param_spec_object ("action-group", "", "", + G_TYPE_ACTION_GROUP, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * IdoActionHelper:action-name: + * + * The name of the action in #IdoActionHelper:action-group that + * should be bound to #IdoActionHelper:widget + */ + properties[PROP_ACTION_NAME] = g_param_spec_string ("action-name", "", "", NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + /** + * IdoActionHelper:action-target: + * + * The target of #IdoActionHelper:widget. ido_action_helper_activate() + * passes the target as parameter when activating the action. + * + * The handler of #IdoActionHelper:action-state-changed is responsible + * for comparing this target with the action's state and updating the + * #IdoActionHelper:widget appropriately. + */ + properties[PROP_ACTION_TARGET] = g_param_spec_variant ("action-target", "", "", + G_VARIANT_TYPE_ANY, NULL, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, NUM_PROPERTIES, properties); +} + +static void +ido_action_helper_init (IdoActionHelper *helper) +{ +} + +/** + * ido_action_helper_new: + * @widget: a #GtkWidget + * @action_group: a #GActionGroup + * @action_name: the name of an action in @action_group + * @target: the target of the action + * + * Creates a new #IdoActionHelper. This helper ties @widget to an action + * (and a target), and performs some common tasks: + * + * @widget will be set to insensitive whenever @action_group does not + * contain an action with the name @action_name, or the action with that + * name is disabled. + * + * Also, the helper emits the "action-state-changed" signal whenever the + * widget must be updated from the action's state. This includes once + * when the action was added, and every time the action changes its + * state. + * + * Returns: (transfer full): a new #IdoActionHelper + */ +IdoActionHelper * +ido_action_helper_new (GtkWidget *widget, + GActionGroup *action_group, + const gchar *action_name, + GVariant *target) +{ + g_return_val_if_fail (widget != NULL, NULL); + g_return_val_if_fail (action_group != NULL, NULL); + g_return_val_if_fail (action_name != NULL, NULL); + + return g_object_new (IDO_TYPE_ACTION_HELPER, + "widget", widget, + "action-group", action_group, + "action-name", action_name, + "action-target", target, + NULL); +} + +/** + * ido_action_helper_get_widget: + * @helper: an #IdoActionHelper + * + * Returns: (transfer none): the #GtkWidget associated with @helper + */ +GtkWidget * +ido_action_helper_get_widget (IdoActionHelper *helper) +{ + g_return_val_if_fail (IDO_IS_ACTION_HELPER (helper), NULL); + + return helper->widget; +} + +/** + * ido_action_helper_get_action_target: + * @helper: an #IdoActionHelper + * + * Returns: (transfer none): the action target that was set in + * ido_action_helper_new() as a #GVariant + */ +GVariant * +ido_action_helper_get_action_target (IdoActionHelper *helper) +{ + g_return_val_if_fail (IDO_IS_ACTION_HELPER (helper), NULL); + + return helper->action_target; +} + +/** + * ido_action_helper_activate: + * @helper: an #IdoActionHelper + * + * Activates the action that is associated with this helper. + */ +void +ido_action_helper_activate (IdoActionHelper *helper) +{ + g_return_if_fail (IDO_IS_ACTION_HELPER (helper)); + + if (helper->actions && helper->action_name) + g_action_group_activate_action (helper->actions, helper->action_name, helper->action_target); +} + +/** + * ido_action_helper_change_action_state: + * @helper: an #IdoActionHelper + * @state: the proposed new state of the action + * + * Requests changing the state of the action that is associated with + * @helper to @state. + * + * If @state is floating, it is consumed. + */ +void +ido_action_helper_change_action_state (IdoActionHelper *helper, + GVariant *state) +{ + g_return_if_fail (IDO_IS_ACTION_HELPER (helper)); + g_return_if_fail (state != NULL); + + g_variant_ref_sink (state); + + if (helper->actions && helper->action_name) + g_action_group_change_action_state (helper->actions, helper->action_name, state); + + g_variant_unref (state); +} diff --git a/src/idoactionhelper.h b/src/idoactionhelper.h new file mode 100644 index 0000000..27dafb7 --- /dev/null +++ b/src/idoactionhelper.h @@ -0,0 +1,47 @@ +/* +* Copyright 2013 Canonical Ltd. +* +* This program is free software: you can redistribute it and/or modify it +* under the terms of the GNU General Public License version 3, as published +* by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, but +* WITHOUT ANY WARRANTY; without even the implied warranties of +* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +* PURPOSE. See the GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License along +* with this program. If not, see <http://www.gnu.org/licenses/>. +* +* Authors: +* Lars Uebernickel <lars.uebernickel@canonical.com> +*/ + +#ifndef __IDO_ACTION_HELPER_H__ +#define __IDO_ACTION_HELPER_H__ + +#include <gtk/gtk.h> + +#define IDO_TYPE_ACTION_HELPER (ido_action_helper_get_type ()) +#define IDO_ACTION_HELPER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IDO_TYPE_ACTION_HELPER, IdoActionHelper)) +#define IDO_IS_ACTION_HELPER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), IDO_TYPE_ACTION_HELPER)) + +typedef struct _IdoActionHelper IdoActionHelper; + +GType ido_menu_item_get_type (void); + +IdoActionHelper * ido_action_helper_new (GtkWidget *widget, + GActionGroup *action_group, + const gchar *action_name, + GVariant *target); + +GtkWidget * ido_action_helper_get_widget (IdoActionHelper *helper); + +GVariant * ido_action_helper_get_action_target (IdoActionHelper *helper); + +void ido_action_helper_activate (IdoActionHelper *helper); + +void ido_action_helper_change_action_state (IdoActionHelper *helper, + GVariant *state); + +#endif diff --git a/src/idoappointmentmenuitem.c b/src/idoappointmentmenuitem.c new file mode 100644 index 0000000..2ac518a --- /dev/null +++ b/src/idoappointmentmenuitem.c @@ -0,0 +1,483 @@ +/** + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * Ted Gould <ted@canonical.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include <string.h> /* strstr() */ + +#include <gtk/gtk.h> + +#include "idoactionhelper.h" +#include "idoappointmentmenuitem.h" + +enum +{ + PROP_0, + PROP_COLOR, + PROP_SUMMARY, + PROP_TIME, + PROP_FORMAT, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _IdoAppointmentMenuItemPrivate +{ + char * summary; + char * format; + char * color_string; + GDateTime * date_time; + + GtkWidget * color_image; + GtkWidget * summary_label; + GtkWidget * timestamp_label; +}; + +typedef IdoAppointmentMenuItemPrivate priv_t; + +G_DEFINE_TYPE (IdoAppointmentMenuItem, + ido_appointment_menu_item, + GTK_TYPE_MENU_ITEM); + +/*** +**** GObject Virtual Functions +***/ + +static void +my_get_property (GObject * o, + guint property_id, + GValue * v, + GParamSpec * pspec) +{ + IdoAppointmentMenuItem * self = IDO_APPOINTMENT_MENU_ITEM (o); + priv_t * p = self->priv; + + switch (property_id) + { + case PROP_COLOR: + g_value_set_string (v, p->color_string); + break; + + case PROP_SUMMARY: + g_value_set_string (v, p->summary); + break; + + case PROP_FORMAT: + g_value_set_string (v, p->format); + break; + + case PROP_TIME: + g_value_set_uint64 (v, g_date_time_to_unix (p->date_time)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + break; + } +} + +static void +my_set_property (GObject * o, + guint property_id, + const GValue * v, + GParamSpec * pspec) +{ + IdoAppointmentMenuItem * self = IDO_APPOINTMENT_MENU_ITEM (o); + + switch (property_id) + { + case PROP_COLOR: + ido_appointment_menu_item_set_color (self, g_value_get_string (v)); + break; + + case PROP_SUMMARY: + ido_appointment_menu_item_set_summary (self, g_value_get_string (v)); + break; + + case PROP_FORMAT: + ido_appointment_menu_item_set_format (self, g_value_get_string (v)); + break; + + case PROP_TIME: + ido_appointment_menu_item_set_time (self, g_value_get_int64 (v)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + break; + } +} + +static void +my_dispose (GObject * object) +{ + IdoAppointmentMenuItem * self = IDO_APPOINTMENT_MENU_ITEM (object); + priv_t * p = self->priv; + + g_clear_pointer (&p->date_time, g_date_time_unref); + + G_OBJECT_CLASS (ido_appointment_menu_item_parent_class)->dispose (object); +} + +static void +my_finalize (GObject * object) +{ + IdoAppointmentMenuItem * self = IDO_APPOINTMENT_MENU_ITEM (object); + priv_t * p = self->priv; + + g_free (p->color_string); + g_free (p->summary); + g_free (p->format); + + G_OBJECT_CLASS (ido_appointment_menu_item_parent_class)->finalize (object); +} + +/*** +**** Instantiation +***/ + +static void +ido_appointment_menu_item_class_init (IdoAppointmentMenuItemClass *klass) +{ + GParamFlags prop_flags; + GObjectClass * gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (IdoAppointmentMenuItemPrivate)); + + gobject_class->get_property = my_get_property; + gobject_class->set_property = my_set_property; + gobject_class->dispose = my_dispose; + gobject_class->finalize = my_finalize; + + prop_flags = G_PARAM_CONSTRUCT + | G_PARAM_READWRITE + | G_PARAM_STATIC_STRINGS; + + properties[PROP_COLOR] = g_param_spec_string ( + "color", + "Color", + "Color coding for the appointment's type", + "White", + prop_flags); + + properties[PROP_SUMMARY] = g_param_spec_string ( + "summary", + "Summary", + "Brief description of the appointment", + "", + prop_flags); + + properties[PROP_TIME] = g_param_spec_int64 ( + "time", + "Time", + "unix time_t specifying when the appointment begins", + 0, G_MAXINT64, 0, + prop_flags); + + properties[PROP_FORMAT] = g_param_spec_string ( + "format", + "strftime format", + "strftime-style format string for the timestamp", + "%F %T", + prop_flags); + + g_object_class_install_properties (gobject_class, PROP_LAST, properties); +} + +static void +ido_appointment_menu_item_init (IdoAppointmentMenuItem *self) +{ + priv_t * p; + GtkBox * box; + GtkWidget * w; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + IDO_APPOINTMENT_MENU_ITEM_TYPE, + IdoAppointmentMenuItemPrivate); + + p = self->priv; + + p->color_image = gtk_image_new (); + p->summary_label = gtk_label_new (NULL); + p->timestamp_label = gtk_label_new (NULL); + w = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3); + + gtk_misc_set_alignment (GTK_MISC(p->timestamp_label), 1.0, 0.5); + box = GTK_BOX (w); + gtk_box_pack_start (box, p->color_image, FALSE, FALSE, 2); + gtk_box_pack_start (box, p->summary_label, FALSE, FALSE, 2); + gtk_box_pack_end (box, p->timestamp_label, FALSE, FALSE, 5); + + gtk_widget_show_all (w); + gtk_container_add (GTK_CONTAINER (self), w); +} + +/*** +**** +***/ + +/* creates a menu-sized pixbuf filled with specified color */ +static GdkPixbuf * +create_color_icon_pixbuf (const char * color_spec) +{ + static int width = -1; + static int height = -1; + GdkPixbuf * pixbuf = NULL; + + if (width == -1) + { + gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, &height); + width = CLAMP (width, 10, 30); + height = CLAMP (height, 10, 30); + } + + if (color_spec && *color_spec) + { + cairo_surface_t * surface; + cairo_t * cr; + GdkRGBA rgba; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + cr = cairo_create (surface); + + if (gdk_rgba_parse (&rgba, color_spec)) + gdk_cairo_set_source_rgba (cr, &rgba); + + cairo_paint (cr); + cairo_set_source_rgba (cr, 0, 0, 0, 0.5); + cairo_set_line_width (cr, 1); + cairo_rectangle (cr, 0.5, 0.5, width-1, height-1); + cairo_stroke (cr); + + pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, width, height); + + cairo_destroy (cr); + cairo_surface_destroy (surface); + } + + return pixbuf; +} + +static void +update_timestamp_label (IdoAppointmentMenuItem * self) +{ + char * str; + priv_t * p = self->priv; + + if (p->date_time && p->format) + str = g_date_time_format (p->date_time, p->format); + else + str = NULL; + + gtk_label_set_text (GTK_LABEL(p->timestamp_label), str); + g_free (str); +} + +/*** +**** Public API +***/ + +/* create a new IdoAppointmentMenuItem */ +GtkWidget * +ido_appointment_menu_item_new (void) +{ + return GTK_WIDGET (g_object_new (IDO_APPOINTMENT_MENU_ITEM_TYPE, NULL)); +} + +/** + * ido_appointment_menu_item_set_color: + * @color: parseable color string + * + * When this is set, the menuitem will include an icon with this color. + * + * These colors can be set in the end user's calendar app as a quick visual cue + * to show what kind of appointment this is. + */ +void +ido_appointment_menu_item_set_color (IdoAppointmentMenuItem * self, + const char * color_string) +{ + priv_t * p; + GdkPixbuf * pixbuf; + + g_return_if_fail (IDO_IS_APPOINTMENT_MENU_ITEM (self)); + p = self->priv; + + g_free (p->color_string); + p->color_string = g_strdup (color_string); + pixbuf = create_color_icon_pixbuf (p->color_string); + gtk_image_set_from_pixbuf (GTK_IMAGE(p->color_image), pixbuf); + g_object_unref (G_OBJECT(pixbuf)); +} + +/** + * ido_appointment_menu_item_set_summary: + * @summary: short string describing the appointment. + * + * Set the menuitem's primary label with a short description of the appointment + */ +void +ido_appointment_menu_item_set_summary (IdoAppointmentMenuItem * self, + const char * summary) +{ + priv_t * p; + + g_return_if_fail (IDO_IS_APPOINTMENT_MENU_ITEM (self)); + p = self->priv; + + g_free (p->summary); + p->summary = g_strdup (summary); + gtk_label_set_text (GTK_LABEL(p->summary_label), p->summary); +} + +/** + * ido_appointment_menu_item_set_time: + * @time: the time to be rendered in the appointment's timestamp label. + * + * Set the time that will be displayed in the menuitem's + * right-justified timestamp label + */ +void +ido_appointment_menu_item_set_time (IdoAppointmentMenuItem * self, + time_t time) +{ + priv_t * p; + + g_return_if_fail (IDO_IS_APPOINTMENT_MENU_ITEM (self)); + p = self->priv; + + g_clear_pointer (&p->date_time, g_date_time_unref); + p->date_time = g_date_time_new_from_unix_local (time); + update_timestamp_label (self); +} + +/** + * ido_appointment_menu_item_set_format: + * @format: the format string used when showing the appointment's time + * + * Set the format string for rendering the appointment's time + * in its right-justified secondary label. + * + * See strfrtime(3) for more information on the format string. + */ +void +ido_appointment_menu_item_set_format (IdoAppointmentMenuItem * self, + const char * strftime_fmt) +{ + priv_t * p; + + g_return_if_fail (IDO_IS_APPOINTMENT_MENU_ITEM (self)); + p = self->priv; + + g_free (p->format); + p->format = g_strdup (strftime_fmt); + update_timestamp_label (self); +} + +/** + * ido_appointment_menu_item_new_from_model: + * @menu_item: the corresponding menuitem + * @actions: action group to tell when this GtkMenuItem is activated + * + * Creates a new IdoAppointmentMenuItem with properties initialized from + * the menuitem's attributes. + * + * If the menuitem's 'action' attribute is set, trigger that action + * in @actions when this IdoAppointmentMenuItem is activated. + */ +GtkMenuItem * +ido_appointment_menu_item_new_from_model (GMenuItem * menu_item, + GActionGroup * actions) +{ + guint i; + guint n; + gint64 i64; + gchar * str; + IdoAppointmentMenuItem * ido_appointment; + GParameter parameters[8]; + + /* create the ido_appointment */ + + n = 0; + + if (g_menu_item_get_attribute (menu_item, "label", "s", &str)) + { + GParameter p = { "summary", G_VALUE_INIT }; + g_value_init (&p.value, G_TYPE_STRING); + g_value_take_string (&p.value, str); + parameters[n++] = p; + } + + if (g_menu_item_get_attribute (menu_item, "x-canonical-color", "s", &str)) + { + GParameter p = { "color", G_VALUE_INIT }; + g_value_init (&p.value, G_TYPE_STRING); + g_value_take_string (&p.value, str); + parameters[n++] = p; + } + + if (g_menu_item_get_attribute (menu_item, "x-canonical-time-format", "s", &str)) + { + GParameter p = { "format", G_VALUE_INIT }; + g_value_init (&p.value, G_TYPE_STRING); + g_value_take_string (&p.value, str); + parameters[n++] = p; + } + + if (g_menu_item_get_attribute (menu_item, "x-canonical-time", "x", &i64)) + { + GParameter p = { "time", G_VALUE_INIT }; + g_value_init (&p.value, G_TYPE_INT64); + g_value_set_int64 (&p.value, i64); + parameters[n++] = p; + } + + g_assert (n <= G_N_ELEMENTS (parameters)); + ido_appointment = g_object_newv (IDO_APPOINTMENT_MENU_ITEM_TYPE, n, parameters); + + for (i=0; i<n; i++) + g_value_unset (¶meters[i].value); + + + /* add an ActionHelper */ + + if (g_menu_item_get_attribute (menu_item, "action", "s", &str)) + { + GVariant * target; + IdoActionHelper * helper; + + target = g_menu_item_get_attribute_value (menu_item, "target", + G_VARIANT_TYPE_ANY); + helper = ido_action_helper_new (GTK_WIDGET(ido_appointment), actions, + str, target); + g_signal_connect_swapped (ido_appointment, "activate", + G_CALLBACK (ido_action_helper_activate), helper); + g_signal_connect_swapped (ido_appointment, "destroy", + G_CALLBACK (g_object_unref), helper); + + g_clear_pointer (&target, g_variant_unref); + g_free (str); + } + + return GTK_MENU_ITEM (ido_appointment); +} diff --git a/src/idoappointmentmenuitem.h b/src/idoappointmentmenuitem.h new file mode 100644 index 0000000..3a8c853 --- /dev/null +++ b/src/idoappointmentmenuitem.h @@ -0,0 +1,79 @@ +/** + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __IDO_APPOINTMENT_MENU_ITEM_H__ +#define __IDO_APPOINTMENT_MENU_ITEM_H__ + +#include <time.h> /* time_t */ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define IDO_APPOINTMENT_MENU_ITEM_TYPE (ido_appointment_menu_item_get_type ()) +#define IDO_APPOINTMENT_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_APPOINTMENT_MENU_ITEM_TYPE, IdoAppointmentMenuItem)) +#define IDO_IS_APPOINTMENT_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_APPOINTMENT_MENU_ITEM_TYPE)) + +typedef struct _IdoAppointmentMenuItem IdoAppointmentMenuItem; +typedef struct _IdoAppointmentMenuItemClass IdoAppointmentMenuItemClass; +typedef struct _IdoAppointmentMenuItemPrivate IdoAppointmentMenuItemPrivate; + +struct _IdoAppointmentMenuItemClass +{ + GtkMenuItemClass parent_class; +}; + +/** + * A menuitem that indicates a appointment. + * + * It contains a color-coded icon to indicate the appointment type, + * a primary label showing the appointment summary, + * and a right-justified secondary label telling when the appointment begins. + */ +struct _IdoAppointmentMenuItem +{ + /*< private >*/ + GtkMenuItem parent; + IdoAppointmentMenuItemPrivate * priv; +}; + + +GType ido_appointment_menu_item_get_type (void) G_GNUC_CONST; + +GtkWidget * ido_appointment_menu_item_new (void); + +GtkMenuItem * ido_appointment_menu_item_new_from_model (GMenuItem * menuitem, + GActionGroup * actions); + +void ido_appointment_menu_item_set_summary (IdoAppointmentMenuItem * menuitem, + const char * summary); + +void ido_appointment_menu_item_set_time (IdoAppointmentMenuItem * menuitem, + time_t time); + +void ido_appointment_menu_item_set_color (IdoAppointmentMenuItem * menuitem, + const char * color_str); + +void ido_appointment_menu_item_set_format (IdoAppointmentMenuItem * menuitem, + const char * strftime_fmt); + + +G_END_DECLS + +#endif diff --git a/src/idocalendarmenuitem.c b/src/idocalendarmenuitem.c index baae342..94f4f61 100644 --- a/src/idocalendarmenuitem.c +++ b/src/idocalendarmenuitem.c @@ -24,6 +24,7 @@ */ #include <gdk/gdkkeysyms.h> +#include "idoactionhelper.h" #include "idocalendarmenuitem.h" #include "config.h" @@ -324,21 +325,44 @@ calendar_day_selected_double_click_cb (GtkWidget *widget, g_signal_emit_by_name (item, "day-selected-double-click", NULL); } -/* Public API */ +/** + * ido_calendar_menu_item_new: + * + * Creates a new #IdoCalendarMenuItem + * + * Return Value: a new #IdoCalendarMenuItem. + **/ GtkWidget * ido_calendar_menu_item_new (void) { return g_object_new (IDO_TYPE_CALENDAR_MENU_ITEM, NULL); } +/** + * ido_calendar_menu_item_get_calendar: + * @menuitem: A #IdoCalendarMenuItem + * + * Returns the calendar associated with this menu item. + * + * Return Value: (transfer none): The #GtkCalendar used in this item. + */ GtkWidget * -ido_calendar_menu_item_get_calendar (IdoCalendarMenuItem *item) +ido_calendar_menu_item_get_calendar (IdoCalendarMenuItem *menuitem) { - g_return_val_if_fail (IDO_IS_CALENDAR_MENU_ITEM (item), NULL); + g_return_val_if_fail (IDO_IS_CALENDAR_MENU_ITEM (menuitem), NULL); - return item->priv->calendar; + return menuitem->priv->calendar; } +/** + * ido_calendar_menu_item_mark_day: + * @menuitem: A #IdoCalendarMenuItem + * @day: the day number to unmark between 1 and 31. + * + * Places a visual marker on a particular day. + * + * Return Value: #TRUE + */ gboolean ido_calendar_menu_item_mark_day (IdoCalendarMenuItem *menuitem, guint day) { @@ -348,6 +372,15 @@ ido_calendar_menu_item_mark_day (IdoCalendarMenuItem *menuitem, guint day) return TRUE; } +/** + * ido_calendar_menu_item_unmark_day: + * @menuitem: A #IdoCalendarMenuItem + * @day: the day number to unmark between 1 and 31. + * + * Removes the visual marker from a particular day. + * + * Return Value: #TRUE + */ gboolean ido_calendar_menu_item_unmark_day (IdoCalendarMenuItem *menuitem, guint day) { @@ -357,6 +390,12 @@ ido_calendar_menu_item_unmark_day (IdoCalendarMenuItem *menuitem, guint day) return TRUE; } +/** + * ido_calendar_menu_item_clear_marks: + * @menuitem: A #IdoCalendarMenuItem + * + * Remove all visual markers. + */ void ido_calendar_menu_item_clear_marks (IdoCalendarMenuItem *menuitem) { @@ -365,6 +404,13 @@ ido_calendar_menu_item_clear_marks (IdoCalendarMenuItem *menuitem) gtk_calendar_clear_marks(GTK_CALENDAR (menuitem->priv->calendar)); } +/** + * ido_calendar_menu_item_set_display_options: + * @menuitem: A #IdoCalendarMenuItem + * @flags: the display options to set + * + * Set the display options for the calendar. + */ void ido_calendar_menu_item_set_display_options (IdoCalendarMenuItem *menuitem, GtkCalendarDisplayOptions flags) { @@ -373,6 +419,14 @@ ido_calendar_menu_item_set_display_options (IdoCalendarMenuItem *menuitem, GtkCa gtk_calendar_set_display_options (GTK_CALENDAR (menuitem->priv->calendar), flags); } +/** + * ido_calendar_menu_item_get_display_options: + * @menuitem: A #IdoCalendarMenuItem + * + * Get the display options for the calendar. + * + * Return Value: the display options in use + */ GtkCalendarDisplayOptions ido_calendar_menu_item_get_display_options (IdoCalendarMenuItem *menuitem) { @@ -381,6 +435,15 @@ ido_calendar_menu_item_get_display_options (IdoCalendarMenuItem *menuitem) return gtk_calendar_get_display_options (GTK_CALENDAR (menuitem->priv->calendar)); } +/** + * ido_calendar_menu_item_get_date: + * @menuitem: A #IdoCalendarMenuItem + * @year: (out) (allow-none): location to store the year as a decimal number (e.g. 2011), or #NULL. + * @month: (out) (allow-none): location to store the month number (between 0 and 11), or #NULL. + * @day: (out) (allow-none): location to store the day number (between 1 and 31), or #NULL. + * + * Gets the selected date. + */ void ido_calendar_menu_item_get_date (IdoCalendarMenuItem *menuitem, guint *year, @@ -391,17 +454,195 @@ ido_calendar_menu_item_get_date (IdoCalendarMenuItem *menuitem, gtk_calendar_get_date (GTK_CALENDAR (menuitem->priv->calendar), year, month, day); } +/** + * ido_calendar_menu_item_set_date: + * @menuitem: A #IdoCalendarMenuItem + * @year: the year to show (e.g. 2011). + * @month: a month number (between 0 and 11). + * @day: The day number (between 1 and 31). + * + * Set the date shown on the calendar. + * + * Return Value: #TRUE + */ gboolean ido_calendar_menu_item_set_date (IdoCalendarMenuItem *menuitem, guint year, guint month, guint day) { - g_return_val_if_fail(IDO_IS_CALENDAR_MENU_ITEM(menuitem), FALSE); - gtk_calendar_select_month (GTK_CALENDAR (menuitem->priv->calendar), month, year); - gtk_calendar_select_day (GTK_CALENDAR (menuitem->priv->calendar), day); + guint old_y, old_m, old_d; + + g_return_val_if_fail (IDO_IS_CALENDAR_MENU_ITEM(menuitem), FALSE); + + ido_calendar_menu_item_get_date (menuitem, &old_y, &old_m, &old_d); + + if ((old_y != year) || (old_m != month)) + gtk_calendar_select_month (GTK_CALENDAR (menuitem->priv->calendar), month, year); + + if (old_d != day) + gtk_calendar_select_day (GTK_CALENDAR (menuitem->priv->calendar), day); + return TRUE; } +/*** +**** +**** +**** +***/ + +static void +activate_current_day (IdoCalendarMenuItem * ido_calendar, + const char * action_name_key) +{ + GObject * o; + const char * action_name; + GActionGroup * action_group; + + o = G_OBJECT (ido_calendar); + action_name = g_object_get_data (o, action_name_key); + action_group = g_object_get_data (o, "ido-action-group"); + + if (action_group && action_name) + { + guint y, m, d; + GDateTime * date_time; + GVariant * target; + + ido_calendar_menu_item_get_date (ido_calendar, &y, &m, &d); + m++; /* adjust month from GtkCalendar (0 based) to GDateTime (1 based) */ + date_time = g_date_time_new_local (y, m, d, 9, 0, 0); + target = g_variant_new_int64 (g_date_time_to_unix (date_time)); + + g_action_group_activate_action (action_group, action_name, target); + + g_date_time_unref (date_time); + } +} + +static void +on_day_selected (IdoCalendarMenuItem * ido_calendar) +{ + activate_current_day (ido_calendar, "ido-selection-action-name"); +} + +static void +on_day_double_clicked (IdoCalendarMenuItem * ido_calendar) +{ + activate_current_day (ido_calendar, "ido-activation-action-name"); +} + +static void +on_action_state_changed (IdoActionHelper * helper, + GVariant * state, + gpointer unused G_GNUC_UNUSED) +{ + GVariant * v; + const char * key; + IdoCalendarMenuItem * ido_calendar; + ido_calendar = IDO_CALENDAR_MENU_ITEM (ido_action_helper_get_widget (helper)); + + g_return_if_fail (ido_calendar != NULL); + g_return_if_fail (g_variant_is_of_type (state, G_VARIANT_TYPE_DICTIONARY)); + + /* an int64 representing a time_t indicating which year and month should + be visible in the calendar and which day should be given the cursor. */ + key = "calendar-day"; + if ((v = g_variant_lookup_value (state, key, G_VARIANT_TYPE_INT64))) + { + int y, m, d; + time_t t; + GDateTime * date_time; + + t = g_variant_get_int64 (v); + date_time = g_date_time_new_from_unix_local (t); + g_date_time_get_ymd (date_time, &y, &m, &d); + m--; /* adjust month from GDateTime (1 based) to GtkCalendar (0 based) */ + ido_calendar_menu_item_set_date (ido_calendar, y, m, d); + + g_date_time_unref (date_time); + g_variant_unref (v); + } + /* a boolean value of whether or not to show the week numbers */ + key = "show-week-numbers"; + if ((v = g_variant_lookup_value (state, key, G_VARIANT_TYPE_BOOLEAN))) + { + const GtkCalendarDisplayOptions old_flags = ido_calendar_menu_item_get_display_options (ido_calendar); + GtkCalendarDisplayOptions new_flags = old_flags; + + if (g_variant_get_boolean (v)) + new_flags |= GTK_CALENDAR_SHOW_WEEK_NUMBERS; + else + new_flags &= ~GTK_CALENDAR_SHOW_WEEK_NUMBERS; + + if (new_flags != old_flags) + ido_calendar_menu_item_set_display_options (ido_calendar, new_flags); + + g_variant_unref (v); + } + + /* an array of int32 day-of-months denoting days that have appointments */ + key = "appointment-days"; + ido_calendar_menu_item_clear_marks (ido_calendar); + if ((v = g_variant_lookup_value (state, key, G_VARIANT_TYPE("ai")))) + { + gint32 day; + GVariantIter iter; + + g_variant_iter_init (&iter, v); + while (g_variant_iter_next (&iter, "i", &day)) + ido_calendar_menu_item_mark_day (ido_calendar, day); + + g_variant_unref (v); + } +} + +GtkMenuItem * +ido_calendar_menu_item_new_from_model (GMenuItem * menu_item, + GActionGroup * actions) +{ + GObject * o; + GtkWidget * calendar; + IdoCalendarMenuItem * ido_calendar; + gchar * selection_action_name; + gchar * activation_action_name; + + /* get the select & activate action names */ + g_menu_item_get_attribute (menu_item, "action", "s", &selection_action_name); + g_menu_item_get_attribute (menu_item, "activation-action", "s", &activation_action_name); + + /* remember the action group & action names so that we can poke them + when user selects and double-clicks */ + ido_calendar = IDO_CALENDAR_MENU_ITEM (ido_calendar_menu_item_new ()); + o = G_OBJECT (ido_calendar); + g_object_set_data_full (o, "ido-action-group", g_object_ref(actions), g_object_unref); + g_object_set_data_full (o, "ido-selection-action-name", selection_action_name, g_free); + g_object_set_data_full (o, "ido-activation-action-name", activation_action_name, g_free); + calendar = ido_calendar_menu_item_get_calendar (ido_calendar); + g_signal_connect_swapped (calendar, "day-selected", + G_CALLBACK(on_day_selected), ido_calendar); + g_signal_connect_swapped (calendar, "day-selected-double-click", + G_CALLBACK(on_day_double_clicked), ido_calendar); + + /* Use an IdoActionHelper for state updates. + Since we have two separate actions for selection & activation, + we'll do the activation & targets logic here in ido-calendar */ + if (selection_action_name != NULL) + { + IdoActionHelper * helper; + + helper = ido_action_helper_new (GTK_WIDGET(ido_calendar), + actions, + selection_action_name, + NULL); + g_signal_connect (helper, "action-state-changed", + G_CALLBACK (on_action_state_changed), NULL); + g_signal_connect_swapped (ido_calendar, "destroy", + G_CALLBACK (g_object_unref), helper); + } + + return GTK_MENU_ITEM (ido_calendar); +} diff --git a/src/idocalendarmenuitem.h b/src/idocalendarmenuitem.h index c4833fb..5cd913e 100644 --- a/src/idocalendarmenuitem.h +++ b/src/idocalendarmenuitem.h @@ -69,7 +69,12 @@ void ido_calendar_menu_item_get_date (IdoCalendarMenuItem *menu gboolean ido_calendar_menu_item_set_date (IdoCalendarMenuItem *menuitem, guint year, guint month, - guint day); + guint day); + +GtkMenuItem * ido_calendar_menu_item_new_from_model (GMenuItem * menuitem, + GActionGroup * actions); + + G_END_DECLS #endif /* __IDO_CALENDAR_MENU_ITEM_H__ */ diff --git a/src/idoentrymenuitem.c b/src/idoentrymenuitem.c index 5390d0b..5b5a3fb 100644 --- a/src/idoentrymenuitem.c +++ b/src/idoentrymenuitem.c @@ -260,17 +260,31 @@ entry_move_focus_cb (GtkWidget *widget, GTK_DIR_TAB_FORWARD); } -/* Public API */ +/** + * ido_entry_menu_item_new: + * + * Creates a new #IdoEntryMenuItem. + * + * Return Value: the newly created #IdoEntryMenuItem. + */ GtkWidget * ido_entry_menu_item_new (void) { return g_object_new (IDO_TYPE_ENTRY_MENU_ITEM, NULL); } +/** + * ido_entry_menu_item_get_entry: + * @menuitem: The #IdoEntryMenuItem. + * + * Get the #GtkEntry used in this menu item. + * + * Return Value: (transfer none): The #GtkEntry inside this menu item. + */ GtkWidget * -ido_entry_menu_item_get_entry (IdoEntryMenuItem *item) +ido_entry_menu_item_get_entry (IdoEntryMenuItem *menuitem) { - g_return_val_if_fail (IDO_IS_ENTRY_MENU_ITEM (item), NULL); + g_return_val_if_fail (IDO_IS_ENTRY_MENU_ITEM (menuitem), NULL); - return item->priv->entry; + return menuitem->priv->entry; } diff --git a/src/idolocationmenuitem.c b/src/idolocationmenuitem.c new file mode 100644 index 0000000..347c9e8 --- /dev/null +++ b/src/idolocationmenuitem.c @@ -0,0 +1,477 @@ +/** + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include <string.h> /* strstr() */ + +#include <gtk/gtk.h> + +#include "idoactionhelper.h" +#include "idolocationmenuitem.h" + +enum +{ + PROP_0, + PROP_NAME, + PROP_TIMEZONE, + PROP_FORMAT, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _IdoLocationMenuItemPrivate +{ + char * name; + char * timezone; + char * format; + + guint timestamp_timer; + + GtkWidget * name_label; + GtkWidget * timestamp_label; +}; + +typedef IdoLocationMenuItemPrivate priv_t; + +G_DEFINE_TYPE (IdoLocationMenuItem, ido_location_menu_item, GTK_TYPE_MENU_ITEM); + +/*** +**** Timestamp Label +***/ + +static void +update_timestamp_label (IdoLocationMenuItem * self) +{ + priv_t * p = self->priv; + + if (p->format && *p->format) + { + GTimeZone * tz; + GDateTime * now; + char * str; + + tz = g_time_zone_new (p->timezone); + if (tz == NULL) + tz = g_time_zone_new_local (); + now = g_date_time_new_now (tz); + str = g_date_time_format (now, p->format); + + gtk_label_set_text (GTK_LABEL(p->timestamp_label), str); + + g_free (str); + g_date_time_unref (now); + g_time_zone_unref (tz); + } + else + { + gtk_label_set_text (GTK_LABEL(p->timestamp_label), ""); + } +} + +static void +stop_timestamp_timer (IdoLocationMenuItem * self) +{ + priv_t * p = self->priv; + + if (p->timestamp_timer != 0) + { + g_source_remove (p->timestamp_timer); + p->timestamp_timer = 0; + } +} + +static void start_timestamp_timer (IdoLocationMenuItem * self); + +static gboolean +on_timestamp_timer (gpointer gself) +{ + IdoLocationMenuItem * self = IDO_LOCATION_MENU_ITEM (gself); + + update_timestamp_label (self); + + start_timestamp_timer (self); + return G_SOURCE_REMOVE; +} + +static guint +calculate_seconds_until_next_minute (void) +{ + guint seconds; + GTimeSpan diff; + GDateTime * now; + GDateTime * next; + GDateTime * start_of_next; + + now = g_date_time_new_now_local (); + next = g_date_time_add_minutes (now, 1); + start_of_next = g_date_time_new_local (g_date_time_get_year (next), + g_date_time_get_month (next), + g_date_time_get_day_of_month (next), + g_date_time_get_hour (next), + g_date_time_get_minute (next), + 1); + + diff = g_date_time_difference (start_of_next, now); + seconds = (diff + (G_TIME_SPAN_SECOND - 1)) / G_TIME_SPAN_SECOND; + + /* cleanup */ + g_date_time_unref (start_of_next); + g_date_time_unref (next); + g_date_time_unref (now); + + return seconds; +} + +static void +start_timestamp_timer (IdoLocationMenuItem * self) +{ + int interval_sec; + gboolean timestamp_shows_seconds; + priv_t * p = self->priv; + const char * const fmt = p->format; + + stop_timestamp_timer (self); + + timestamp_shows_seconds = fmt && (strstr(fmt,"%s") || strstr(fmt,"%S") || + strstr(fmt,"%T") || strstr(fmt,"%X") || + strstr(fmt,"%c")); + + if (timestamp_shows_seconds) + interval_sec = 1; + else + interval_sec = calculate_seconds_until_next_minute(); + + p->timestamp_timer = g_timeout_add_seconds (interval_sec, + on_timestamp_timer, + self); +} + +/*** +**** GObject Virtual Functions +***/ + +static void +my_get_property (GObject * o, + guint property_id, + GValue * value, + GParamSpec * pspec) +{ + IdoLocationMenuItem * self = IDO_LOCATION_MENU_ITEM (o); + priv_t * p = self->priv; + + switch (property_id) + { + case PROP_NAME: + g_value_set_string (value, p->name); + break; + + case PROP_TIMEZONE: + g_value_set_string (value, p->timezone); + break; + + case PROP_FORMAT: + g_value_set_string (value, p->format); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + break; + } +} + +static void +my_set_property (GObject * o, + guint property_id, + const GValue * value, + GParamSpec * pspec) +{ + IdoLocationMenuItem * self = IDO_LOCATION_MENU_ITEM (o); + + switch (property_id) + { + case PROP_NAME: + ido_location_menu_item_set_name (self, g_value_get_string (value)); + break; + + case PROP_TIMEZONE: + ido_location_menu_item_set_timezone (self, g_value_get_string (value)); + break; + + case PROP_FORMAT: + ido_location_menu_item_set_format (self, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + break; + } +} + +static void +my_dispose (GObject * object) +{ + stop_timestamp_timer (IDO_LOCATION_MENU_ITEM (object)); + + G_OBJECT_CLASS (ido_location_menu_item_parent_class)->dispose (object); +} + +static void +my_finalize (GObject * object) +{ + IdoLocationMenuItem * self = IDO_LOCATION_MENU_ITEM (object); + priv_t * p = self->priv; + + g_free (p->format); + g_free (p->name); + g_free (p->timezone); + + G_OBJECT_CLASS (ido_location_menu_item_parent_class)->finalize (object); +} + +/*** +**** Instantiation +***/ + +static void +ido_location_menu_item_class_init (IdoLocationMenuItemClass *klass) +{ + GParamFlags prop_flags; + GObjectClass * gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (IdoLocationMenuItemPrivate)); + + gobject_class->get_property = my_get_property; + gobject_class->set_property = my_set_property; + gobject_class->dispose = my_dispose; + gobject_class->finalize = my_finalize; + + prop_flags = G_PARAM_CONSTRUCT + | G_PARAM_READWRITE + | G_PARAM_STATIC_STRINGS; + + properties[PROP_NAME] = g_param_spec_string ( + "name", + "The location's name", + "The name to display; eg, 'Oklahoma City'", + "Location", + prop_flags); + + properties[PROP_TIMEZONE] = g_param_spec_string ( + "timezone", + "timezone identifier", + "string used to identify a timezone; eg, 'America/Chicago'", + NULL, + prop_flags); + + properties[PROP_FORMAT] = g_param_spec_string ( + "format", + "strftime format", + "strftime-style format string for the timestamp", + "%T", + prop_flags); + + g_object_class_install_properties (gobject_class, PROP_LAST, properties); +} + +static void +ido_location_menu_item_init (IdoLocationMenuItem *self) +{ + priv_t * p; + GtkBox * box; + GtkWidget * w; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + IDO_LOCATION_MENU_ITEM_TYPE, + IdoLocationMenuItemPrivate); + + p = self->priv; + + p->name_label = gtk_label_new (NULL); + p->timestamp_label = gtk_label_new (NULL); + + w = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 3); + gtk_misc_set_alignment (GTK_MISC(p->timestamp_label), 1.0, 0.5); + box = GTK_BOX (w); + gtk_box_pack_start (box, p->name_label, FALSE, FALSE, 3); + gtk_box_pack_end (box, p->timestamp_label, FALSE, FALSE, 5); + + gtk_widget_show_all (w); + gtk_container_add (GTK_CONTAINER (self), w); +} + + +/*** +**** Public API +***/ + +/* create a new IdoLocationMenuItemType */ +GtkWidget * +ido_location_menu_item_new (void) +{ + return GTK_WIDGET (g_object_new (IDO_LOCATION_MENU_ITEM_TYPE, NULL)); +} + +/** + * ido_location_menu_item_set_name: + * @name: human-readable name, such as a city (eg: "Oklahoma City") + * + * Sets this location's name, for display in the menuitem's primary label. + */ +void +ido_location_menu_item_set_name (IdoLocationMenuItem * self, + const char * name) +{ + priv_t * p; + + g_return_if_fail (IDO_IS_LOCATION_MENU_ITEM (self)); + p = self->priv; + + g_free (p->name); + p->name = g_strdup (name); + gtk_label_set_text (GTK_LABEL(p->name_label), p->name); +} + +/** + * ido_location_menu_item_set_timezone: + * @timezone: timezone identifier (eg: "America/Chicago") + * + * Set this location's timezone. This will be used to show the location's + * current time in menuitem's right-justified secondary label. + */ +void +ido_location_menu_item_set_timezone (IdoLocationMenuItem * self, + const char * timezone) +{ + priv_t * p; + + g_return_if_fail (IDO_IS_LOCATION_MENU_ITEM (self)); + p = self->priv; + + g_free (p->timezone); + p->timezone = g_strdup (timezone); + update_timestamp_label (self); +} + +/** + * ido_location_menu_item_set_format: + * @format: the format string used when showing the location's time + * + * Set the format string for rendering the location's time + * in its right-justified secondary label. + * + * See strfrtime(3) for more information on the format string. + */ +void +ido_location_menu_item_set_format (IdoLocationMenuItem * self, + const char * format) +{ + priv_t * p; + + g_return_if_fail (IDO_IS_LOCATION_MENU_ITEM (self)); + p = self->priv; + + g_free (p->format); + p->format = g_strdup (format); + update_timestamp_label (self); + start_timestamp_timer (self); +} + +/** + * ido_location_menu_item_new_from_model: + * @menu_item: the corresponding menuitem + * @actions: action group to tell when this GtkMenuItem is activated + * + * Creates a new IdoLocationMenuItem with properties initialized from + * the menuitem's attributes. + * + * If the menuitem's 'action' attribute is set, trigger that action + * in @actions when this IdoLocationMenuItem is activated. + */ +GtkMenuItem * +ido_location_menu_item_new_from_model (GMenuItem * menu_item, + GActionGroup * actions) +{ + guint i; + guint n; + gchar * str; + IdoLocationMenuItem * ido_location; + GParameter parameters[4]; + + /* create the ido_location */ + + n = 0; + + if (g_menu_item_get_attribute (menu_item, "label", "s", &str)) + { + GParameter p = { "name", G_VALUE_INIT }; + g_value_init (&p.value, G_TYPE_STRING); + g_value_take_string (&p.value, str); + parameters[n++] = p; + } + + if (g_menu_item_get_attribute (menu_item, "x-canonical-timezone", "s", &str)) + { + GParameter p = { "timezone", G_VALUE_INIT }; + g_value_init (&p.value, G_TYPE_STRING); + g_value_take_string (&p.value, str); + parameters[n++] = p; + } + + if (g_menu_item_get_attribute (menu_item, "x-canonical-time-format", "s", &str)) + { + GParameter p = { "format", G_VALUE_INIT }; + g_value_init (&p.value, G_TYPE_STRING); + g_value_take_string (&p.value, str); + parameters[n++] = p; + } + + g_assert (n <= G_N_ELEMENTS (parameters)); + ido_location = g_object_newv (IDO_LOCATION_MENU_ITEM_TYPE, n, parameters); + + for (i=0; i<n; i++) + g_value_unset (¶meters[i].value); + + + /* give it an ActionHelper */ + + if (g_menu_item_get_attribute (menu_item, "action", "s", &str)) + { + GVariant * target; + IdoActionHelper * helper; + + target = g_menu_item_get_attribute_value (menu_item, "target", + G_VARIANT_TYPE_ANY); + helper = ido_action_helper_new (GTK_WIDGET(ido_location), actions, + str, target); + g_signal_connect_swapped (ido_location, "activate", + G_CALLBACK (ido_action_helper_activate), helper); + g_signal_connect_swapped (ido_location, "destroy", + G_CALLBACK (g_object_unref), helper); + + if (target) + g_variant_unref (target); + g_free (str); + } + + return GTK_MENU_ITEM (ido_location); +} diff --git a/src/idolocationmenuitem.h b/src/idolocationmenuitem.h new file mode 100644 index 0000000..08a97af --- /dev/null +++ b/src/idolocationmenuitem.h @@ -0,0 +1,73 @@ +/** + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __IDO_LOCATION_MENU_ITEM_H__ +#define __IDO_LOCATION_MENU_ITEM_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define IDO_LOCATION_MENU_ITEM_TYPE (ido_location_menu_item_get_type ()) +#define IDO_LOCATION_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_LOCATION_MENU_ITEM_TYPE, IdoLocationMenuItem)) +#define IDO_IS_LOCATION_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_LOCATION_MENU_ITEM_TYPE)) + +typedef struct _IdoLocationMenuItem IdoLocationMenuItem; +typedef struct _IdoLocationMenuItemClass IdoLocationMenuItemClass; +typedef struct _IdoLocationMenuItemPrivate IdoLocationMenuItemPrivate; + +struct _IdoLocationMenuItemClass +{ + GtkMenuItemClass parent_class; +}; + +/** + * A menuitem that indicates a location. + * + * It contains a primary label giving the location's name and a + * right-aligned secondary label showing the location's current time + */ +struct _IdoLocationMenuItem +{ + /*< private >*/ + GtkMenuItem parent; + IdoLocationMenuItemPrivate * priv; +}; + + +GType ido_location_menu_item_get_type (void) G_GNUC_CONST; + +GtkWidget * ido_location_menu_item_new (void); + +GtkMenuItem * ido_location_menu_item_new_from_model (GMenuItem * menuitem, + GActionGroup * actions); + +void ido_location_menu_item_set_name (IdoLocationMenuItem * menuitem, + const char * name); + +void ido_location_menu_item_set_timezone (IdoLocationMenuItem * menuitem, + const char * timezone); + +void ido_location_menu_item_set_format (IdoLocationMenuItem * menuitem, + const char * strftime_fmt); + + +G_END_DECLS + +#endif diff --git a/src/idomediaplayermenuitem.c b/src/idomediaplayermenuitem.c new file mode 100644 index 0000000..7e6e9d3 --- /dev/null +++ b/src/idomediaplayermenuitem.c @@ -0,0 +1,376 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Conor Curran <conor.curran@canonical.com> + * Mirco Müller <mirco.mueller@canonical.com> + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include "config.h" + +#include "idomediaplayermenuitem.h" +#include "idoactionhelper.h" + +#define ALBUM_ART_SIZE 60 + +typedef GtkMenuItemClass IdoMediaPlayerMenuItemClass; + +struct _IdoMediaPlayerMenuItem +{ + GtkMenuItem parent; + + GCancellable *cancellable; + GtkWidget* player_label; + GtkWidget* player_icon; + GtkWidget* metadata_widget; + GtkWidget* album_art; + GtkWidget* artist_label; + GtkWidget* piece_label; + GtkWidget* container_label; + + gboolean running; +}; + +G_DEFINE_TYPE (IdoMediaPlayerMenuItem, ido_media_player_menu_item, GTK_TYPE_MENU_ITEM); + +static void +ido_media_player_menu_item_dispose (GObject *object) +{ + IdoMediaPlayerMenuItem *self = IDO_MEDIA_PLAYER_MENU_ITEM (object); + + if (self->cancellable) + { + g_cancellable_cancel (self->cancellable); + g_clear_object (&self->cancellable); + } + + G_OBJECT_CLASS (ido_media_player_menu_item_parent_class)->dispose (object); +} + +static gboolean +ido_media_player_menu_item_draw (GtkWidget *widget, + cairo_t *cr) +{ + IdoMediaPlayerMenuItem *self = IDO_MEDIA_PLAYER_MENU_ITEM (widget); + + GTK_WIDGET_CLASS (ido_media_player_menu_item_parent_class)->draw (widget, cr); + + /* draw a triangle next to the application name if the app is running */ + if (self->running) + { + const int arrow_width = 5; + const int half_arrow_height = 4; + + GdkRGBA color; + GtkAllocation allocation; + GtkAllocation label_allocation; + int x; + int y; + + gtk_style_context_get_color (gtk_widget_get_style_context (widget), + gtk_widget_get_state (widget), + &color); + + gtk_widget_get_allocation (widget, &allocation); + gtk_widget_get_allocation (self->player_label, &label_allocation); + x = allocation.x; + y = label_allocation.y - allocation.y + label_allocation.height / 2; + + cairo_move_to (cr, x, y - half_arrow_height); + cairo_line_to (cr, x, y + half_arrow_height); + cairo_line_to (cr, x + arrow_width, y); + cairo_close_path (cr); + + gdk_cairo_set_source_rgba (cr, &color); + cairo_fill (cr); + } + + return FALSE; +} + +static void +ido_media_player_menu_item_get_preferred_width (GtkWidget *widget, + gint *minimum, + gint *natural) +{ + *minimum = *natural = 200; +} + +static void +ido_media_player_menu_item_class_init (IdoMediaPlayerMenuItemClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = ido_media_player_menu_item_dispose; + + widget_class->get_preferred_width = ido_media_player_menu_item_get_preferred_width; + widget_class->draw = ido_media_player_menu_item_draw; +} + +static void +ido_media_player_menu_item_init (IdoMediaPlayerMenuItem *self) +{ + GtkWidget *grid; + + self->cancellable = g_cancellable_new (); + + self->player_icon = gtk_image_new(); + gtk_widget_set_margin_right (self->player_icon, 6); + gtk_widget_set_halign (self->player_icon, GTK_ALIGN_START); + + self->player_label = gtk_label_new (NULL); + gtk_widget_set_halign (self->player_label, GTK_ALIGN_START); + gtk_widget_set_hexpand (self->player_label, TRUE); + + self->album_art = gtk_image_new(); + gtk_widget_set_size_request (self->album_art, ALBUM_ART_SIZE, ALBUM_ART_SIZE); + gtk_widget_set_margin_right (self->album_art, 8); + + self->artist_label = gtk_label_new (NULL); + gtk_widget_set_halign (self->artist_label, GTK_ALIGN_START); + gtk_label_set_ellipsize (GTK_LABEL (self->artist_label), PANGO_ELLIPSIZE_MIDDLE); + + self->piece_label = gtk_label_new (NULL); + gtk_widget_set_halign (self->piece_label, GTK_ALIGN_START); + gtk_label_set_ellipsize (GTK_LABEL (self->piece_label), PANGO_ELLIPSIZE_MIDDLE); + + self->container_label = gtk_label_new (NULL); + gtk_widget_set_halign (self->container_label, GTK_ALIGN_START); + gtk_widget_set_valign (self->container_label, GTK_ALIGN_START); + gtk_widget_set_vexpand (self->container_label, TRUE); + gtk_label_set_ellipsize (GTK_LABEL (self->container_label), PANGO_ELLIPSIZE_MIDDLE); + + self->metadata_widget = gtk_grid_new (); + gtk_grid_attach (GTK_GRID (self->metadata_widget), self->album_art, 0, 0, 1, 4); + gtk_grid_attach (GTK_GRID (self->metadata_widget), self->piece_label, 1, 0, 1, 1); + gtk_grid_attach (GTK_GRID (self->metadata_widget), self->artist_label, 1, 1, 1, 1); + gtk_grid_attach (GTK_GRID (self->metadata_widget), self->container_label, 1, 2, 1, 1); + + grid = gtk_grid_new (); + gtk_grid_set_row_spacing (GTK_GRID (grid), 8); + gtk_grid_attach (GTK_GRID (grid), self->player_icon, 0, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), self->player_label, 1, 0, 1, 1); + gtk_grid_attach (GTK_GRID (grid), self->metadata_widget, 0, 1, 2, 1); + + gtk_container_add (GTK_CONTAINER (self), grid); + gtk_widget_show_all (grid); + + /* hide metadata by defalut (player is not running) */ + gtk_widget_hide (self->metadata_widget); +} + +static void +ido_media_player_menu_item_set_player_name (IdoMediaPlayerMenuItem *self, + const gchar *name) +{ + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + gtk_label_set_label (GTK_LABEL (self->player_label), name); +} + +static void +ido_media_player_menu_item_set_player_icon (IdoMediaPlayerMenuItem *self, + GIcon *icon) +{ + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + gtk_image_set_from_gicon (GTK_IMAGE (self->player_icon), icon, GTK_ICON_SIZE_MENU); +} + +static void +ido_media_player_menu_item_set_is_running (IdoMediaPlayerMenuItem *self, + gboolean running) +{ + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + if (self->running != running) + { + self->running = running; + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +static void +album_art_received (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + IdoMediaPlayerMenuItem *self = user_data; + GdkPixbuf *pixbuf; + GError *error = NULL; + + pixbuf = gdk_pixbuf_new_from_stream_finish (result, &error); + if (pixbuf == NULL) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("unable to fetch album art: %s", error->message); + + g_error_free (error); + return; + } + + gtk_image_set_from_pixbuf (GTK_IMAGE (self->album_art), pixbuf); + g_object_unref (pixbuf); +} + +static void +album_art_file_opened (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + IdoMediaPlayerMenuItem *self = user_data; + GFileInputStream *input; + GError *error = NULL; + + input = g_file_read_finish (G_FILE (object), result, &error); + if (input == NULL) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("unable to fetch album art: %s", error->message); + + g_error_free (error); + return; + } + + gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (input), + ALBUM_ART_SIZE, ALBUM_ART_SIZE, TRUE, + self->cancellable, + album_art_received, self); + + g_object_unref (input); +} + +static void +ido_media_player_menu_item_set_album_art (IdoMediaPlayerMenuItem *self, + const gchar *url) +{ + GFile *file; + + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + gtk_image_clear (GTK_IMAGE (self->album_art)); + + if (url == NULL) + return; + + file = g_file_new_for_uri (url); + g_file_read_async (file, G_PRIORITY_DEFAULT, self->cancellable, album_art_file_opened, self); + + g_object_unref (file); +} + +static void +ido_media_player_menu_item_set_metadata (IdoMediaPlayerMenuItem *self, + const gchar *title, + const gchar *artist, + const gchar *album, + const gchar *art_url) +{ + g_return_if_fail (IDO_IS_MEDIA_PLAYER_MENU_ITEM (self)); + + /* hide if there's no metadata */ + if (title == NULL || *title == '\0') + { + gtk_label_set_label (GTK_LABEL (self->piece_label), NULL); + gtk_label_set_label (GTK_LABEL (self->artist_label), NULL); + gtk_label_set_label (GTK_LABEL (self->container_label), NULL); + ido_media_player_menu_item_set_album_art (self, NULL); + gtk_widget_hide (self->metadata_widget); + } + else + { + gtk_label_set_label (GTK_LABEL (self->piece_label), title); + gtk_label_set_label (GTK_LABEL (self->artist_label), artist); + gtk_label_set_label (GTK_LABEL (self->container_label), album); + ido_media_player_menu_item_set_album_art (self, art_url); + gtk_widget_show (self->metadata_widget); + } +} + +static void +ido_media_player_menu_item_state_changed (IdoActionHelper *helper, + GVariant *state, + gpointer user_data) +{ + IdoMediaPlayerMenuItem *widget; + gboolean running = FALSE; + const gchar *title = NULL; + const gchar *artist = NULL; + const gchar *album = NULL; + const gchar *art_url = NULL; + + g_variant_lookup (state, "running", "b", &running); + g_variant_lookup (state, "title", "&s", &title); + g_variant_lookup (state, "artist", "&s", &artist); + g_variant_lookup (state, "album", "&s", &album); + g_variant_lookup (state, "art-url", "&s", &art_url); + + widget = IDO_MEDIA_PLAYER_MENU_ITEM (ido_action_helper_get_widget (helper)); + ido_media_player_menu_item_set_is_running (widget, running); + ido_media_player_menu_item_set_metadata (widget, title, artist, album, art_url); +} + +GtkMenuItem * +ido_media_player_menu_item_new_from_model (GMenuItem *menuitem, + GActionGroup *actions) +{ + GtkMenuItem *widget; + gchar *label; + gchar *action; + GVariant *v; + + widget = g_object_new (IDO_TYPE_MEDIA_PLAYER_MENU_ITEM, NULL); + + if (g_menu_item_get_attribute (menuitem, "label", "s", &label)) + { + ido_media_player_menu_item_set_player_name (IDO_MEDIA_PLAYER_MENU_ITEM (widget), label); + g_free (label); + } + + if ((v = g_menu_item_get_attribute_value (menuitem, "icon", NULL))) + { + GIcon *icon; + + icon = g_icon_deserialize (v); + if (icon) + { + ido_media_player_menu_item_set_player_icon (IDO_MEDIA_PLAYER_MENU_ITEM (widget), icon); + g_object_unref (icon); + } + + g_variant_unref (v); + } + + if (g_menu_item_get_attribute (menuitem, "action", "s", &action)) + { + IdoActionHelper *helper; + + helper = ido_action_helper_new (GTK_WIDGET (widget), actions, action, NULL); + g_signal_connect (helper, "action-state-changed", + G_CALLBACK (ido_media_player_menu_item_state_changed), NULL); + + g_signal_connect_object (widget, "activate", + G_CALLBACK (ido_action_helper_activate), + helper, G_CONNECT_SWAPPED); + + g_signal_connect_swapped (widget, "destroy", G_CALLBACK (g_object_unref), helper); + + g_free (action); + } + + return widget; +} diff --git a/src/idomediaplayermenuitem.h b/src/idomediaplayermenuitem.h new file mode 100644 index 0000000..e4a7e8e --- /dev/null +++ b/src/idomediaplayermenuitem.h @@ -0,0 +1,42 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Conor Curran <conor.curran@canonical.com> + * Mirco Müller <mirco.mueller@canonical.com> + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#ifndef __IDO_MEDIA_PLAYER_MENU_ITEM_H__ +#define __IDO_MEDIA_PLAYER_MENU_ITEM_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define IDO_TYPE_MEDIA_PLAYER_MENU_ITEM (ido_media_player_menu_item_get_type ()) +#define IDO_MEDIA_PLAYER_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IDO_TYPE_MEDIA_PLAYER_MENU_ITEM, IdoMediaPlayerMenuItem)) +#define IDO_IS_MEDIA_PLAYER_MENU_ITEM(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), IDO_TYPE_MEDIA_PLAYER_MENU_ITEM)) + +typedef struct _IdoMediaPlayerMenuItem IdoMediaPlayerMenuItem; + +GType ido_media_player_menu_item_get_type (void); + +GtkMenuItem * ido_media_player_menu_item_new_from_model (GMenuItem *menuitem, + GActionGroup *actions); + +G_END_DECLS + +#endif diff --git a/src/idomenuitemfactory.c b/src/idomenuitemfactory.c new file mode 100644 index 0000000..650c95f --- /dev/null +++ b/src/idomenuitemfactory.c @@ -0,0 +1,92 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include <gtk/gtk.h> +#include <gtk/ubuntu-private.h> + +#include "idoappointmentmenuitem.h" +#include "idocalendarmenuitem.h" +#include "idolocationmenuitem.h" +#include "idoscalemenuitem.h" +#include "idousermenuitem.h" +#include "idomediaplayermenuitem.h" +#include "idoplaybackmenuitem.h" + +#define IDO_TYPE_MENU_ITEM_FACTORY (ido_menu_item_factory_get_type ()) +#define IDO_MENU_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), IDO_TYPE_MENU_ITEM_FACTORY, IdoMenuItemFactory)) +#define IDO_IS_MENU_ITEM_FACTORY(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), IDO_TYPE_MENU_ITEM_FACTORY)) + +typedef GObject IdoMenuItemFactory; +typedef GObjectClass IdoMenuItemFactoryClass; + +GType ido_menu_item_factory_get_type (void); +static void ido_menu_item_factory_interface_init (UbuntuMenuItemFactoryInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (IdoMenuItemFactory, ido_menu_item_factory, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (UBUNTU_TYPE_MENU_ITEM_FACTORY, ido_menu_item_factory_interface_init) + g_io_extension_point_implement (UBUNTU_MENU_ITEM_FACTORY_EXTENSION_POINT_NAME, + g_define_type_id, "ido", 0);) + +static GtkMenuItem * +ido_menu_item_factory_create_menu_item (UbuntuMenuItemFactory *factory, + const gchar *type, + GMenuItem *menuitem, + GActionGroup *actions) +{ + GtkMenuItem *item = NULL; + + if (g_str_equal (type, "indicator.user-menu-item")) + item = ido_user_menu_item_new_from_model (menuitem, actions); + + else if (g_str_equal (type, "com.canonical.indicator.calendar")) + item = ido_calendar_menu_item_new_from_model (menuitem, actions); + + else if (g_str_equal (type, "com.canonical.indicator.location")) + item = ido_location_menu_item_new_from_model (menuitem, actions); + + else if (g_str_equal (type, "com.canonical.indicator.appointment")) + item = ido_appointment_menu_item_new_from_model (menuitem, actions); + + else if (g_str_equal (type, "com.canonical.unity.slider")) + item = ido_scale_menu_item_new_from_model (menuitem, actions); + + else if (g_str_equal (type, "com.canonical.unity.media-player")) + item = ido_media_player_menu_item_new_from_model (menuitem, actions); + + else if (g_str_equal (type, "com.canonical.unity.playback-item")) + item = ido_playback_menu_item_new_from_model (menuitem, actions); + + return item; +} + +static void +ido_menu_item_factory_class_init (IdoMenuItemFactoryClass *class) +{ +} + +static void +ido_menu_item_factory_interface_init (UbuntuMenuItemFactoryInterface *iface) +{ + iface->create_menu_item = ido_menu_item_factory_create_menu_item; +} + +static void +ido_menu_item_factory_init (IdoMenuItemFactory *factory) +{ +} diff --git a/src/idomessagedialog.c b/src/idomessagedialog.c index 41ff2e7..f2c2e93 100644 --- a/src/idomessagedialog.c +++ b/src/idomessagedialog.c @@ -258,7 +258,7 @@ ido_message_dialog_init (IdoMessageDialog *dialog) * dialog it will expand to provide the secondary message * and the action buttons. * - * Return value: a new #IdoMessageDialog + * Return Value: a new #IdoMessageDialog **/ GtkWidget* ido_message_dialog_new (GtkWindow *parent, @@ -305,6 +305,25 @@ ido_message_dialog_new (GtkWindow *parent, return widget; } +/** + * ido_message_dialog_new_with_markup: + * @parent: transient parent, or %NULL for none + * @flags: flags + * @type: type of message + * @buttons: a set of buttons to use + * @message_format: printf()-style format string, or %NULL + * @Varargs: arguments for @message_format. They will be escaped to allow valid XML. + * + * Creates a new message dialog, which is based upon + * GtkMessageDialog so it shares API and functionality + * with it. IdoMessageDialog differs in that it has two + * states. The initial state hides the action buttons + * and the secondary message. When a user clicks on the + * dialog it will expand to provide the secondary message + * and the action buttons. + * + * Return Value: a new #IdoMessageDialog + **/ GtkWidget* ido_message_dialog_new_with_markup (GtkWindow *parent, GtkDialogFlags flags, diff --git a/src/idomessagedialog.h b/src/idomessagedialog.h index e11cd59..4313fb3 100644 --- a/src/idomessagedialog.h +++ b/src/idomessagedialog.h @@ -29,8 +29,6 @@ #ifndef __IDO_MESSAGE_DIALOG_H__ #define __IDO_MESSAGE_DIALOG_H__ -G_BEGIN_DECLS - #include <gtk/gtk.h> #define IDO_TYPE_MESSAGE_DIALOG (ido_message_dialog_get_type ()) diff --git a/src/idoplaybackmenuitem.c b/src/idoplaybackmenuitem.c new file mode 100644 index 0000000..662ceaf --- /dev/null +++ b/src/idoplaybackmenuitem.c @@ -0,0 +1,1680 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Conor Curran <conor.curran@canonical.com> + * Mirco Müller <mirco.mueller@canonical.com> + * Andrea Cimitan <andrea.cimitan@canonical.com> + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include "config.h" + +#include "idoplaybackmenuitem.h" + +#include <gdk/gdkkeysyms.h> +#include <math.h> + +typedef enum +{ + STATE_PAUSED, + STATE_PLAYING, + STATE_LAUNCHING +} State; + +typedef enum +{ + BUTTON_NONE, + BUTTON_PREVIOUS, + BUTTON_PLAYPAUSE, + BUTTON_NEXT +} Button; + +typedef GtkMenuItemClass IdoPlaybackMenuItemClass; + +struct _IdoPlaybackMenuItem +{ + GtkMenuItem parent; + + State current_state; + Button cur_pushed_button; + Button cur_hover_button; + gboolean has_focus; + gboolean keyboard_activated; /* TRUE if the current button was activated with a key */ + + GActionGroup *action_group; + gchar *play_action; + gchar *next_action; + gchar *prev_action; +}; + +G_DEFINE_TYPE (IdoPlaybackMenuItem, ido_playback_menu_item, GTK_TYPE_MENU_ITEM); + +static gboolean ido_playback_menu_item_draw (GtkWidget* button, cairo_t *cr); + +static void +ido_playback_menu_item_dispose (GObject *object) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (object); + + if (item->action_group) + { + g_signal_handlers_disconnect_by_data (item->action_group, item); + g_clear_object (&item->action_group); + } + + G_OBJECT_CLASS (ido_playback_menu_item_parent_class)->dispose (object); +} + +static void +ido_playback_menu_item_finalize (GObject *object) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (object); + + g_free (item->play_action); + g_free (item->next_action); + g_free (item->prev_action); + + G_OBJECT_CLASS (ido_playback_menu_item_parent_class)->finalize (object); +} + +static Button +ido_playback_menu_item_get_button_at_pos (gint x, + gint y) +{ + /* 57 101 143 187 + * 5 +------+ + * 12 +-----+ +-----+ + * |prev play next| + * 40 +-----+ +-----+ + * 47 +------+ + */ + + if (x > 57 && x < 102 && y > 12 && y < 40) + return BUTTON_PREVIOUS; + + if (x > 101 && x < 143 && y > 5 && y < 47) + return BUTTON_PLAYPAUSE; + + if (x > 142 && x < 187 && y > 12 && y < 40) + return BUTTON_NEXT; + + return BUTTON_NONE; +} + +static gboolean +ido_playback_menu_item_parent_key_press_event (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + /* only listen to events when the playback menu item is selected */ + if (!self->has_focus) + return FALSE; + + switch (event->keyval) + { + case GDK_KEY_Left: + self->cur_pushed_button = BUTTON_PREVIOUS; + if (self->action_group && self->prev_action) + g_action_group_activate_action (self->action_group, self->prev_action, NULL); + break; + + case GDK_KEY_Right: + self->cur_pushed_button = BUTTON_NEXT; + if (self->action_group && self->next_action) + g_action_group_activate_action (self->action_group, self->next_action, NULL); + break; + + case GDK_KEY_space: + self->cur_pushed_button = BUTTON_PLAYPAUSE; + if (self->action_group && self->play_action) + g_action_group_activate_action (self->action_group, self->play_action, NULL); + break; + + default: + self->cur_pushed_button = BUTTON_NONE; + } + + if (self->cur_pushed_button != BUTTON_NONE) + { + self->keyboard_activated = TRUE; + gtk_widget_queue_draw (widget); + return TRUE; + } + + return FALSE; +} + +static gboolean +ido_playback_menu_item_parent_key_release_event (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + switch (event->keyval) + { + case GDK_KEY_Left: + case GDK_KEY_Right: + case GDK_KEY_space: + self->cur_pushed_button = BUTTON_NONE; + self->keyboard_activated = FALSE; + gtk_widget_queue_draw (widget); + return TRUE; + } + + return FALSE; +} + +static void +ido_playback_menu_item_parent_set (GtkWidget *widget, + GtkWidget *old_parent) +{ + GtkWidget *parent; + + /* Menus don't pass key events to their children. This works around + * that by listening to key events on the parent widget. */ + + if (old_parent) + { + g_signal_handlers_disconnect_by_func (old_parent, ido_playback_menu_item_parent_key_press_event, widget); + g_signal_handlers_disconnect_by_func (old_parent, ido_playback_menu_item_parent_key_release_event, widget); + } + + parent = gtk_widget_get_parent (widget); + if (parent) + { + g_signal_connect (parent, "key-press-event", + G_CALLBACK (ido_playback_menu_item_parent_key_press_event), widget); + g_signal_connect (parent, "key-release-event", + G_CALLBACK (ido_playback_menu_item_parent_key_release_event), widget); + } +} + +static void +ido_playback_menu_item_select (GtkMenuItem *item) +{ + IdoPlaybackMenuItem *self = IDO_PLAYBACK_MENU_ITEM (item); + + self->has_focus = TRUE; + + GTK_MENU_ITEM_CLASS (ido_playback_menu_item_parent_class)->select (item); +} + +static void +ido_playback_menu_item_deselect (GtkMenuItem *item) +{ + IdoPlaybackMenuItem *self = IDO_PLAYBACK_MENU_ITEM (item); + + self->has_focus = FALSE; + + GTK_MENU_ITEM_CLASS (ido_playback_menu_item_parent_class)->deselect (item); +} + +static gboolean +ido_playback_menu_item_button_press_event (GtkWidget *menuitem, + GdkEventButton *event) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); + + item->cur_pushed_button = ido_playback_menu_item_get_button_at_pos (event->x, event->y); + gtk_widget_queue_draw (menuitem); + + return TRUE; +} + +static gboolean +ido_playback_menu_item_button_release_event (GtkWidget *menuitem, + GdkEventButton *event) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); + Button button; + + button = ido_playback_menu_item_get_button_at_pos (event->x, event->y); + if (button != item->cur_pushed_button) + button = BUTTON_NONE; + + switch (button) + { + case BUTTON_NONE: + break; + + case BUTTON_PREVIOUS: + if (item->action_group && item->prev_action) + g_action_group_activate_action (item->action_group, item->prev_action, NULL); + break; + + case BUTTON_NEXT: + if (item->action_group && item->next_action) + g_action_group_activate_action (item->action_group, item->next_action, NULL); + break; + + case BUTTON_PLAYPAUSE: + if (item->action_group && item->play_action) + g_action_group_activate_action (item->action_group, item->play_action, NULL); + break; + } + + item->cur_pushed_button = BUTTON_NONE; + gtk_widget_queue_draw (menuitem); + + return TRUE; +} + +static gboolean +ido_playback_menu_item_motion_notify_event (GtkWidget *menuitem, + GdkEventMotion *event) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); + + item->cur_hover_button = ido_playback_menu_item_get_button_at_pos (event->x, event->y); + gtk_widget_queue_draw (menuitem); + + return TRUE; +} + +static gboolean +ido_playback_menu_item_leave_notify_event (GtkWidget *menuitem, + GdkEventCrossing *event) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (menuitem); + + item->cur_pushed_button = BUTTON_NONE; + item->cur_hover_button = BUTTON_NONE; + gtk_widget_queue_draw (GTK_WIDGET(menuitem)); + + return TRUE; +} + +static void +ido_playback_menu_item_class_init (IdoPlaybackMenuItemClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GtkMenuItemClass *menuitem_class = GTK_MENU_ITEM_CLASS (klass); + + gobject_class->dispose = ido_playback_menu_item_dispose; + gobject_class->finalize = ido_playback_menu_item_finalize; + + widget_class->button_press_event = ido_playback_menu_item_button_press_event; + widget_class->button_release_event = ido_playback_menu_item_button_release_event; + widget_class->motion_notify_event = ido_playback_menu_item_motion_notify_event; + widget_class->leave_notify_event = ido_playback_menu_item_leave_notify_event; + widget_class->parent_set = ido_playback_menu_item_parent_set; + widget_class->draw = ido_playback_menu_item_draw; + + menuitem_class->select = ido_playback_menu_item_select; + menuitem_class->deselect = ido_playback_menu_item_deselect; +} + +static void +ido_playback_menu_item_init (IdoPlaybackMenuItem *self) +{ + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)), GTK_STYLE_CLASS_SPINNER); + + gtk_widget_set_size_request (GTK_WIDGET (self), 200, 43); +} + +static void +ido_playback_menu_item_set_state_from_string (IdoPlaybackMenuItem *self, + const gchar *state) +{ + g_return_if_fail (state != NULL); + + if (g_str_equal (state, "Playing")) + self->current_state = STATE_PLAYING; + else if (g_str_equal (state, "Launching")) + self->current_state = STATE_LAUNCHING; + else /* "Paused" and fallback */ + self->current_state = STATE_PAUSED; + + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +static void +ido_playback_menu_item_action_added (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + if (self->play_action && g_str_equal (action_name, self->play_action)) + { + GVariant *state; + + state = g_action_group_get_action_state (action_group, self->play_action); + if (g_variant_is_of_type (state, G_VARIANT_TYPE_STRING)) + ido_playback_menu_item_set_state_from_string (self, g_variant_get_string (state, NULL)); + + g_variant_unref (state); + } +} + +static void +ido_playback_menu_item_action_removed (GActionGroup *action_group, + const gchar *action_name, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + if (self->play_action && g_str_equal (action_name, self->play_action)) + { + self->current_state = STATE_PAUSED; + gtk_widget_queue_draw (GTK_WIDGET (self)); + } +} + +static void +ido_playback_menu_item_action_state_changed (GActionGroup *action_group, + const gchar *action_name, + GVariant *value, + gpointer user_data) +{ + IdoPlaybackMenuItem *self = user_data; + + g_return_if_fail (action_name != NULL); + + if (self->play_action && g_str_equal (action_name, self->play_action)) + { + if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) + ido_playback_menu_item_set_state_from_string (self, g_variant_get_string (value, NULL)); + } +} + +GtkMenuItem * +ido_playback_menu_item_new_from_model (GMenuItem *item, + GActionGroup *actions) +{ + IdoPlaybackMenuItem *widget; + + widget = g_object_new (IDO_TYPE_PLAYBACK_MENU_ITEM, NULL); + + widget->action_group = g_object_ref (actions); + g_signal_connect (actions, "action-state-changed", G_CALLBACK (ido_playback_menu_item_action_state_changed), widget); + g_signal_connect (actions, "action-added", G_CALLBACK (ido_playback_menu_item_action_added), widget); + g_signal_connect (actions, "action-removed", G_CALLBACK (ido_playback_menu_item_action_removed), widget); + + g_menu_item_get_attribute (item, "x-canonical-play-action", "s", &widget->play_action); + g_menu_item_get_attribute (item, "x-canonical-next-action", "s", &widget->next_action); + g_menu_item_get_attribute (item, "x-canonical-previous-action", "s", &widget->prev_action); + + if (widget->play_action && g_action_group_has_action (actions, widget->play_action)) + ido_playback_menu_item_action_added (actions, widget->play_action, widget); + + return GTK_MENU_ITEM (widget); +} + + +/* + * Drawing + */ + +#define RECT_WIDTH 130.0f +#define Y 7.0f +#define X 70.0f +#define INNER_RADIUS 12.5 +#define MIDDLE_RADIUS 13.0f +#define OUTER_RADIUS 14.5f +#define CIRCLE_RADIUS 21.0f +#define PREV_WIDTH 25.0f +#define PREV_HEIGHT 17.0f +#define NEXT_WIDTH 25.0f //PREV_WIDTH +#define NEXT_HEIGHT 17.0f //PREV_HEIGHT +#define TRI_WIDTH 11.0f +#define TRI_HEIGHT 13.0f +#define TRI_OFFSET 6.0f +#define PREV_X 68.0f +#define PREV_Y 13.0f +#define NEXT_X 146.0f +#define NEXT_Y 13.0f //prev_y +#define PAUSE_WIDTH 21.0f +#define PAUSE_HEIGHT 27.0f +#define BAR_WIDTH 4.5f +#define BAR_HEIGHT 24.0f +#define BAR_OFFSET 10.0f +#define PAUSE_X 111.0f +#define PAUSE_Y 7.0f +#define PLAY_WIDTH 28.0f +#define PLAY_HEIGHT 29.0f +#define PLAY_PADDING 5.0f +#define INNER_START_SHADE 0.98 +#define INNER_END_SHADE 0.98 +#define MIDDLE_START_SHADE 1.0 +#define MIDDLE_END_SHADE 1.0 +#define OUTER_START_SHADE 0.75 +#define OUTER_END_SHADE 1.3 +#define SHADOW_BUTTON_SHADE 0.8 +#define OUTER_PLAY_START_SHADE 0.7 +#define OUTER_PLAY_END_SHADE 1.38 +#define BUTTON_START_SHADE 1.1 +#define BUTTON_END_SHADE 0.9 +#define BUTTON_SHADOW_SHADE 0.8 +#define INNER_COMPRESSED_START_SHADE 1.0 +#define INNER_COMPRESSED_END_SHADE 1.0 + +typedef struct +{ + double r; + double g; + double b; +} CairoColorRGB; + +static void +draw_gradient (cairo_t* cr, + double x, + double y, + double w, + double r, + double* rgba_start, + double* rgba_end) +{ + cairo_pattern_t* pattern = NULL; + + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + w - 2.0f * r, y); + cairo_arc (cr, + x + w - 2.0f * r, + y + r, + r, + -90.0f * G_PI / 180.0f, + 90.0f * G_PI / 180.0f); + cairo_line_to (cr, x, y + 2.0f * r); + cairo_arc (cr, + x, + y + r, + r, + 90.0f * G_PI / 180.0f, + 270.0f * G_PI / 180.0f); + cairo_close_path (cr); + + pattern = cairo_pattern_create_linear (x, y, x, y + 2.0f * r); + cairo_pattern_add_color_stop_rgba (pattern, + 0.0f, + rgba_start[0], + rgba_start[1], + rgba_start[2], + rgba_start[3]); + cairo_pattern_add_color_stop_rgba (pattern, + 1.0f, + rgba_end[0], + rgba_end[1], + rgba_end[2], + rgba_end[3]); + cairo_set_source (cr, pattern); + cairo_fill (cr); + cairo_pattern_destroy (pattern); +} + +static void +draw_circle (cairo_t* cr, + double x, + double y, + double r, + double* rgba_start, + double* rgba_end) +{ + cairo_pattern_t* pattern = NULL; + + cairo_move_to (cr, x, y); + cairo_arc (cr, + x + r, + y + r, + r, + 0.0f * G_PI / 180.0f, + 360.0f * G_PI / 180.0f); + + pattern = cairo_pattern_create_linear (x, y, x, y + 2.0f * r); + cairo_pattern_add_color_stop_rgba (pattern, + 0.0f, + rgba_start[0], + rgba_start[1], + rgba_start[2], + rgba_start[3]); + cairo_pattern_add_color_stop_rgba (pattern, + 1.0f, + rgba_end[0], + rgba_end[1], + rgba_end[2], + rgba_end[3]); + cairo_set_source (cr, pattern); + cairo_fill (cr); + cairo_pattern_destroy (pattern); +} + +static void +_setup (cairo_t** cr, + cairo_surface_t** surf, + gint width, + gint height) +{ + if (!cr || !surf) + return; + + *surf = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height); + *cr = cairo_create (*surf); + cairo_scale (*cr, 1.0f, 1.0f); + cairo_set_operator (*cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (*cr); + cairo_set_operator (*cr, CAIRO_OPERATOR_OVER); +} + +static void +_mask_prev (cairo_t* cr, + double x, + double y, + double tri_width, + double tri_height, + double tri_offset) +{ + if (!cr) + return; + + cairo_move_to (cr, x, y + tri_height / 2.0f); + cairo_line_to (cr, x + tri_width, y); + cairo_line_to (cr, x + tri_width, y + tri_height); + x += tri_offset; + cairo_move_to (cr, x, y + tri_height / 2.0f); + cairo_line_to (cr, x + tri_width, y); + cairo_line_to (cr, x + tri_width, y + tri_height); + x -= tri_offset; + cairo_rectangle (cr, x, y, 2.5f, tri_height); + cairo_close_path (cr); +} + +static void +_mask_next (cairo_t* cr, + double x, + double y, + double tri_width, + double tri_height, + double tri_offset) +{ + if (!cr) + return; + + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f); + cairo_line_to (cr, x, y + tri_height); + x += tri_offset; + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f); + cairo_line_to (cr, x, y + tri_height); + x -= tri_offset; + x += 2.0f * tri_width - tri_offset - 1.0f; + cairo_rectangle (cr, x, y, 2.5f, tri_height); + + cairo_close_path (cr); +} + +static void +_mask_pause (cairo_t* cr, + double x, + double y, + double bar_width, + double bar_height, + double bar_offset) +{ + if (!cr) + return; + + cairo_set_line_width (cr, bar_width); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + + x += bar_width; + y += bar_width; + cairo_move_to (cr, x, y); + cairo_line_to (cr, x, y + bar_height); + cairo_move_to (cr, x + bar_offset, y); + cairo_line_to (cr, x + bar_offset, y + bar_height); + +} + +static void +_mask_play (cairo_t* cr, + double x, + double y, + double tri_width, + double tri_height) +{ + if (!cr) + return; + + cairo_move_to (cr, x, y); + cairo_line_to (cr, x + tri_width, y + tri_height / 2.0f); + cairo_line_to (cr, x, y + tri_height); + cairo_close_path (cr); + +} + +static void +_fill (cairo_t* cr, + double x_start, + double y_start, + double x_end, + double y_end, + double* rgba_start, + double* rgba_end, + gboolean stroke) +{ + cairo_pattern_t* pattern = NULL; + + if (!cr || !rgba_start || !rgba_end) + return; + + pattern = cairo_pattern_create_linear (x_start, y_start, x_end, y_end); + cairo_pattern_add_color_stop_rgba (pattern, + 0.0f, + rgba_start[0], + rgba_start[1], + rgba_start[2], + rgba_start[3]); + cairo_pattern_add_color_stop_rgba (pattern, + 1.0f, + rgba_end[0], + rgba_end[1], + rgba_end[2], + rgba_end[3]); + cairo_set_source (cr, pattern); + if (stroke) + cairo_stroke (cr); + else + cairo_fill (cr); + cairo_pattern_destroy (pattern); +} + +static void +_finalize (cairo_t* cr, + cairo_t** cr_surf, + cairo_surface_t** surf, + double x, + double y) +{ + if (!cr || !cr_surf || !surf) + return; + + cairo_set_source_surface (cr, *surf, x, y); + cairo_paint (cr); + cairo_surface_destroy (*surf); + cairo_destroy (*cr_surf); +} + +static void +_finalize_repaint (cairo_t* cr, + cairo_t** cr_surf, + cairo_surface_t** surf, + double x, + double y, + int repaints) +{ + if (!cr || !cr_surf || !surf) + return; + + while (repaints > 0) + { + cairo_set_source_surface (cr, *surf, x, y); + cairo_paint (cr); + repaints--; + } + + cairo_surface_destroy (*surf); + cairo_destroy (*cr_surf); +} + +static void +_color_rgb_to_hls (gdouble *r, + gdouble *g, + gdouble *b) +{ + gdouble min; + gdouble max; + gdouble red; + gdouble green; + gdouble blue; + gdouble h = 0; + gdouble l; + gdouble s; + gdouble delta; + + red = *r; + green = *g; + blue = *b; + + if (red > green) + { + if (red > blue) + max = red; + else + max = blue; + + if (green < blue) + min = green; + else + min = blue; + } + else + { + if (green > blue) + max = green; + else + max = blue; + + if (red < blue) + min = red; + else + min = blue; + } + l = (max+min)/2; + if (fabs (max-min) < 0.0001) + { + h = 0; + s = 0; + } + else + { + if (l <= 0.5) + s = (max-min)/(max+min); + else + s = (max-min)/(2-max-min); + + delta = (max -min) != 0 ? (max -min) : 1; + + if(delta == 0) + delta = 1; + if (red == max) + h = (green-blue)/delta; + else if (green == max) + h = 2+(blue-red)/delta; + else if (blue == max) + h = 4+(red-green)/delta; + + h *= 60; + if (h < 0.0) + h += 360; + } + + *r = h; + *g = l; + *b = s; +} + +static void +_color_hls_to_rgb (gdouble *h, + gdouble *l, + gdouble *s) +{ + gdouble hue; + gdouble lightness; + gdouble saturation; + gdouble m1, m2; + gdouble r, g, b; + + lightness = *l; + saturation = *s; + + if (lightness <= 0.5) + m2 = lightness*(1+saturation); + else + m2 = lightness+saturation-lightness*saturation; + + m1 = 2*lightness-m2; + + if (saturation == 0) + { + *h = lightness; + *l = lightness; + *s = lightness; + } + else + { + hue = *h+120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + r = m1+(m2-m1)*hue/60; + else if (hue < 180) + r = m2; + else if (hue < 240) + r = m1+(m2-m1)*(240-hue)/60; + else + r = m1; + + hue = *h; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + g = m1+(m2-m1)*hue/60; + else if (hue < 180) + g = m2; + else if (hue < 240) + g = m1+(m2-m1)*(240-hue)/60; + else + g = m1; + + hue = *h-120; + while (hue > 360) + hue -= 360; + while (hue < 0) + hue += 360; + + if (hue < 60) + b = m1+(m2-m1)*hue/60; + else if (hue < 180) + b = m2; + else if (hue < 240) + b = m1+(m2-m1)*(240-hue)/60; + else + b = m1; + + *h = r; + *l = g; + *s = b; + } +} + +static void +_color_shade (const CairoColorRGB *a, float k, CairoColorRGB *b) +{ + double red; + double green; + double blue; + + red = a->r; + green = a->g; + blue = a->b; + + if (k == 1.0) + { + b->r = red; + b->g = green; + b->b = blue; + return; + } + + _color_rgb_to_hls (&red, &green, &blue); + + green *= k; + if (green > 1.0) + green = 1.0; + else if (green < 0.0) + green = 0.0; + + blue *= k; + if (blue > 1.0) + blue = 1.0; + else if (blue < 0.0) + blue = 0.0; + + _color_hls_to_rgb (&red, &green, &blue); + + b->r = red; + b->g = green; + b->b = blue; +} + +static inline void +_blurinner (guchar* pixel, + gint* zR, + gint* zG, + gint* zB, + gint* zA, + gint alpha, + gint aprec, + gint zprec) +{ + gint R; + gint G; + gint B; + guchar A; + + R = *pixel; + G = *(pixel + 1); + B = *(pixel + 2); + A = *(pixel + 3); + + *zR += (alpha * ((R << zprec) - *zR)) >> aprec; + *zG += (alpha * ((G << zprec) - *zG)) >> aprec; + *zB += (alpha * ((B << zprec) - *zB)) >> aprec; + *zA += (alpha * ((A << zprec) - *zA)) >> aprec; + + *pixel = *zR >> zprec; + *(pixel + 1) = *zG >> zprec; + *(pixel + 2) = *zB >> zprec; + *(pixel + 3) = *zA >> zprec; +} + +static inline void +_blurrow (guchar* pixels, + gint width, + gint height, + gint channels, + gint line, + gint alpha, + gint aprec, + gint zprec) +{ + gint zR; + gint zG; + gint zB; + gint zA; + gint index; + guchar* scanline; + + scanline = &(pixels[line * width * channels]); + + zR = *scanline << zprec; + zG = *(scanline + 1) << zprec; + zB = *(scanline + 2) << zprec; + zA = *(scanline + 3) << zprec; + + for (index = 0; index < width; index ++) + _blurinner (&scanline[index * channels], + &zR, + &zG, + &zB, + &zA, + alpha, + aprec, + zprec); + + for (index = width - 2; index >= 0; index--) + _blurinner (&scanline[index * channels], + &zR, + &zG, + &zB, + &zA, + alpha, + aprec, + zprec); +} + +static inline void +_blurcol (guchar* pixels, + gint width, + gint height, + gint channels, + gint x, + gint alpha, + gint aprec, + gint zprec) +{ + gint zR; + gint zG; + gint zB; + gint zA; + gint index; + guchar* ptr; + + ptr = pixels; + + ptr += x * channels; + + zR = *((guchar*) ptr ) << zprec; + zG = *((guchar*) ptr + 1) << zprec; + zB = *((guchar*) ptr + 2) << zprec; + zA = *((guchar*) ptr + 3) << zprec; + + for (index = width; index < (height - 1) * width; index += width) + _blurinner ((guchar*) &ptr[index * channels], + &zR, + &zG, + &zB, + &zA, + alpha, + aprec, + zprec); + + for (index = (height - 2) * width; index >= 0; index -= width) + _blurinner ((guchar*) &ptr[index * channels], + &zR, + &zG, + &zB, + &zA, + alpha, + aprec, + zprec); +} + +static void +_expblur (guchar* pixels, + gint width, + gint height, + gint channels, + gint radius, + gint aprec, + gint zprec) +{ + gint alpha; + gint row = 0; + gint col = 0; + + if (radius < 1) + return; + + // calculate the alpha such that 90% of + // the kernel is within the radius. + // (Kernel extends to infinity) + alpha = (gint) ((1 << aprec) * (1.0f - expf (-2.3f / (radius + 1.f)))); + + for (; row < height; row++) + _blurrow (pixels, + width, + height, + channels, + row, + alpha, + aprec, + zprec); + + for(; col < width; col++) + _blurcol (pixels, + width, + height, + channels, + col, + alpha, + aprec, + zprec); + + return; +} + +static void +_surface_blur (cairo_surface_t* surface, + guint radius) +{ + guchar* pixels; + guint width; + guint height; + cairo_format_t format; + + // before we mess with the surface execute any pending drawing + cairo_surface_flush (surface); + + pixels = cairo_image_surface_get_data (surface); + width = cairo_image_surface_get_width (surface); + height = cairo_image_surface_get_height (surface); + format = cairo_image_surface_get_format (surface); + + switch (format) + { + case CAIRO_FORMAT_ARGB32: + _expblur (pixels, width, height, 4, radius, 16, 7); + break; + + case CAIRO_FORMAT_RGB24: + _expblur (pixels, width, height, 3, radius, 16, 7); + break; + + case CAIRO_FORMAT_A8: + _expblur (pixels, width, height, 1, radius, 16, 7); + break; + + default : + // do nothing + break; + } + + // inform cairo we altered the surfaces contents + cairo_surface_mark_dirty (surface); +} + +static gboolean +ido_playback_menu_item_draw (GtkWidget* button, cairo_t *cr) +{ + IdoPlaybackMenuItem *item = IDO_PLAYBACK_MENU_ITEM (button); + + g_return_val_if_fail(IDO_IS_PLAYBACK_MENU_ITEM (button), FALSE); + g_return_val_if_fail(cr != NULL, FALSE); + + cairo_surface_t* surf = NULL; + cairo_t* cr_surf = NULL; + + GtkStyle *style; + + gtk_style_context_add_class (gtk_widget_get_style_context (button), + GTK_STYLE_CLASS_MENU); + CairoColorRGB bg_color, fg_color, bg_selected, bg_prelight; + CairoColorRGB color_middle[2], color_middle_prelight[2], color_outer[2], color_outer_prelight[2], + color_play_outer[2], color_play_outer_prelight[2], + color_button[4], color_button_shadow, color_inner[2], color_inner_compressed[2]; + + /* Use the menu's style instead of that of the menuitem ('button' is a + * menuitem that is packed in a menu directly). The menuitem's style + * can't be used due to a change in light-themes (lp #1130183). + * Menuitems now have a transparent background, which confuses + * GtkStyle. + * + * This is a workaround until this code gets refactored to use + * GtkStyleContext. + */ + style = gtk_widget_get_style (gtk_widget_get_parent (button)); + + bg_color.r = style->bg[0].red/65535.0; + bg_color.g = style->bg[0].green/65535.0; + bg_color.b = style->bg[0].blue/65535.0; + + bg_prelight.r = style->bg[GTK_STATE_PRELIGHT].red/65535.0; + bg_prelight.g = style->bg[GTK_STATE_PRELIGHT].green/65535.0; + bg_prelight.b = style->bg[GTK_STATE_PRELIGHT].blue/65535.0; + + bg_selected.r = style->bg[GTK_STATE_SELECTED].red/65535.0; + bg_selected.g = style->bg[GTK_STATE_SELECTED].green/65535.0; + bg_selected.b = style->bg[GTK_STATE_SELECTED].blue/65535.0; + + fg_color.r = style->fg[0].red/65535.0; + fg_color.g = style->fg[0].green/65535.0; + fg_color.b = style->fg[0].blue/65535.0; + + _color_shade (&bg_color, MIDDLE_START_SHADE, &color_middle[0]); + _color_shade (&bg_color, MIDDLE_END_SHADE, &color_middle[1]); + _color_shade (&bg_prelight, MIDDLE_START_SHADE, &color_middle_prelight[0]); + _color_shade (&bg_prelight, MIDDLE_END_SHADE, &color_middle_prelight[1]); + _color_shade (&bg_color, OUTER_START_SHADE, &color_outer[0]); + _color_shade (&bg_color, OUTER_END_SHADE, &color_outer[1]); + _color_shade (&bg_prelight, OUTER_START_SHADE, &color_outer_prelight[0]); + _color_shade (&bg_prelight, OUTER_END_SHADE, &color_outer_prelight[1]); + _color_shade (&bg_color, OUTER_PLAY_START_SHADE, &color_play_outer[0]); + _color_shade (&bg_color, OUTER_PLAY_END_SHADE, &color_play_outer[1]); + _color_shade (&bg_prelight, OUTER_PLAY_START_SHADE, &color_play_outer_prelight[0]); + _color_shade (&bg_prelight, OUTER_PLAY_END_SHADE, &color_play_outer_prelight[1]); + _color_shade (&bg_color, INNER_START_SHADE, &color_inner[0]); + _color_shade (&bg_color, INNER_END_SHADE, &color_inner[1]); + _color_shade (&fg_color, BUTTON_START_SHADE, &color_button[0]); + _color_shade (&fg_color, BUTTON_END_SHADE, &color_button[1]); + _color_shade (&bg_color, BUTTON_SHADOW_SHADE, &color_button[2]); + _color_shade (&bg_color, SHADOW_BUTTON_SHADE, &color_button_shadow); + _color_shade (&bg_selected, 1.0, &color_button[3]); + _color_shade (&bg_color, INNER_COMPRESSED_START_SHADE, &color_inner_compressed[0]); + _color_shade (&bg_color, INNER_COMPRESSED_END_SHADE, &color_inner_compressed[1]); + + double MIDDLE_END[] = {color_middle[0].r, color_middle[0].g, color_middle[0].b, 1.0f}; + double MIDDLE_START[] = {color_middle[1].r, color_middle[1].g, color_middle[1].b, 1.0f}; + double MIDDLE_END_PRELIGHT[] = {color_middle_prelight[0].r, color_middle_prelight[0].g, color_middle_prelight[0].b, 1.0f}; + double MIDDLE_START_PRELIGHT[] = {color_middle_prelight[1].r, color_middle_prelight[1].g, color_middle_prelight[1].b, 1.0f}; + double OUTER_END[] = {color_outer[0].r, color_outer[0].g, color_outer[0].b, 1.0f}; + double OUTER_START[] = {color_outer[1].r, color_outer[1].g, color_outer[1].b, 1.0f}; + double OUTER_END_PRELIGHT[] = {color_outer_prelight[0].r, color_outer_prelight[0].g, color_outer_prelight[0].b, 1.0f}; + double OUTER_START_PRELIGHT[] = {color_outer_prelight[1].r, color_outer_prelight[1].g, color_outer_prelight[1].b, 1.0f}; + double SHADOW_BUTTON[] = {color_button_shadow.r, color_button_shadow.g, color_button_shadow.b, 0.3f}; + double OUTER_PLAY_END[] = {color_play_outer[0].r, color_play_outer[0].g, color_play_outer[0].b, 1.0f}; + double OUTER_PLAY_START[] = {color_play_outer[1].r, color_play_outer[1].g, color_play_outer[1].b, 1.0f}; + double OUTER_PLAY_END_PRELIGHT[] = {color_play_outer_prelight[0].r, color_play_outer_prelight[0].g, color_play_outer_prelight[0].b, 1.0f}; + double OUTER_PLAY_START_PRELIGHT[] = {color_play_outer_prelight[1].r, color_play_outer_prelight[1].g, color_play_outer_prelight[1].b, 1.0f}; + double BUTTON_END[] = {color_button[0].r, color_button[0].g, color_button[0].b, 1.0f}; + double BUTTON_START[] = {color_button[1].r, color_button[1].g, color_button[1].b, 1.0f}; + double BUTTON_SHADOW[] = {color_button[2].r, color_button[2].g, color_button[2].b, 0.75f}; + double BUTTON_SHADOW_FOCUS[] = {color_button[3].r, color_button[3].g, color_button[3].b, 1.0f}; + double INNER_COMPRESSED_END[] = {color_inner_compressed[1].r, color_inner_compressed[1].g, color_inner_compressed[1].b, 1.0f}; + double INNER_COMPRESSED_START[] = {color_inner_compressed[0].r, color_inner_compressed[0].g, color_inner_compressed[0].b, 1.0f}; + + + draw_gradient (cr, + X, + Y, + RECT_WIDTH, + OUTER_RADIUS, + OUTER_START, + OUTER_END); + + draw_gradient (cr, + X, + Y + 1, + RECT_WIDTH - 2, + MIDDLE_RADIUS, + MIDDLE_START, + MIDDLE_END); + + draw_gradient (cr, + X, + Y + 2, + RECT_WIDTH - 4, + MIDDLE_RADIUS, + MIDDLE_START, + MIDDLE_END); + + + if(item->cur_pushed_button == BUTTON_PREVIOUS) + { + draw_gradient (cr, + X, + Y, + RECT_WIDTH/2, + OUTER_RADIUS, + OUTER_END, + OUTER_START); + + draw_gradient (cr, + X, + Y + 1, + RECT_WIDTH/2, + MIDDLE_RADIUS, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + + draw_gradient (cr, + X, + Y + 2, + RECT_WIDTH/2, + MIDDLE_RADIUS, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + } + else if(item->cur_pushed_button == BUTTON_NEXT) + { + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y, + RECT_WIDTH/2, + OUTER_RADIUS, + OUTER_END, + OUTER_START); + + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y + 1, + (RECT_WIDTH - 4.5)/2, + MIDDLE_RADIUS, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y + 2, + (RECT_WIDTH - 7)/2, + MIDDLE_RADIUS, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + } + else if (item->cur_hover_button == BUTTON_PREVIOUS) + { + draw_gradient (cr, + X, + Y, + RECT_WIDTH/2, + OUTER_RADIUS, + OUTER_START_PRELIGHT, + OUTER_END_PRELIGHT); + + draw_gradient (cr, + X, + Y + 1, + RECT_WIDTH/2, + MIDDLE_RADIUS, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + + draw_gradient (cr, + X, + Y + 2, + RECT_WIDTH/2, + MIDDLE_RADIUS, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + } + else if (item->cur_hover_button == BUTTON_NEXT) + { + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y, + RECT_WIDTH/2, + OUTER_RADIUS, + OUTER_START_PRELIGHT, + OUTER_END_PRELIGHT); + + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y + 1, + (RECT_WIDTH - 4.5)/2, + MIDDLE_RADIUS, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + + draw_gradient (cr, + RECT_WIDTH / 2 + X, + Y + 2, + (RECT_WIDTH - 7)/2, + MIDDLE_RADIUS, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + } + + // play/pause shadow + if(item->cur_pushed_button != BUTTON_PLAYPAUSE) + { + cairo_save (cr); + cairo_rectangle (cr, X, Y, RECT_WIDTH, MIDDLE_RADIUS*2); + cairo_clip (cr); + + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f - 1.0f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) - 1.0f, + CIRCLE_RADIUS + 1.0f, + SHADOW_BUTTON, + SHADOW_BUTTON); + + cairo_restore (cr); + } + + // play/pause button + if(item->cur_pushed_button == BUTTON_PLAYPAUSE) + { + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) , + CIRCLE_RADIUS, + OUTER_PLAY_END, + OUTER_PLAY_START); + + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f, + CIRCLE_RADIUS - 1.25, + INNER_COMPRESSED_START, + INNER_COMPRESSED_END); + } + else if (item->cur_hover_button == BUTTON_PLAYPAUSE) + { + /* this subtle offset is to fix alpha borders, should be removed once this draw routine will be refactored */ + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 0.1, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 0.1, + CIRCLE_RADIUS - 0.1, + OUTER_PLAY_START_PRELIGHT, + OUTER_PLAY_END_PRELIGHT); + + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f, + CIRCLE_RADIUS - 1.25, + MIDDLE_START_PRELIGHT, + MIDDLE_END_PRELIGHT); + } + else + { + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)), + CIRCLE_RADIUS, + OUTER_PLAY_START, + OUTER_PLAY_END); + + draw_circle (cr, + X + RECT_WIDTH / 2.0f - 2.0f * OUTER_RADIUS - 5.5f + 1.25f, + Y - ((CIRCLE_RADIUS - OUTER_RADIUS)) + 1.25f, + CIRCLE_RADIUS - 1.25, + MIDDLE_START, + MIDDLE_END); + } + + // draw previous-button drop-shadow + if (item->cur_pushed_button == BUTTON_PREVIOUS && item->keyboard_activated ) + { + _setup (&cr_surf, &surf, PREV_WIDTH+6, PREV_HEIGHT+6); + _mask_prev (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_SHADOW_FOCUS, + BUTTON_SHADOW_FOCUS, + FALSE); + _surface_blur (surf, 3); + _finalize_repaint (cr, &cr_surf, &surf, PREV_X, PREV_Y + 0.5f, 3); + } + else + { + _setup (&cr_surf, &surf, PREV_WIDTH, PREV_HEIGHT); + _mask_prev (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_SHADOW, + BUTTON_SHADOW, + FALSE); + _surface_blur (surf, 1); + _finalize (cr, &cr_surf, &surf, PREV_X, PREV_Y + 1.0f); + } + + // draw previous-button + _setup (&cr_surf, &surf, PREV_WIDTH, PREV_HEIGHT); + _mask_prev (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (PREV_HEIGHT - TRI_HEIGHT) / 2.0f, + (PREV_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_START, + BUTTON_END, + FALSE); + _finalize (cr, &cr_surf, &surf, PREV_X, PREV_Y); + + // draw next-button drop-shadow + if (item->cur_pushed_button == BUTTON_NEXT && item->keyboard_activated) + { + _setup (&cr_surf, &surf, NEXT_WIDTH+6, NEXT_HEIGHT+6); + _mask_next (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_SHADOW_FOCUS, + BUTTON_SHADOW_FOCUS, + FALSE); + _surface_blur (surf, 3); + _finalize_repaint (cr, &cr_surf, &surf, NEXT_X, NEXT_Y + 0.5f, 3); + } + else + { + _setup (&cr_surf, &surf, NEXT_WIDTH, NEXT_HEIGHT); + _mask_next (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_SHADOW, + BUTTON_SHADOW, + FALSE); + _surface_blur (surf, 1); + _finalize (cr, &cr_surf, &surf, NEXT_X, NEXT_Y + 1.0f); + } + + // draw next-button + _setup (&cr_surf, &surf, NEXT_WIDTH, NEXT_HEIGHT); + _mask_next (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + TRI_WIDTH, + TRI_HEIGHT, + TRI_OFFSET); + _fill (cr_surf, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (NEXT_HEIGHT - TRI_HEIGHT) / 2.0f, + (NEXT_WIDTH - (2.0f * TRI_WIDTH - TRI_OFFSET)) / 2.0f, + (double) TRI_HEIGHT, + BUTTON_START, + BUTTON_END, + FALSE); + _finalize (cr, &cr_surf, &surf, NEXT_X, NEXT_Y); + + // draw pause-button drop-shadow + if (item->current_state == STATE_PLAYING) + { + if (item->has_focus && + (item->cur_pushed_button == BUTTON_NONE || item->cur_pushed_button == BUTTON_PLAYPAUSE)) + { + _setup (&cr_surf, &surf, PAUSE_WIDTH+6, PAUSE_HEIGHT+6); + _mask_pause (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + BAR_WIDTH, + BAR_HEIGHT - 2.0f * BAR_WIDTH, + BAR_OFFSET); + _fill (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (double) BAR_HEIGHT, + BUTTON_SHADOW_FOCUS, + BUTTON_SHADOW_FOCUS, + TRUE); + _surface_blur (surf, 3); + _finalize_repaint (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y + 0.5f, 3); + } + else + { + _setup (&cr_surf, &surf, PAUSE_WIDTH, PAUSE_HEIGHT); + _mask_pause (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + BAR_WIDTH, + BAR_HEIGHT - 2.0f * BAR_WIDTH, + BAR_OFFSET); + _fill (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (double) BAR_HEIGHT, + BUTTON_SHADOW, + BUTTON_SHADOW, + TRUE); + _surface_blur (surf, 1); + _finalize (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y + 1.0f); + } + + // draw pause-button + _setup (&cr_surf, &surf, PAUSE_WIDTH, PAUSE_HEIGHT); + _mask_pause (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + BAR_WIDTH, + BAR_HEIGHT - 2.0f * BAR_WIDTH, + BAR_OFFSET); + _fill (cr_surf, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (PAUSE_HEIGHT - BAR_HEIGHT) / 2.0f, + (PAUSE_WIDTH - (2.0f * BAR_WIDTH + BAR_OFFSET)) / 2.0f, + (double) BAR_HEIGHT, + BUTTON_START, + BUTTON_END, + TRUE); + _finalize (cr, &cr_surf, &surf, PAUSE_X, PAUSE_Y); + } + else if (item->current_state == STATE_PAUSED) + { + if (item->has_focus && + (item->cur_pushed_button == BUTTON_NONE || item->cur_pushed_button == BUTTON_PLAYPAUSE)) + { + _setup (&cr_surf, &surf, PLAY_WIDTH+6, PLAY_HEIGHT+6); + _mask_play (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING)); + _fill (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING), + BUTTON_SHADOW_FOCUS, + BUTTON_SHADOW_FOCUS, + FALSE); + _surface_blur (surf, 3); + _finalize_repaint (cr, &cr_surf, &surf, PAUSE_X-0.5f, PAUSE_Y + 0.5f, 3); + } + else + { + _setup (&cr_surf, &surf, PLAY_WIDTH, PLAY_HEIGHT); + _mask_play (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING)); + _fill (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING), + BUTTON_SHADOW, + BUTTON_SHADOW, + FALSE); + _surface_blur (surf, 1); + _finalize (cr, &cr_surf, &surf, PAUSE_X-0.75f, PAUSE_Y + 1.0f); + } + + // draw play-button + _setup (&cr_surf, &surf, PLAY_WIDTH, PLAY_HEIGHT); + cairo_set_line_width (cr, 10.5); + cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); + cairo_set_line_join(cr, CAIRO_LINE_JOIN_ROUND); + _mask_play (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING)); + _fill (cr_surf, + PLAY_PADDING, + PLAY_PADDING, + PLAY_WIDTH - (2*PLAY_PADDING), + PLAY_HEIGHT - (2*PLAY_PADDING), + BUTTON_START, + BUTTON_END, + FALSE); + _finalize (cr, &cr_surf, &surf, PAUSE_X-0.5f, PAUSE_Y); + } + else if (item->current_state == STATE_LAUNCHING) + { + // the spinner is not aligned, why? because the play button has odd width/height numbers + gtk_render_activity (gtk_widget_get_style_context (button), cr, 106, 6, 30, 30); + } + return FALSE; +} diff --git a/src/idoplaybackmenuitem.h b/src/idoplaybackmenuitem.h new file mode 100644 index 0000000..8b6d45a --- /dev/null +++ b/src/idoplaybackmenuitem.h @@ -0,0 +1,37 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Conor Curran <conor.curran@canonical.com> + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#ifndef __IDO_PLAYBACK_MENU_ITEM_H__ +#define __IDO_PLAYBACK_MENU_ITEM_H__ + +#include <gtk/gtk.h> + +#define IDO_TYPE_PLAYBACK_MENU_ITEM (ido_playback_menu_item_get_type ()) +#define IDO_PLAYBACK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_TYPE_PLAYBACK_MENU_ITEM, IdoPlaybackMenuItem)) +#define IDO_IS_PLAYBACK_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_TYPE_PLAYBACK_MENU_ITEM)) + +typedef struct _IdoPlaybackMenuItem IdoPlaybackMenuItem; + +GType ido_playback_menu_item_get_type (void); + +GtkMenuItem * ido_playback_menu_item_new_from_model (GMenuItem *item, + GActionGroup *actions); + +#endif diff --git a/src/idorange.c b/src/idorange.c index 3e88fd9..acdfa5d 100644 --- a/src/idorange.c +++ b/src/idorange.c @@ -172,6 +172,8 @@ ido_range_init (IdoRange *range) * @style: The range style * * Creates a new #IdoRange widget. + * + * Return Value: A new #IdoRange **/ GtkWidget * ido_range_new (GObject *adj, diff --git a/src/idoscalemenuitem.c b/src/idoscalemenuitem.c index 986d9a7..7aaf9d6 100644 --- a/src/idoscalemenuitem.c +++ b/src/idoscalemenuitem.c @@ -30,6 +30,7 @@ #include "idorange.h" #include "idoscalemenuitem.h" #include "idotypebuiltins.h" +#include "idoactionhelper.h" static void ido_scale_menu_item_set_property (GObject *object, guint prop_id, @@ -309,6 +310,12 @@ ido_scale_menu_item_class_init (IdoScaleMenuItemClass *item_class) FALSE, G_PARAM_READWRITE)); + /** + * IdoScaleMenuItem::slider-grabbed: + * @menuitem: The #IdoScaleMenuItem emitting the signal. + * + * The ::slider-grabbed signal is emitted when the pointer selects the slider. + */ signals[SLIDER_GRABBED] = g_signal_new ("slider-grabbed", G_OBJECT_CLASS_TYPE (gobject_class), G_SIGNAL_RUN_FIRST, @@ -317,6 +324,12 @@ ido_scale_menu_item_class_init (IdoScaleMenuItemClass *item_class) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + /** + * IdoScaleMenuItem::slider-released: + * @menuitem: The #IdoScaleMenuItem emitting the signal. + * + * The ::slider-released signal is emitted when the pointer releases the slider. + */ signals[SLIDER_RELEASED] = g_signal_new ("slider-released", G_OBJECT_CLASS_TYPE (gobject_class), G_SIGNAL_RUN_FIRST, @@ -325,6 +338,12 @@ ido_scale_menu_item_class_init (IdoScaleMenuItemClass *item_class) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + /** + * IdoScaleMenuItem::primary-clicked: + * @menuitem: The #IdoScaleMenuItem emitting the signal. + * + * The ::primary-clicked signal is emitted when the pointer clicks the primary label. + */ signals[PRIMARY_CLICKED] = g_signal_new ("primary-clicked", G_TYPE_FROM_CLASS (item_class), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, @@ -334,6 +353,12 @@ ido_scale_menu_item_class_init (IdoScaleMenuItemClass *item_class) G_TYPE_NONE, /* return type */ 0 /* n_params */); + /** + * IdoScaleMenuItem::secondary-clicked: + * @menuitem: The #IdoScaleMenuItem emitting the signal. + * + * The ::secondary-clicked signal is emitted when the pointer clicks the secondary label. + */ signals[SECONDARY_CLICKED] = g_signal_new ("secondary-clicked", G_TYPE_FROM_CLASS (item_class), G_SIGNAL_RUN_FIRST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, @@ -365,18 +390,18 @@ update_packing (IdoScaleMenuItem *self, IdoScaleMenuItemStyle style) { case IDO_SCALE_MENU_ITEM_STYLE_IMAGE: gtk_box_pack_start (box, priv->primary_image, FALSE, FALSE, 0); - gtk_box_pack_start (box, priv->scale, FALSE, FALSE, 0); + gtk_box_pack_start (box, priv->scale, TRUE, TRUE, 0); gtk_box_pack_start (box, priv->secondary_image, FALSE, FALSE, 0); break; case IDO_SCALE_MENU_ITEM_STYLE_LABEL: gtk_box_pack_start (box, priv->primary_label, FALSE, FALSE, 0); - gtk_box_pack_start (box, priv->scale, FALSE, FALSE, 0); + gtk_box_pack_start (box, priv->scale, TRUE, TRUE, 0); gtk_box_pack_start (box, priv->secondary_label, FALSE, FALSE, 0); break; default: - gtk_box_pack_start (box, priv->scale, FALSE, FALSE, 0); + gtk_box_pack_start (box, priv->scale, TRUE, TRUE, 0); break; } @@ -625,9 +650,10 @@ ido_scale_menu_item_secondary_image_notify (GtkImage *image, * @label: the text of the new menu item. * @size: The size style of the range. * @adjustment: A #GtkAdjustment describing the slider value. - * @returns: a new #IdoScaleMenuItem. * * Creates a new #IdoScaleMenuItem with an empty label. + * + * Return Value: a new #IdoScaleMenuItem. **/ GtkWidget* ido_scale_menu_item_new (const gchar *label, @@ -647,9 +673,10 @@ ido_scale_menu_item_new (const gchar *label, * @min: The minimum value of the slider. * @max: The maximum value of the slider. * @step: The step increment of the slider. - * @returns: a new #IdoScaleMenuItem. * * Creates a new #IdoScaleMenuItem containing a label. + * + * Return Value: a new #IdoScaleMenuItem. **/ GtkWidget* ido_scale_menu_item_new_with_range (const gchar *label, @@ -671,9 +698,10 @@ ido_scale_menu_item_new_with_range (const gchar *label, /** * ido_scale_menu_item_get_scale: * @menuitem: The #IdoScaleMenuItem - * @returns: A pointer to the scale widget. * * Retrieves the scale widget. + * + * Return Value: (transfer none): The #IdoRange in this item **/ GtkWidget* ido_scale_menu_item_get_scale (IdoScaleMenuItem *menuitem) @@ -690,10 +718,11 @@ ido_scale_menu_item_get_scale (IdoScaleMenuItem *menuitem) /** * ido_scale_menu_item_get_style: * @menuitem: The #IdoScaleMenuItem - * @returns: A #IdoScaleMenuItemStyle enum describing the style. * * Retrieves the type of widgets being used for the primary and * secondary widget slots. This could be images, labels, or nothing. + * + * Return Value: A #IdoScaleMenuItemStyle enum describing the style. **/ IdoScaleMenuItemStyle ido_scale_menu_item_get_style (IdoScaleMenuItem *menuitem) @@ -707,6 +736,14 @@ ido_scale_menu_item_get_style (IdoScaleMenuItem *menuitem) return priv->style; } +/** + * ido_scale_menu_item_set_style: + * @menuitem: The #IdoScaleMenuItem + * @style: Set the style use for the primary and secondary widget slots. + * + * Sets the type of widgets being used for the primary and + * secondary widget slots. This could be images, labels, or nothing. + **/ void ido_scale_menu_item_set_style (IdoScaleMenuItem *menuitem, IdoScaleMenuItemStyle style) @@ -725,11 +762,12 @@ ido_scale_menu_item_set_style (IdoScaleMenuItem *menuitem, /** * ido_scale_menu_item_get_primary_image: * @menuitem: The #IdoScaleMenuItem - * @returns: A #GtkWidget pointer for the primary image. * * Retrieves a pointer to the image widget used in the primary slot. * Whether this is visible depends upon the return value from * ido_scale_menu_item_get_style(). + * + * Return Value: (transfer none): A #GtkWidget pointer for the primary image. **/ GtkWidget * ido_scale_menu_item_get_primary_image (IdoScaleMenuItem *menuitem) @@ -746,11 +784,12 @@ ido_scale_menu_item_get_primary_image (IdoScaleMenuItem *menuitem) /** * ido_scale_menu_item_get_secondary_image: * @menuitem: The #IdoScaleMenuItem - * @returns: A #GtkWidget pointer for the secondary image. * * Retrieves a pointer to the image widget used in the secondary slot. * Whether this is visible depends upon the return value from * ido_scale_menu_item_get_style(). + * + * Return Value: (transfer none): A #GtkWidget pointer for the secondary image. **/ GtkWidget * ido_scale_menu_item_get_secondary_image (IdoScaleMenuItem *menuitem) @@ -765,13 +804,45 @@ ido_scale_menu_item_get_secondary_image (IdoScaleMenuItem *menuitem) } /** + * ido_scale_menu_item_set_icons: + * @item: a #IdoScaleMenuItem + * @primary-icon: (allow-none): the primary icon, or %NULL + * @secondary-icon: (allow-non): the secondary icon, %NULL + * + * Sets the icons of @item to @primary_icon and @secondary_icon. + * Pass %NULL for either of them to unset the icon. + */ +static void +ido_scale_menu_item_set_icons (IdoScaleMenuItem *item, + GIcon *primary_icon, + GIcon *secondary_icon) +{ + GtkWidget *primary; + GtkWidget *secondary; + + primary = ido_scale_menu_item_get_primary_image (item); + secondary = ido_scale_menu_item_get_secondary_image (item); + + if (primary_icon) + gtk_image_set_from_gicon (GTK_IMAGE (primary), primary_icon, GTK_ICON_SIZE_MENU); + else + gtk_image_clear (GTK_IMAGE (primary)); + + if (secondary_icon) + gtk_image_set_from_gicon (GTK_IMAGE (secondary), secondary_icon, GTK_ICON_SIZE_MENU); + else + gtk_image_clear (GTK_IMAGE (secondary)); +} + +/** * ido_scale_menu_item_get_primary_label: * @menuitem: The #IdoScaleMenuItem - * @returns: A const gchar* string of the label text. * * Retrieves a string of the text for the primary label widget. * Whether this is visible depends upon the return value from * ido_scale_menu_item_get_style(). + * + * Return Value: The label text. **/ const gchar* ido_scale_menu_item_get_primary_label (IdoScaleMenuItem *menuitem) @@ -786,13 +857,14 @@ ido_scale_menu_item_get_primary_label (IdoScaleMenuItem *menuitem) } /** - * ido_scale_menu_item_get_primary_label: + * ido_scale_menu_item_get_secondary_label: * @menuitem: The #IdoScaleMenuItem - * @returns: A const gchar* string of the label text. * - * Retrieves a string of the text for the primary label widget. + * Retrieves a string of the text for the secondary label widget. * Whether this is visible depends upon the return value from * ido_scale_menu_item_get_style(). + * + * Return Value: The label text. **/ const gchar* ido_scale_menu_item_get_secondary_label (IdoScaleMenuItem *menuitem) @@ -809,7 +881,7 @@ ido_scale_menu_item_get_secondary_label (IdoScaleMenuItem *menuitem) /** * ido_scale_menu_item_set_primary_label: * @menuitem: The #IdoScaleMenuItem - * @label: A string containing the label text + * @label: The label text * * Sets the text for the label widget in the primary slot. This * widget will only be visibile if the return value of @@ -832,11 +904,11 @@ ido_scale_menu_item_set_primary_label (IdoScaleMenuItem *menuitem, } /** - * ido_scale_menu_item_set_primary_label: + * ido_scale_menu_item_set_secondary_label: * @menuitem: The #IdoScaleMenuItem - * @label: A string containing the label text + * @label: The label text * - * Sets the text for the label widget in the primary slot. This + * Sets the text for the label widget in the secondary slot. This * widget will only be visibile if the return value of * ido_scale_menu_item_get_style() is set to %IDO_SCALE_MENU_ITEM_STYLE_LABEL. **/ @@ -859,16 +931,16 @@ ido_scale_menu_item_set_secondary_label (IdoScaleMenuItem *menuitem, /** * ido_scale_menu_item_primary_clicked: * @menuitem: the #IdoScaleMenuItem - * + * * Emits the "primary-clicked" signal. * * The default handler for this signal lowers the scale's * adjustment to its lower bound. */ void -ido_scale_menu_item_primary_clicked (IdoScaleMenuItem * item) +ido_scale_menu_item_primary_clicked (IdoScaleMenuItem * menuitem) { - g_signal_emit (item, signals[PRIMARY_CLICKED], 0); + g_signal_emit (menuitem, signals[PRIMARY_CLICKED], 0); } static void default_primary_clicked_handler (IdoScaleMenuItem * item) @@ -880,18 +952,18 @@ default_primary_clicked_handler (IdoScaleMenuItem * item) } /** - * ido_scale_menu_item_primary_clicked: + * ido_scale_menu_item_secondary_clicked: * @menuitem: the #IdoScaleMenuItem - * - * Emits the "primary-clicked" signal. + * + * Emits the "secondary-clicked" signal. * * The default handler for this signal raises the scale's * adjustment to its upper bound. */ void -ido_scale_menu_item_secondary_clicked (IdoScaleMenuItem * item) +ido_scale_menu_item_secondary_clicked (IdoScaleMenuItem * menuitem) { - g_signal_emit (item, signals[SECONDARY_CLICKED], 0); + g_signal_emit (menuitem, signals[SECONDARY_CLICKED], 0); } static void default_secondary_clicked_handler (IdoScaleMenuItem * item) @@ -902,4 +974,99 @@ default_secondary_clicked_handler (IdoScaleMenuItem * item) gtk_adjustment_set_value (adj, gtk_adjustment_get_upper (adj)); } +/** + * ido_scale_menu_item_state_changed: + * + * Updates a IdoScaleMenuItem from @state. State must be a double which + * contains the current value of the slider. + */ +static void +ido_scale_menu_item_state_changed (IdoActionHelper *helper, + GVariant *state, + gpointer user_data) +{ + GtkWidget *scale; + + scale = ido_scale_menu_item_get_scale (IDO_SCALE_MENU_ITEM (ido_action_helper_get_widget (helper))); + gtk_range_set_value (GTK_RANGE (scale), g_variant_get_double (state)); +} + +static void +ido_scale_menu_item_value_changed (GtkScale *scale, + gpointer user_data) +{ + IdoActionHelper *helper = user_data; + gdouble value; + + value = gtk_range_get_value (GTK_RANGE (scale)); + ido_action_helper_change_action_state (helper, g_variant_new_double (value)); +} + +static GIcon * +menu_item_get_icon (GMenuItem *menuitem, + const gchar *attribute) +{ + GVariant *value; + + value = g_menu_item_get_attribute_value (menuitem, attribute, NULL); + + return value ? g_icon_deserialize (value) : NULL; +} + +/** + * ido_scale_menu_item_new_from_model: + * + * Creates a new #IdoScaleMenuItem. If @menuitem contains an action, it + * will be bound to that action in @actions. + * + * Returns: (transfer full): a new #IdoScaleMenuItem + */ +GtkMenuItem * +ido_scale_menu_item_new_from_model (GMenuItem *menuitem, + GActionGroup *actions) +{ + GtkWidget *item; + gchar *action; + gdouble min = 0.0; + gdouble max = 100.0; + gdouble step = 1.0; + GIcon *min_icon; + GIcon *max_icon; + + g_menu_item_get_attribute (menuitem, "min-value", "d", &min); + g_menu_item_get_attribute (menuitem, "max-value", "d", &max); + g_menu_item_get_attribute (menuitem, "step", "d", &step); + + item = ido_scale_menu_item_new_with_range ("Volume", IDO_RANGE_STYLE_DEFAULT, 0.0, min, max, step); + ido_scale_menu_item_set_style (IDO_SCALE_MENU_ITEM (item), IDO_SCALE_MENU_ITEM_STYLE_IMAGE); + + if (g_menu_item_get_attribute (menuitem, "action", "s", &action)) + { + IdoActionHelper *helper; + GtkWidget *scale; + + helper = ido_action_helper_new (item, actions, action, NULL); + g_signal_connect (helper, "action-state-changed", + G_CALLBACK (ido_scale_menu_item_state_changed), NULL); + + scale = ido_scale_menu_item_get_scale (IDO_SCALE_MENU_ITEM (item)); + g_signal_connect (scale, "value-changed", G_CALLBACK (ido_scale_menu_item_value_changed), helper); + + g_signal_connect_swapped (item, "destroy", G_CALLBACK (g_object_unref), helper); + + g_free (action); + } + + min_icon = menu_item_get_icon (menuitem, "min-icon"); + max_icon = menu_item_get_icon (menuitem, "max-icon"); + ido_scale_menu_item_set_icons (IDO_SCALE_MENU_ITEM (item), min_icon, max_icon); + + if (min_icon) + g_object_unref (min_icon); + if (max_icon) + g_object_unref (max_icon); + + return GTK_MENU_ITEM (item); +} + #define __IDO_SCALE_MENU_ITEM_C__ diff --git a/src/idoscalemenuitem.h b/src/idoscalemenuitem.h index 81a3474..2c32a49 100644 --- a/src/idoscalemenuitem.h +++ b/src/idoscalemenuitem.h @@ -61,8 +61,8 @@ struct _IdoScaleMenuItemClass GtkMenuItemClass parent_class; /* signal default handlers */ - void (*primary_clicked)(IdoScaleMenuItem * self); - void (*secondary_clicked)(IdoScaleMenuItem * self); + void (*primary_clicked)(IdoScaleMenuItem * menuitem); + void (*secondary_clicked)(IdoScaleMenuItem * menuitem); }; @@ -92,6 +92,9 @@ void ido_scale_menu_item_set_secondary_label (IdoScaleMenuItem void ido_scale_menu_item_primary_clicked (IdoScaleMenuItem *menuitem); void ido_scale_menu_item_secondary_clicked (IdoScaleMenuItem *menuitem); +GtkMenuItem * ido_scale_menu_item_new_from_model (GMenuItem *menuitem, + GActionGroup *actions); + G_END_DECLS #endif /* __IDO_SCALE_MENU_ITEM_H__ */ diff --git a/src/idoswitchmenuitem.c b/src/idoswitchmenuitem.c index 3831336..10ff1f3 100644 --- a/src/idoswitchmenuitem.c +++ b/src/idoswitchmenuitem.c @@ -101,16 +101,27 @@ ido_switch_menu_button_release_event (GtkWidget * widget, GdkEventButton * event return TRUE; /* stop the event so that it doesn't trigger popdown() */ } -/*** -**** Public API -***/ - +/** + * ido_switch_menu_item_new: + * + * Creates a new #IdoSwitchMenuItem + * + * Return Value: a new #IdoSwitchMenuItem. + **/ GtkWidget * ido_switch_menu_item_new (void) { return g_object_new (IDO_TYPE_SWITCH_MENU_ITEM, NULL); } +/** + * ido_switch_menu_item_get_content_area: + * @item: The #IdoSwitchMenuItem. + * + * Get the #GtkContainer to add additional widgets into. + * + * Return Value: (transfer none): The #GtkContainer to add additional widgets into. + **/ GtkContainer * ido_switch_menu_item_get_content_area (IdoSwitchMenuItem * item) { diff --git a/src/idotimeline.c b/src/idotimeline.c index c6275ae..8eea4b5 100644 --- a/src/idotimeline.c +++ b/src/idotimeline.c @@ -132,6 +132,12 @@ ido_timeline_class_init (IdoTimelineClass *klass) GDK_TYPE_SCREEN, G_PARAM_READWRITE)); + /** + * IdoTimeline::started: + * @timeline: The #IdoTimeline emitting the signal. + * + * The ::started signal is emitted when the timeline starts. + */ signals[STARTED] = g_signal_new ("started", G_TYPE_FROM_CLASS (object_class), @@ -141,6 +147,12 @@ ido_timeline_class_init (IdoTimelineClass *klass) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + /** + * IdoTimeline::paused: + * @timeline: The #IdoTimeline emitting the signal. + * + * The ::paused signal is emitted when the timeline pauses. + */ signals[PAUSED] = g_signal_new ("paused", G_TYPE_FROM_CLASS (object_class), @@ -150,6 +162,12 @@ ido_timeline_class_init (IdoTimelineClass *klass) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + /** + * IdoTimeline::finished: + * @timeline: The #IdoTimeline emitting the signal. + * + * The ::paused signal is emitted when the timeline finishes. + */ signals[FINISHED] = g_signal_new ("finished", G_TYPE_FROM_CLASS (object_class), @@ -159,6 +177,13 @@ ido_timeline_class_init (IdoTimelineClass *klass) g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + /** + * IdoTimeline::frame: + * @timeline: The #IdoTimeline emitting the signal. + * @progress: The progress position for this frame from 0.0 (start) to 1.0 (end). + * + * The ::frame signal is emitted when a frame should be drawn. + */ signals[FRAME] = g_signal_new ("frame", G_TYPE_FROM_CLASS (object_class), @@ -342,6 +367,15 @@ ido_timeline_new (guint duration) NULL); } +/** + * ido_timeline_new_for_screen: + * @duration: duration in milliseconds for the timeline + * @screen: Screen to start on. + * + * Creates a new #IdoTimeline with the specified number of frames on the given screen. + * + * Return Value: the newly created #IdoTimeline + **/ IdoTimeline * ido_timeline_new_for_screen (guint duration, GdkScreen *screen) @@ -375,8 +409,8 @@ ido_timeline_start (IdoTimeline *timeline) else priv->timer = g_timer_new (); - /* sanity check */ - g_assert (priv->fps > 0); + /* sanity check; CID: 12651 */ + priv->fps = priv->fps > 0 ? priv->fps : DEFAULT_FPS; if (priv->screen) { @@ -515,7 +549,8 @@ ido_timeline_set_fps (IdoTimeline *timeline, priv = IDO_TIMELINE_GET_PRIV (timeline); - priv->fps = fps; + /* Coverity CID: 12650/12651: guard against division by 0. */ + priv->fps = fps > 0 ? fps : priv->fps; if (ido_timeline_is_running (timeline)) { @@ -573,6 +608,13 @@ ido_timeline_set_loop (IdoTimeline *timeline, } } +/** + * ido_timeline_set_duration: + * @timeline: A #IdoTimeline + * @duration: Duration in milliseconds. + * + * Set the animation duration. + */ void ido_timeline_set_duration (IdoTimeline *timeline, guint duration) @@ -590,6 +632,14 @@ ido_timeline_set_duration (IdoTimeline *timeline, } } +/** + * ido_timeline_get_duration: + * @timeline: A #IdoTimeline + * + * Set the animation duration. + * + * Return Value: Duration in milliseconds. + */ guint ido_timeline_get_duration (IdoTimeline *timeline) { @@ -645,6 +695,13 @@ ido_timeline_get_direction (IdoTimeline *timeline) return priv->direction; } +/** + * ido_timeline_set_screen: + * @timeline: A #IdoTimeline + * @screen: A #GdkScreen to use + * + * Set the screen the timeline is running on. + */ void ido_timeline_set_screen (IdoTimeline *timeline, GdkScreen *screen) @@ -664,6 +721,14 @@ ido_timeline_set_screen (IdoTimeline *timeline, g_object_notify (G_OBJECT (timeline), "screen"); } +/** + * ido_timeline_get_screen: + * @timeline: A #IdoTimeline + * + * Get the screen this timeline is running on. + * + * Return Value: (transfer none): The #GdkScreen this timeline is running on. + */ GdkScreen * ido_timeline_get_screen (IdoTimeline *timeline) { @@ -675,6 +740,14 @@ ido_timeline_get_screen (IdoTimeline *timeline) return priv->screen; } +/** + * ido_timeline_get_progress: + * @timeline: A #IdoTimeline + * + * Get the progress on the timeline. + * + * Return Value: The progress from 0.0 (start) to 1.0 (end) + */ gdouble ido_timeline_get_progress (IdoTimeline *timeline) { @@ -686,6 +759,13 @@ ido_timeline_get_progress (IdoTimeline *timeline) return priv->progress; } +/** + * ido_timeline_set_progress: + * @timeline: A #IdoTimeline + * @progress: The progress from 0.0 (start) to 1.0 (end) + * + * Set the progress on the timeline. + */ void ido_timeline_set_progress (IdoTimeline *timeline, gdouble progress) { @@ -707,6 +787,15 @@ ido_timeline_set_progress (IdoTimeline *timeline, gdouble progress) ido_timeline_start (timeline); } +/** + * ido_timeline_calculate_progress: + * @linear_progress: The progress from 0.0 (start) to 1.0 (end) + * @progress_type: The progress transform to apply + * + * Transform a linear progress position using the given transform. + * + * Return Value: the progress position using the provided transform. + */ gdouble ido_timeline_calculate_progress (gdouble linear_progress, IdoTimelineProgressType progress_type) diff --git a/src/idousermenuitem.c b/src/idousermenuitem.c new file mode 100644 index 0000000..655ae21 --- /dev/null +++ b/src/idousermenuitem.c @@ -0,0 +1,468 @@ +/* +Copyright 2011 Canonical Ltd. + +Authors: + Conor Curran <conor.curran@canonical.com> + Mirco Müller <mirco.mueller@canonical.com> + Charles Kerr <charles.kerr@canonical.com> + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include <gtk/gtk.h> + +#include "idousermenuitem.h" +#include "idoactionhelper.h" + +#define FALLBACK_ICON_NAME "avatar-default" + +enum +{ + PROP_0, + PROP_LABEL, + PROP_ICON, + PROP_IS_LOGGED_IN, + PROP_IS_CURRENT_USER, + PROP_LAST +}; + +static GParamSpec *properties[PROP_LAST]; + +struct _IdoUserMenuItemPrivate +{ + GtkWidget* user_image; + GtkWidget* user_name; + GtkWidget* container; + GtkWidget* tick_icon; + gboolean is_logged_in; + gboolean is_current_user; + gchar * label; + GIcon * icon; +}; + +G_DEFINE_TYPE (IdoUserMenuItem, ido_user_menu_item, GTK_TYPE_MENU_ITEM); + +/* Prototypes */ +static gboolean ido_user_menu_item_primitive_draw_cb_gtk_3 (GtkWidget * image, + cairo_t * cr, + gpointer gself); + + +static void +my_get_property (GObject * o, + guint property_id, + GValue * value, + GParamSpec * pspec) +{ + IdoUserMenuItem * self = IDO_USER_MENU_ITEM (o); + + switch (property_id) + { + case PROP_LABEL: + g_value_set_string (value, self->priv->label); + break; + + case PROP_ICON: + g_value_set_object (value, self->priv->icon); + break; + + case PROP_IS_LOGGED_IN: + g_value_set_boolean (value, self->priv->is_logged_in); + break; + + case PROP_IS_CURRENT_USER: + g_value_set_boolean (value, self->priv->is_current_user); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + break; + } +} + +static void +my_set_property (GObject * o, + guint property_id, + const GValue * value, + GParamSpec * pspec) +{ + IdoUserMenuItem * self = IDO_USER_MENU_ITEM (o); + + switch (property_id) + { + case PROP_LABEL: + ido_user_menu_item_set_label (self, g_value_get_string (value)); + break; + + case PROP_ICON: + ido_user_menu_item_set_icon (self, g_value_get_object (value)); + break; + + case PROP_IS_LOGGED_IN: + ido_user_menu_item_set_logged_in (self, g_value_get_boolean (value)); + break; + + case PROP_IS_CURRENT_USER: + self->priv->is_current_user = g_value_get_boolean (value); + gtk_widget_queue_draw (GTK_WIDGET(self)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (o, property_id, pspec); + break; + } +} + +static void +my_dispose (GObject *object) +{ + IdoUserMenuItem * self = IDO_USER_MENU_ITEM (object); + + g_clear_object (&self->priv->icon); + + G_OBJECT_CLASS (ido_user_menu_item_parent_class)->dispose (object); +} + +static void +my_finalize (GObject *object) +{ + IdoUserMenuItem * self = IDO_USER_MENU_ITEM (object); + + g_free (self->priv->label); + + G_OBJECT_CLASS (ido_user_menu_item_parent_class)->finalize (object); +} + +static void +ido_user_menu_item_class_init (IdoUserMenuItemClass *klass) +{ + GParamFlags prop_flags; + GObjectClass * gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (IdoUserMenuItemPrivate)); + + gobject_class->get_property = my_get_property; + gobject_class->set_property = my_set_property; + gobject_class->dispose = my_dispose; + gobject_class->finalize = my_finalize; + + prop_flags = G_PARAM_CONSTRUCT + | G_PARAM_READWRITE + | G_PARAM_STATIC_STRINGS; + + properties[PROP_LABEL] = g_param_spec_string ("label", + "The user's name", + "The name to display", + "J. Random User", + prop_flags); + + properties[PROP_ICON] = g_param_spec_object ("icon", + "Icon", + "The user's GIcon", + G_TYPE_OBJECT, + prop_flags); + + properties[PROP_IS_LOGGED_IN] = g_param_spec_boolean ("is-logged-in", + "is logged in", + "is user logged in?", + FALSE, + prop_flags); + + properties[PROP_IS_CURRENT_USER] = g_param_spec_boolean ("is-current-user", + "is current user", + "is user current?", + FALSE, + prop_flags); + + g_object_class_install_properties (gobject_class, PROP_LAST, properties); +} + +static void +ido_user_menu_item_init (IdoUserMenuItem *self) +{ + IdoUserMenuItemPrivate * priv; + + self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + IDO_USER_MENU_ITEM_TYPE, + IdoUserMenuItemPrivate); + + priv = self->priv; + + // Create the UI elements. + priv->user_image = gtk_image_new (); + gtk_misc_set_alignment(GTK_MISC(priv->user_image), 0.0, 0.0); + + priv->user_name = gtk_label_new (NULL); + + priv->container = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + + priv->tick_icon = gtk_image_new_from_icon_name ("account-logged-in", + GTK_ICON_SIZE_MENU); + gtk_misc_set_alignment(GTK_MISC(priv->tick_icon), 1.0, 0.5); + + // Pack it together + gtk_box_pack_start (GTK_BOX (priv->container), + priv->user_image, + FALSE, + FALSE, + 0); + gtk_box_pack_start (GTK_BOX (priv->container), + priv->user_name, + FALSE, + FALSE, + 3); + gtk_box_pack_end (GTK_BOX(priv->container), + priv->tick_icon, + FALSE, + FALSE, 5); + + gtk_widget_show_all (priv->container); + gtk_container_add (GTK_CONTAINER (self), priv->container); + gtk_widget_show_all (priv->tick_icon); + gtk_widget_set_no_show_all (priv->tick_icon, TRUE); + gtk_widget_hide (priv->tick_icon); + + + // Fetch the drawing context. + g_signal_connect_after (GTK_WIDGET(self), "draw", + G_CALLBACK(ido_user_menu_item_primitive_draw_cb_gtk_3), + GTK_WIDGET(self)); +} + + +/*****************************************************************/ + +static gboolean +ido_user_menu_item_primitive_draw_cb_gtk_3 (GtkWidget * widget, + cairo_t * cr, + gpointer user_data) +{ + IdoUserMenuItemPrivate * priv; + + g_return_val_if_fail(IS_IDO_USER_MENU_ITEM(user_data), FALSE); + + priv = IDO_USER_MENU_ITEM(user_data)->priv; + + /* Draw dot only when user is the current user. */ + if (priv->is_current_user) + { + GtkStyleContext * style_context; + GtkStateFlags state_flags; + GdkRGBA color; + gdouble x, y; + + /* get the foreground color */ + style_context = gtk_widget_get_style_context (widget); + state_flags = gtk_widget_get_state_flags (widget); + gtk_style_context_get_color (style_context, state_flags, &color); + + GtkAllocation allocation; + gtk_widget_get_allocation (widget, &allocation); + x = allocation.x + 13; + y = allocation.height / 2; + + cairo_arc (cr, x, y, 3.0, 0.0, 2 * G_PI); + + gdk_cairo_set_source_rgba (cr, &color); + + cairo_fill (cr); + } + + return FALSE; +} + +/*** +**** PUBLIC API +***/ + +void +ido_user_menu_item_set_icon (IdoUserMenuItem * self, GIcon * icon) +{ + IdoUserMenuItemPrivate * p = self->priv; + GtkImage * image = GTK_IMAGE (p->user_image); + + g_clear_object (&p->icon); + + if (icon != NULL) + g_object_ref (icon); + else + icon = g_themed_icon_new_with_default_fallbacks (FALLBACK_ICON_NAME); + + gtk_image_set_from_gicon (image, icon, GTK_ICON_SIZE_MENU); +} + +void +ido_user_menu_item_set_icon_from_file (IdoUserMenuItem * self, const char * filename) +{ + GFile * file = g_file_new_for_path (filename); + GIcon * icon = g_file_icon_new (file); + + ido_user_menu_item_set_icon (self, icon); + + g_clear_object (&icon); + g_clear_object (&file); +} + +void +ido_user_menu_item_set_logged_in (IdoUserMenuItem * self, gboolean is_logged_in) +{ + gtk_widget_set_visible (self->priv->tick_icon, is_logged_in); +} + +void +ido_user_menu_item_set_current_user (IdoUserMenuItem * self, gboolean is_current_user) +{ + self->priv->is_current_user = is_current_user; + gtk_widget_queue_draw (GTK_WIDGET (self)); +} + +void +ido_user_menu_item_set_label (IdoUserMenuItem * self, const char * label) +{ + gtk_label_set_label (GTK_LABEL(self->priv->user_name), label); +} + +GtkWidget* +ido_user_menu_item_new (void) +{ + return GTK_WIDGET (g_object_new (IDO_USER_MENU_ITEM_TYPE, NULL)); +} + +/** + * user_menu_item_state_changed: + * + * Updates an IdoUserMenuItem from @state. The state contains a + * dictionary with keys 'active-user' (for the user that the current + * session belongs too) and 'logged-in-users' (a list of all currently + * logged in users). + */ +static void +user_menu_item_state_changed (IdoActionHelper *helper, + GVariant *state, + gpointer user_data) +{ + IdoUserMenuItem *item; + GVariant *target; + GVariant *v; + + item = IDO_USER_MENU_ITEM (ido_action_helper_get_widget (helper)); + + ido_user_menu_item_set_current_user (item, FALSE); + ido_user_menu_item_set_logged_in (item, FALSE); + + target = ido_action_helper_get_action_target (helper); + g_return_if_fail (g_variant_is_of_type (target, G_VARIANT_TYPE_STRING)); + + if ((v = g_variant_lookup_value (state, "active-user", G_VARIANT_TYPE_STRING))) + { + if (g_variant_equal (v, target)) + ido_user_menu_item_set_current_user (item, TRUE); + + g_variant_unref (v); + } + + if ((v = g_variant_lookup_value (state, "logged-in-users", G_VARIANT_TYPE_STRING_ARRAY))) + { + GVariantIter it; + GVariant *user; + + g_variant_iter_init (&it, v); + while ((user = g_variant_iter_next_value (&it))) + { + if (g_variant_equal (user, target)) + ido_user_menu_item_set_logged_in (item, TRUE); + g_variant_unref (user); + } + + g_variant_unref (v); + } +} + +/** + * ido_user_menu_item_new_from_model: + * + * Creates an #IdoUserMenuItem. If @menuitem contains an action, the + * widget is bound to that action in @actions. + * + * Returns: (transfer full): a new #IdoUserMenuItem + */ +GtkMenuItem * +ido_user_menu_item_new_from_model (GMenuItem *menuitem, + GActionGroup *actions) +{ + guint i; + guint n; + IdoUserMenuItem * ido_user; + gchar * str; + gchar * action; + GVariant * v; + GParameter parameters[4]; + + /* create the ido_user */ + + n = 0; + + if (g_menu_item_get_attribute (menuitem, "label", "s", &str)) + { + GParameter p = { "label", G_VALUE_INIT }; + g_value_init (&p.value, G_TYPE_STRING); + g_value_take_string (&p.value, str); + parameters[n++] = p; + } + + if ((v = g_menu_item_get_attribute_value (menuitem, G_MENU_ATTRIBUTE_ICON, NULL))) + { + GParameter p = { "icon", G_VALUE_INIT }; + GIcon * icon = g_icon_deserialize (v); + g_value_init (&p.value, G_TYPE_OBJECT); + g_value_take_object (&p.value, icon); + g_variant_unref (v); + parameters[n++] = p; + } + + g_assert (n <= G_N_ELEMENTS (parameters)); + ido_user = g_object_newv (IDO_USER_MENU_ITEM_TYPE, n, parameters); + + for (i=0; i<n; i++) + g_value_unset (¶meters[i].value); + + /* gie it an ActionHelper */ + + if (g_menu_item_get_attribute (menuitem, "action", "s", &action)) + { + IdoActionHelper *helper; + GVariant *target; + + target = g_menu_item_get_attribute_value (menuitem, "target", G_VARIANT_TYPE_ANY); + + helper = ido_action_helper_new (GTK_WIDGET (ido_user), actions, action, target); + g_signal_connect (helper, "action-state-changed", + G_CALLBACK (user_menu_item_state_changed), NULL); + + g_signal_connect_object (ido_user, "activate", + G_CALLBACK (ido_action_helper_activate), + helper, G_CONNECT_SWAPPED); + g_signal_connect_swapped (ido_user, "destroy", G_CALLBACK (g_object_unref), helper); + + if (target) + g_variant_unref (target); + g_free (action); + } + + return GTK_MENU_ITEM (ido_user); +} + diff --git a/src/idousermenuitem.h b/src/idousermenuitem.h new file mode 100644 index 0000000..89e9a12 --- /dev/null +++ b/src/idousermenuitem.h @@ -0,0 +1,70 @@ +/* +Copyright 2011 Canonical Ltd. + +Authors: + Conor Curran <conor.curran@canonical.com> + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3, as published +by the Free Software Foundation. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranties of +MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see <http://www.gnu.org/licenses/>. +*/ +#ifndef __IDO_USER_MENU_ITEM_H__ +#define __IDO_USER_MENU_ITEM_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define IDO_USER_MENU_ITEM_TYPE (ido_user_menu_item_get_type ()) +#define IDO_USER_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDO_USER_MENU_ITEM_TYPE, IdoUserMenuItem)) +#define IDO_USER_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), IDO_USER_MENU_ITEM_TYPE, IdoUserMenuItemClass)) +#define IS_IDO_USER_MENU_ITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDO_USER_MENU_ITEM_TYPE)) +#define IS_IDO_USER_MENU_ITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), IDO_USER_MENU_ITEM_TYPE)) +#define IDO_USER_MENU_ITEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), IDO_USER_MENU_ITEM_TYPE, IdoUserMenuItemClass)) + +typedef struct _IdoUserMenuItem IdoUserMenuItem; +typedef struct _IdoUserMenuItemClass IdoUserMenuItemClass; +typedef struct _IdoUserMenuItemPrivate IdoUserMenuItemPrivate; + +/* property keys */ +#define IDO_USER_MENU_ITEM_PROP_LABEL "label" +#define IDO_USER_MENU_ITEM_PROP_ICON_FILENAME "icon-filename" +#define IDO_USER_MENU_ITEM_PROP_IS_LOGGED_IN "is-logged-in" +#define IDO_USER_MENU_ITEM_PROP_IS_CURRENT_USER "is-current-user" + +struct _IdoUserMenuItemClass +{ + GtkMenuItemClass parent_class; +}; + +struct _IdoUserMenuItem +{ + /*< private >*/ + GtkMenuItem parent; + IdoUserMenuItemPrivate * priv; +}; + +GType ido_user_menu_item_get_type (void) G_GNUC_CONST; + +GtkWidget* ido_user_menu_item_new(void); + +void ido_user_menu_item_set_icon (IdoUserMenuItem * self, GIcon * icon); +void ido_user_menu_item_set_icon_from_file (IdoUserMenuItem * self, const char * filename); +void ido_user_menu_item_set_logged_in (IdoUserMenuItem * self, gboolean is_logged_in); +void ido_user_menu_item_set_current_user (IdoUserMenuItem * self, gboolean is_current_user); +void ido_user_menu_item_set_label (IdoUserMenuItem * self, const char * label); + +GtkMenuItem * ido_user_menu_item_new_from_model (GMenuItem *menuitem, + GActionGroup *actions); + +G_END_DECLS + +#endif diff --git a/src/libido.c b/src/libido.c new file mode 100644 index 0000000..0c90213 --- /dev/null +++ b/src/libido.c @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Lars Uebernickel <lars.uebernickel@canonical.com> + */ + +#include <gio/gio.h> + +/** + * ido_init: + * + * Initializes ido. It has to be called after gtk_init(), but before any + * other calls into ido are made. + */ +void +ido_init (void) +{ + GType ido_menu_item_factory_get_type (void); + + /* make sure this extension point is registered so that gtk calls it + * when finding custom menu items */ + g_type_ensure (ido_menu_item_factory_get_type ()); +} diff --git a/src/libido.h b/src/libido.h index 4c4f68b..43a8168 100644 --- a/src/libido.h +++ b/src/libido.h @@ -31,4 +31,6 @@ #include <libido/idoentrymenuitem.h> #include <libido/idomessagedialog.h> +void ido_init (void); + #endif /* __IDO__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 39ad2d3..f45114e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,7 +15,7 @@ AM_CPPFLAGS = \ nodist_libgtest_a_SOURCES = \ $(XORG_GTEST_SOURCE)/src/xorg-gtest-all.cpp \ - $(GTEST_SOURCE)/src/gtest-all.cc \ + $(GTEST_SOURCE)/gtest-all.cc \ $(XORG_GTEST_SOURCE)/src/xorg-gtest_main.cpp libgtest_a_CPPFLAGS = \ $(XORG_GTEST_CPPFLAGS) \ @@ -43,27 +43,6 @@ gtest_menuitems_LDFLAGS = \ gtest_menuitems_LDADD = \ $(GTK_LIBS) \ $(IDOLIB) \ - libgtest.a - -############################# -# Dialog tests -############################# - -TESTS += gtest-dialog -check_PROGRAMS += gtest-dialog - -gtest_dialog_SOURCES = \ - gtest-dialog.cpp -gtest_dialog_CPPFLAGS = \ - $(GCC_CFLAGS) \ - $(GTK_CFLAGS) \ - $(MAINTAINER_CFLAGS) \ - $(AM_CPPFLAGS) -gtest_dialog_LDFLAGS = \ - -pthread -gtest_dialog_LDADD = \ - $(GTK_LIBS) \ - $(IDOLIB) \ - libgtest.a - + libgtest.a \ + -lX11 -lXi diff --git a/tests/gtest-dialog.cpp b/tests/gtest-dialog.cpp deleted file mode 100644 index c35a6cc..0000000 --- a/tests/gtest-dialog.cpp +++ /dev/null @@ -1,152 +0,0 @@ - -#include <gtk/gtk.h> -#include <gtest/gtest.h> -#include "idomessagedialog.h" - -class TestDialog : public ::testing::Test -{ - private: - - guint log_handler_id; - - int log_count_actual; - - static void log_count_func (const gchar *log_domain, - GLogLevelFlags log_level, - const gchar *message, - gpointer user_data) - { - reinterpret_cast<TestDialog*>(user_data)->log_count_actual++; - } - - protected: - - int log_count_expected; - - GMainLoop * main_loop; - - protected: - - virtual void SetUp() - { - const GLogLevelFlags flags = GLogLevelFlags(G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING); - log_handler_id = g_log_set_handler ("Gdk", flags, log_count_func, this); - log_count_expected = 0; - log_count_actual = 0; - - main_loop = NULL; - - static bool initialized = false; - if (G_UNLIKELY (!initialized)) - { - g_type_init(); - initialized = true; - } - - main_loop = g_main_loop_new (NULL, FALSE); - } - - virtual void TearDown() - { - ASSERT_EQ (log_count_expected, log_count_actual); - - g_clear_pointer (&main_loop, g_main_loop_unref); - } - - public: - - TestDialog() - { - gint argc = 0; - gchar * argv[] = {NULL}; - gtk_init (&argc, (gchar ***)&argv); - } - - protected: - - static gboolean - on_wait_timeout (gpointer main_loop) - { - g_main_loop_quit (static_cast<GMainLoop*>(main_loop)); - return G_SOURCE_REMOVE; - } - - void - WaitForSignal (gpointer instance, const gchar * detailed_signal) - { - guint timeout_id; - gulong handler_id; - const int timeout_seconds = 5; - - ASSERT_TRUE (instance != NULL); - ASSERT_TRUE (main_loop != NULL); - - - handler_id = g_signal_connect_swapped (instance, - detailed_signal, - G_CALLBACK(g_main_loop_quit), - main_loop); - - timeout_id = g_timeout_add_seconds (timeout_seconds, - on_wait_timeout, - main_loop); - - // wait for the signal or for timeout, whichever comes first - g_main_loop_run (main_loop); - ASSERT_TRUE (g_main_context_find_source_by_id(NULL,timeout_id) != NULL); - g_signal_handler_disconnect (instance, handler_id); - g_source_remove (timeout_id); - } - - protected: - - void ShowDialog (GtkWidget * dialog) - { - EXPECT_TRUE (dialog != NULL); - EXPECT_TRUE (IDO_IS_MESSAGE_DIALOG (dialog)); - - gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog), - "Some Secondary Text"); - - GtkWidget * action_area; - action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog)); - EXPECT_TRUE (GTK_IS_WIDGET (action_area)); - - gtk_widget_show (dialog); - - // synthesize a focus-in event to activate the idotimeline - GdkEventFocus focus; - focus.type = GDK_FOCUS_CHANGE; - focus.window = gtk_widget_get_window (dialog); - focus.send_event = FALSE; - focus.in = TRUE; - gtk_main_do_event ((GdkEvent*)&focus); - ++log_count_expected; // this will throw up a synthesized event warning - - g_timeout_add_seconds (1, on_wait_timeout, main_loop); - g_main_loop_run (main_loop); - } -}; - -TEST_F (TestDialog, BuildMessageDialog) -{ - GtkWidget * dialog = ido_message_dialog_new (NULL, - GtkDialogFlags(0), - GTK_MESSAGE_INFO, - GTK_BUTTONS_CLOSE, - "%s", - "Hello World"); - ShowDialog (dialog); -} - -TEST_F (TestDialog, BuildMessageDialogWithMarkup) -{ - GtkWidget * dialog = ido_message_dialog_new_with_markup (NULL, - GtkDialogFlags(0), - GTK_MESSAGE_INFO, - GTK_BUTTONS_CLOSE, - "%s", - "<small>Hello World</small>"); - ShowDialog (dialog); -} - diff --git a/tests/gtest-menuitems.cpp b/tests/gtest-menuitems.cpp index 3942bfc..269d360 100644 --- a/tests/gtest-menuitems.cpp +++ b/tests/gtest-menuitems.cpp @@ -4,7 +4,6 @@ #include "idocalendarmenuitem.h" #include "idoentrymenuitem.h" #include "idoscalemenuitem.h" -#include "idoswitchmenuitem.h" class TestMenuitems : public ::testing::Test { @@ -16,90 +15,48 @@ public: gtk_init(&argc, (gchar ***)&argv); return; } - -protected: - void PutInMenu (GtkWidget * item) - { - GtkWidget * menu = gtk_menu_new(); - gtk_widget_show(menu); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - - gtk_widget_show(item); - gtk_widget_realize(item); - - EXPECT_TRUE(gtk_widget_get_realized(item)); - - g_object_ref_sink(menu); - g_object_unref(menu); - } }; TEST_F(TestMenuitems, BuildCalendar) { GtkWidget * cal = ido_calendar_menu_item_new(); - IdoCalendarMenuItem * c = IDO_CALENDAR_MENU_ITEM (cal); EXPECT_TRUE(cal != NULL); EXPECT_TRUE(IDO_IS_CALENDAR_MENU_ITEM(cal)); EXPECT_TRUE(GTK_IS_MENU_ITEM(cal)); - const guint year_in = 1963; - const guint month_in = 10; - const guint day_in = 23; - ido_calendar_menu_item_set_date (c, year_in, month_in, day_in); - guint year_out = 0; - guint month_out = 0; - guint day_out = 0; - ido_calendar_menu_item_get_date (c, &year_out, &month_out, &day_out); - ASSERT_EQ (year_in, year_out); - ASSERT_EQ (month_in, month_out); - ASSERT_EQ (day_in, day_out); - - const GtkCalendarDisplayOptions options_in = GTK_CALENDAR_SHOW_DAY_NAMES; - ido_calendar_menu_item_set_display_options (c, options_in); - const GtkCalendarDisplayOptions options_out = ido_calendar_menu_item_get_display_options (c); - ASSERT_EQ (options_in, options_out); - - GtkWidget * w; - w = ido_calendar_menu_item_get_calendar (c); - ASSERT_TRUE (w != NULL); - ASSERT_TRUE (GTK_IS_CALENDAR (w)); - - // test clear/mark/unmark days - ido_calendar_menu_item_clear_marks (c); - ido_calendar_menu_item_mark_day (c, 0); - ido_calendar_menu_item_mark_day (c, 1); - ido_calendar_menu_item_mark_day (c, 2); - ido_calendar_menu_item_unmark_day (c, 0); - ido_calendar_menu_item_unmark_day (c, 2); - for (int i=0; i<28; i++) - ASSERT_EQ (gtk_calendar_get_day_is_marked(GTK_CALENDAR(w), i), i==1); - - PutInMenu (cal); + GtkWidget * menu = gtk_menu_new(); + gtk_widget_show(menu); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), cal); + gtk_widget_show(cal); + gtk_widget_realize(cal); + + EXPECT_TRUE(gtk_widget_get_realized(cal)); + + g_object_ref_sink(menu); + g_object_unref(menu); return; } -TEST_F(TestMenuitems, BuildEntry) -{ +TEST_F(TestMenuitems, BuildEntry) { GtkWidget * entry = ido_entry_menu_item_new(); - EXPECT_TRUE (entry != NULL); - EXPECT_TRUE (IDO_IS_ENTRY_MENU_ITEM(entry)); - EXPECT_TRUE (GTK_IS_MENU_ITEM(entry)); - GtkWidget * w = ido_entry_menu_item_get_entry (IDO_ENTRY_MENU_ITEM(entry)); - ASSERT_TRUE (w != NULL); - ASSERT_TRUE (GTK_IS_ENTRY (w)); + EXPECT_TRUE(entry != NULL); + EXPECT_TRUE(IDO_IS_ENTRY_MENU_ITEM(entry)); + EXPECT_TRUE(GTK_IS_MENU_ITEM(entry)); - PutInMenu (entry); - return; -} + GtkWidget * menu = gtk_menu_new(); + gtk_widget_show(menu); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), entry); -namespace -{ - void increment_the_userdata (GObject * scale, gpointer userdata) - { - ++*static_cast<int*>(userdata); - } + gtk_widget_show(entry); + gtk_widget_realize(entry); + + EXPECT_TRUE(gtk_widget_get_realized(entry)); + + g_object_ref_sink(menu); + g_object_unref(menu); + return; } TEST_F(TestMenuitems, BuildScaleDefault) { @@ -109,27 +66,17 @@ TEST_F(TestMenuitems, BuildScaleDefault) { EXPECT_TRUE(IDO_IS_SCALE_MENU_ITEM(scale)); EXPECT_TRUE(GTK_IS_MENU_ITEM(scale)); - const gchar * str_in = "Primary Text"; - ido_scale_menu_item_set_primary_label (IDO_SCALE_MENU_ITEM(scale), str_in); - const gchar * str_out = ido_scale_menu_item_get_primary_label (IDO_SCALE_MENU_ITEM(scale)); - ASSERT_TRUE (str_in != str_out); - ASSERT_STREQ (str_in, str_out); - - str_in = "Secondary Text"; - ido_scale_menu_item_set_secondary_label (IDO_SCALE_MENU_ITEM(scale), str_in); - str_out = ido_scale_menu_item_get_secondary_label (IDO_SCALE_MENU_ITEM(scale)); - ASSERT_TRUE (str_in != str_out); - ASSERT_STREQ (str_in, str_out); - - int i = 0; - g_signal_connect (scale, "primary-clicked", G_CALLBACK(increment_the_userdata), &i); - g_signal_connect (scale, "secondary-clicked", G_CALLBACK(increment_the_userdata), &i); - ido_scale_menu_item_primary_clicked (IDO_SCALE_MENU_ITEM (scale)); - ASSERT_EQ (1, i); - ido_scale_menu_item_secondary_clicked (IDO_SCALE_MENU_ITEM (scale)); - ASSERT_EQ (2, i); - - PutInMenu (scale); + GtkWidget * menu = gtk_menu_new(); + gtk_widget_show(menu); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), scale); + + gtk_widget_show(scale); + gtk_widget_realize(scale); + + EXPECT_TRUE(gtk_widget_get_realized(scale)); + + g_object_ref_sink(menu); + g_object_unref(menu); return; } @@ -140,20 +87,16 @@ TEST_F(TestMenuitems, BuildScaleSmall) { EXPECT_TRUE(IDO_IS_SCALE_MENU_ITEM(scale)); EXPECT_TRUE(GTK_IS_MENU_ITEM(scale)); - PutInMenu (scale); - return; -} + GtkWidget * menu = gtk_menu_new(); + gtk_widget_show(menu); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), scale); + gtk_widget_show(scale); + gtk_widget_realize(scale); -TEST_F(TestMenuitems, BuildSwitch) { - GtkWidget * item = ido_switch_menu_item_new (); - EXPECT_TRUE (item != NULL); - EXPECT_TRUE (IDO_IS_SWITCH_MENU_ITEM(item)); - EXPECT_TRUE (GTK_IS_MENU_ITEM(item)); + EXPECT_TRUE(gtk_widget_get_realized(scale)); - GtkContainer * content_area = ido_switch_menu_item_get_content_area (IDO_SWITCH_MENU_ITEM(item)); - EXPECT_TRUE (content_area != NULL); - EXPECT_TRUE (GTK_IS_CONTAINER (content_area)); - - PutInMenu (item); + g_object_ref_sink(menu); + g_object_unref(menu); + return; } |