summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml280
-rw-r--r--.travis.yml19
-rw-r--r--NEWS2
-rw-r--r--applets/Makefile.am4
-rw-r--r--applets/clock/Makefile.am17
-rw-r--r--applets/clock/calendar-client.c2095
-rw-r--r--applets/clock/calendar-client.h151
-rw-r--r--applets/clock/calendar-debug.h50
-rw-r--r--applets/clock/calendar-sources.c503
-rw-r--r--applets/clock/calendar-sources.h64
-rw-r--r--applets/clock/calendar-window.c1126
-rw-r--r--applets/clock/calendar-window.h3
-rw-r--r--applets/clock/clock-location-tile.c6
-rw-r--r--applets/clock/clock.c85
-rw-r--r--applets/clock/clock.ui98
-rw-r--r--applets/clock/org.mate.panel.applet.clock.gschema.xml.in10
-rw-r--r--applets/fish/fish.c4
-rw-r--r--applets/notification_area/Makefile.am28
-rw-r--r--applets/notification_area/main.c5
-rw-r--r--applets/notification_area/na-grid.c5
-rw-r--r--applets/notification_area/na-grid.h5
-rw-r--r--applets/notification_area/status-notifier/sn-item-v0.c7
-rw-r--r--applets/wncklet/Makefile.am26
-rw-r--r--applets/wncklet/showdesktop.c21
-rw-r--r--applets/wncklet/window-list.c118
-rw-r--r--applets/wncklet/window-list.ui1
-rw-r--r--applets/wncklet/window-menu.c13
-rw-r--r--applets/wncklet/wncklet.c27
-rw-r--r--applets/wncklet/wncklet.h7
-rw-r--r--applets/wncklet/workspace-switcher.c13
-rw-r--r--configure.ac38
-rw-r--r--libmate-panel-applet/mate-panel-applet-gsettings.c18
-rw-r--r--libmate-panel-applet/mate-panel-applet.c4
-rw-r--r--mate-panel/libmate-panel-applet-private/panel-applet-frame-dbus.c2
-rw-r--r--mate-panel/libmate-panel-applet-private/panel-applets-manager-dbus.c3
-rw-r--r--mate-panel/panel-action-button.c7
-rw-r--r--mate-panel/panel-applet-frame.c7
-rw-r--r--mate-panel/panel-background.c2
-rw-r--r--mate-panel/panel-force-quit.c6
-rw-r--r--mate-panel/panel-layout.c5
-rw-r--r--mate-panel/panel-menu-button.c2
-rw-r--r--mate-panel/panel-multimonitor.c5
-rw-r--r--mate-panel/panel-profile.c23
-rw-r--r--mate-panel/panel-recent.c6
-rw-r--r--mate-panel/panel-stock-icons.c79
-rw-r--r--mate-panel/panel-stock-icons.h12
-rw-r--r--mate-panel/panel-test-applets.c2
-rw-r--r--mate-panel/panel-toplevel.c24
-rw-r--r--mate-panel/panel.c16
49 files changed, 4751 insertions, 303 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 00000000..2584a8af
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,280 @@
+name: CI Build
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - master
+ workflow_dispatch:
+
+# cancel already running builds of the same branch or pull request
+concurrency:
+ group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.head_ref || github.sha }}
+ cancel-in-progress: true
+
+env:
+ MATE_PANEL_DEP: 1.28.2
+ CONFIGURE_FLAGS: --enable-compile-warnings=maximum
+ CFLAGS: -g -O2 -Werror=pointer-arith -Werror=implicit-function-declaration
+ JOBS: 2
+ DEBUG: 1
+ # Useful URL: https://github.com/mate-desktop/debian-packages
+ # Useful URL: https://salsa.debian.org/debian-mate-team/mate-panel
+ DEB_LIBRARY_DEPS: |
+ libatk1.0-dev
+ libcairo2-dev
+ libdconf-dev
+ libgirepository1.0-dev
+ libglib2.0-dev
+ libgtk-3-dev
+ libgtk-layer-shell-dev
+ libice-dev
+ libmate-desktop-dev
+ libmate-menu-dev
+ libmateweather-dev
+ libpango1.0-dev
+ libsm-dev
+ libsoup2.4-dev
+ libwnck-3-dev
+ libx11-dev
+ libxrandr-dev
+ # mate-desktop dependencies
+ DEB_LIBRARY_DEPS_MATE_DESKTOP: |
+ libgirepository1.0-dev
+ DEB_BUILD_DEPS: |
+ ccache
+ autoconf-archive
+ autopoint
+ gir1.2-freedesktop
+ git
+ gobject-introspection
+ gtk-doc-tools
+ lsb-release
+ make
+ mate-common
+ meson
+ yelp-tools
+ # mate-desktop dependencies
+ DEB_BUILD_DEPS_MATE_DESKTOP: |
+ iso-codes
+ gobject-introspection
+ # TODO
+ DEB_SCAN_BUILD_DEPS: |
+ clang
+ clang-tools
+ # Useful URL: https://git.archlinux.org/svntogit/community.git/tree/mate-panel
+ ARCH_BUILD_DEPS: |
+ ccache
+ autoconf-archive
+ clang
+ gcc
+ git
+ glib2-devel
+ gobject-introspection
+ gtk-layer-shell
+ itstool
+ libcanberra
+ libmateweather
+ libsm
+ libwnck3
+ make
+ mate-common
+ mate-desktop
+ mate-menus
+ meson
+ which
+ yelp-tools
+ # mate-desktop dependencies
+ ARCH_BUILD_DEPS_MATE_DESKTOP: |
+ iso-codes
+ gobject-introspection
+
+jobs:
+ build:
+ name: Build on ${{matrix.container}} with in-process=${{matrix.in-process}} (using ${{matrix.cc}})
+ runs-on: ubuntu-latest
+ container: ${{matrix.container}}
+
+ strategy:
+ fail-fast: false # don't cancel other jobs in the matrix if one fails
+ matrix:
+ in-process: [all, none]
+ container: ['debian:testing', 'ubuntu:rolling', 'archlinux:latest']
+ cc: ['gcc']
+ cxx: ['g++']
+ include:
+ # test with clang on archlinux:latest
+ - container: 'archlinux:latest'
+ cc: 'clang'
+ cxx: 'clang++'
+ in-process: all
+ - container: 'archlinux:latest'
+ cc: 'clang'
+ cxx: 'clang++'
+ in-process: none
+
+ env:
+ # Speed up build with ccache
+ CC: ccache ${{matrix.cc}}
+ CXX: ccache ${{matrix.cxx}}
+ # root install path for the mate-desktop dependency
+ MATE_DESKTOP_INSTALL_PATH: ${{github.workspace}}/mate-desktop-install
+
+ steps:
+ # We can't *extend* the environment in 'env' directly, so use GITHUB_ENV
+ # output variable to do so.
+ - name: Setup environment
+ run: |
+ echo "PATH=${MATE_DESKTOP_INSTALL_PATH}/bin:${PATH}" >> "$GITHUB_ENV"
+ echo "PKG_CONFIG_PATH=${MATE_DESKTOP_INSTALL_PATH}/lib/pkgconfig:${PKG_CONFIG_PATH}" >> "$GITHUB_ENV"
+
+ # Debugging
+ - name: Show environment
+ run: env | sort
+ if: ${{ env.DEBUG == '1' }}
+
+ # For Debian and Ubuntu (apt-based with reasonably compatible packages)
+ - name: Install dependencies
+ if: ${{ startsWith(matrix.container, 'debian:') || startsWith(matrix.container, 'ubuntu:') }}
+ run: |
+ apt-get update -qq
+ apt-get install --assume-yes --no-install-recommends \
+ ${DEB_BUILD_DEPS} ${DEB_BUILD_DEPS_MATE_DESKTOP} \
+ ${DEB_LIBRARY_DEPS} ${DEB_LIBRARY_DEPS_MATE_DESKTOP}
+
+ # For ArchLinux
+ - name: Install dependencies
+ if: ${{ startsWith(matrix.container, 'archlinux:') }}
+ # don't upgrade, although told otherwise (see link below), because
+ # apparently in the container it doesn't quit work...
+ # https://wiki.archlinux.org/title/System_maintenance#Partial_upgrades_are_unsupported
+ run: |
+ pacman --noconfirm -Syu
+ pacman --noconfirm -S ${ARCH_BUILD_DEPS} ${ARCH_BUILD_DEPS_MATE_DESKTOP}
+
+ # Checkout the repository
+ - uses: actions/checkout@v3
+ with:
+ path: mate-panel
+ submodules: true
+
+ # Setup ccache cache
+ - name: ccache
+ uses: hendrikmuhs/[email protected]
+ with:
+ key: ${{ github.job }}-${{ matrix.container }}-${{ matrix.cc }}
+
+ # Cache the build of the mate-desktop dependency
+ - name: Cache mate-desktop v${{env.MATE_PANEL_DEP}} dependency
+ uses: actions/cache@v3
+ id: cache-mate-desktop
+ with:
+ path: ${{env.MATE_DESKTOP_INSTALL_PATH}}
+ # We try and be as specific as possible not to use the wrongly cached
+ # build, as this is a *binary*.
+ key: ${{runner.os}}-${{runner.arch}}-${{matrix.container}}-build-mate-desktop-${{env.MATE_PANEL_DEP}}
+
+ # Checkout mate-desktop dep, if not already cached
+ - name: Checkout mate-desktop v${{env.MATE_PANEL_DEP}}
+ uses: actions/checkout@v3
+ if: ${{ steps.cache-mate-desktop.outputs.cache-hit != 'true' }}
+ with:
+ repository: mate-desktop/mate-desktop
+ ref: v${{env.MATE_PANEL_DEP}}
+ path: mate-desktop
+ submodules: true
+
+ # Build and install mate-desktop dep, if not already cached
+ - name: Install mate-desktop v${{env.MATE_PANEL_DEP}}
+ if: ${{ steps.cache-mate-desktop.outputs.cache-hit != 'true' }}
+ run: |
+ cd mate-desktop
+ NOCONFIGURE=1 ./autogen.sh
+ { ./configure --prefix="${MATE_DESKTOP_INSTALL_PATH}" || { cat config.log; exit 1; } ; }
+ make -j ${{ env.JOBS }}
+ make -j ${{ env.JOBS }} install
+
+ # Follows regular mate-panel build and test steps
+
+ - name: Configure
+ run: |
+ cd mate-panel
+ NOCONFIGURE=1 ./autogen.sh
+ { ./configure ${CONFIGURE_FLAGS} --with-in-process-applets=${{matrix.in-process}} || { cat config.log; exit 1; } ; }
+
+ - name: Build
+ run: make -C mate-panel -j ${{ env.JOBS }}
+
+ - name: Run Tests
+ run: make -C mate-panel -j ${{ env.JOBS }} check
+
+ - name: Run distcheck
+ # We only run distcheck on one container, because it takes time and
+ # doesn't seem so useful to repeat everywhere -- it mostly checks the
+ # build system itself, rather than the build.
+ if: ${{ startsWith(matrix.container, 'debian:') }}
+ run: make -C mate-panel -j ${{ env.JOBS }} distcheck
+
+ # Do we need the real build for cppcheck run? I don't think so
+ cppcheck:
+ name: Run cppcheck
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ submodules: true
+
+ # Install code dependencies so that cppcheck has more info
+ - name: Install dependencies
+ run: |
+ sudo apt-get update -qq
+ sudo apt-get install --assume-yes --no-install-recommends \
+ cppcheck ${DEB_LIBRARY_DEPS} ${DEB_LIBRARY_DEPS_MATE_DESKTOP}
+
+ # - define relevant configuration I can think of
+ # - X11-related stuff
+ # - Wayland-related stuff
+ # - in-process for Wayland
+ # - optional features
+ # - _Noreturn: this is to avoid false positive with functions that
+ # don't return, like g_assert(false). Here, we rely on G_NORETURN
+ # (GLib 2.68+) using _Noreturn C11 attribute if __STDC_VERSION__ is
+ # high enough (cppcheck sets it for us in newer versions, but not on
+ # here yet); but the version of cppcheck we run on don't know about
+ # the C11 attribute, so map it to the GCC one it does know.
+ # This is a tad over-specific, but it removes some spurious warnings,
+ # and defining e.g. __GNUC__=12 is simpler, but is a *lot* slower
+ # (more than 3 times slower), and doesn't seem to yield other
+ # benefits for the moment.
+ # - -I flags from pkg-config (grepped from configure.ac)
+ # - ignore non-source directories
+ - name: cppcheck
+ env:
+ checks: warning,style,performance,portability,information,missingInclude
+ defines: >
+ -DHAVE_X11 -DHAVE_RANDR
+ -DHAVE_WAYLAND
+ -DCLOCK_INPROCESS -DFISH_INPROCESS -DNOTIFICATION_AREA_INPROCESS -DWNCKLET_INPROCESS
+ -DHAVE_LANGINFO_H -DHAVE_NL_LANGINFO
+ -DGETTEXT_PACKAGE="mate-panel"
+ -D__STDC_VERSION__=201112 -D_Noreturn=__attribute__((__noreturn__))
+ packages: >
+ gdk-pixbuf-2.0
+ gio-unix-2.0
+ gmodule-2.0
+ gtk+-3.0
+ ice
+ libwnck-3.0
+ mate-desktop-2.0
+ sm
+ run: |
+ cppcheck --enable="$checks" \
+ -j $JOBS \
+ $defines \
+ $(pkg-config --cflags-only-I $packages) \
+ -i gtk-layer-shell-build \
+ -i mate-panel/mate-submodules/ \
+ .
diff --git a/.travis.yml b/.travis.yml
index cd82a933..12de5f7c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,7 +20,9 @@ before_install:
- chmod +x docker-build gen-index
install:
- - pip3 install PyGithub
+ - sudo apt-get install -y python3-pip python3-setuptools
+ - sudo pip3 install --upgrade pip
+ - sudo pip3 install PyGithub
- ./docker-build --name ${DISTRO} --config .build.yml --install
script:
@@ -38,9 +40,17 @@ notifications:
on_success: never
on_failure: always
+before_deploy:
+ - yes | gem update --system --force
+ - gem install bundler
+ - gem install faraday-net_http -v '3.3.0' # Avoid faraday version problem
+ - gem install uri
+ - gem install logger
+
deploy:
- provider: pages
- edge: true
+ edge:
+ branch: v2.0.5
token: $GITHUB_TOKEN
keep_history: false
committer_from_gh: true
@@ -51,7 +61,8 @@ deploy:
all_branches: true
condition: ${DISTRO} =~ ^fedora.*$
- provider: script
- edge: true
+ edge:
+ branch: v2.0.5
script: ./docker-build --verbose --config .build.yml --release github
on:
tags: true
@@ -70,6 +81,6 @@ after_success:
env:
# - DISTRO="archlinux:latest"
- - DISTRO="debian:testing"
+# - DISTRO="debian:testing"
- DISTRO="fedora:latest"
# - DISTRO="ubuntu:rolling"
diff --git a/NEWS b/NEWS
index 439d0511..372cd54d 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,5 @@
+### mate-panel 1.29.0
+
### mate-panel 1.28.1
* Build: require mate-desktop 1.28.2
diff --git a/applets/Makefile.am b/applets/Makefile.am
index 1f040ff9..9e6c33fc 100644
--- a/applets/Makefile.am
+++ b/applets/Makefile.am
@@ -1,7 +1,9 @@
SUBDIRS = \
clock \
fish \
- wncklet
+ wncklet \
+ notification_area
+
if ENABLE_X11
SUBDIRS += \
diff --git a/applets/clock/Makefile.am b/applets/clock/Makefile.am
index e145dd5f..5a932422 100644
--- a/applets/clock/Makefile.am
+++ b/applets/clock/Makefile.am
@@ -35,6 +35,15 @@ CLOCK_SOURCES = \
set-timezone.h \
$(BUILT_SOURCES)
+if HAVE_EDS
+CLOCK_SOURCES += \
+ calendar-client.c \
+ calendar-client.h \
+ calendar-sources.c \
+ calendar-sources.h \
+ calendar-debug.h
+endif
+
CLOCK_CPPFLAGS = \
$(AM_CPPFLAGS) \
$(LIBMATE_PANEL_APPLET_CFLAGS) \
@@ -44,6 +53,10 @@ CLOCK_CPPFLAGS = \
-DMATELOCALEDIR=\""$(datadir)/locale"\" \
-DMATEWEATHER_I_KNOW_THIS_IS_UNSTABLE
+if HAVE_EDS
+CLOCK_CPPFLAGS += $(EDS_CFLAGS)
+endif
+
CLOCK_LDADD = \
../../libmate-panel-applet/libmate-panel-applet-4.la \
$(CLOCK_LIBS) \
@@ -51,6 +64,10 @@ CLOCK_LDADD = \
libsystem-timezone.la \
-lm
+if HAVE_EDS
+CLOCK_LDADD += $(EDS_LIBS)
+endif
+
test_system_timezone_SOURCES = \
test-system-timezone.c
test_system_timezone_LDADD = libsystem-timezone.la
diff --git a/applets/clock/calendar-client.c b/applets/clock/calendar-client.c
new file mode 100644
index 00000000..8bd9a2f3
--- /dev/null
+++ b/applets/clock/calendar-client.c
@@ -0,0 +1,2095 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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:
+ * Mark McLoughlin <[email protected]>
+ * William Jon McCann <[email protected]>
+ * Martin Grimme <[email protected]>
+ * Christian Kellner <[email protected]>
+ */
+
+#include <config.h>
+
+#include "calendar-client.h"
+
+#include <libintl.h>
+#include <string.h>
+#define HANDLE_LIBICAL_MEMORY
+
+#ifdef HAVE_EDS
+
+#include <libecal/libecal.h>
+#include "calendar-sources.h"
+#include "system-timezone.h"
+#endif
+
+#undef CALENDAR_ENABLE_DEBUG
+#include "calendar-debug.h"
+
+#ifndef _
+#define _(x) gettext(x)
+#endif
+
+#ifndef N_
+#define N_(x) x
+#endif
+
+#ifdef HAVE_EDS
+
+typedef struct _CalendarClientQuery CalendarClientQuery;
+typedef struct _CalendarClientSource CalendarClientSource;
+
+struct _CalendarClientQuery
+{
+ ECalClientView *view;
+ GHashTable *events;
+};
+
+struct _CalendarClientSource
+{
+ CalendarClient *client;
+ ECalClient *source;
+
+ CalendarClientQuery completed_query;
+ CalendarClientQuery in_progress_query;
+
+ guint changed_signal_id;
+
+ guint query_completed : 1;
+ guint query_in_progress : 1;
+};
+
+struct _CalendarClientPrivate
+{
+ CalendarSources *calendar_sources;
+
+ GSList *appointment_sources;
+ GSList *task_sources;
+
+ ICalTimezone *zone;
+
+ guint zone_listener;
+ GSettings *calendar_settings;
+
+ guint day;
+ guint month;
+ guint year;
+};
+
+static void calendar_client_finalize (GObject *object);
+static void calendar_client_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void calendar_client_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static GSList *calendar_client_update_sources_list (CalendarClient *client,
+ GSList *sources,
+ GList *esources,
+ guint changed_signal_id);
+static void calendar_client_appointment_sources_changed (CalendarClient *client);
+static void calendar_client_task_sources_changed (CalendarClient *client);
+
+static void calendar_client_stop_query (CalendarClient *client,
+ CalendarClientSource *source,
+ CalendarClientQuery *query);
+static void calendar_client_start_query (CalendarClient *client,
+ CalendarClientSource *source,
+ const char *query);
+
+static void calendar_client_source_finalize (CalendarClientSource *source);
+static void calendar_client_query_finalize (CalendarClientQuery *query);
+
+static void
+calendar_client_update_appointments (CalendarClient *client);
+static void
+calendar_client_update_tasks (CalendarClient *client);
+
+enum
+{
+ PROP_O,
+ PROP_DAY,
+ PROP_MONTH,
+ PROP_YEAR
+};
+
+enum
+{
+ APPOINTMENTS_CHANGED,
+ TASKS_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE_WITH_PRIVATE (CalendarClient, calendar_client, G_TYPE_OBJECT)
+
+static void
+calendar_client_class_init (CalendarClientClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+
+ gobject_class->finalize = calendar_client_finalize;
+ gobject_class->set_property = calendar_client_set_property;
+ gobject_class->get_property = calendar_client_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_DAY,
+ g_param_spec_uint ("day",
+ "Day",
+ "The currently monitored day between 1 and 31 (0 denotes unset)",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_MONTH,
+ g_param_spec_uint ("month",
+ "Month",
+ "The currently monitored month between 0 and 11",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (gobject_class,
+ PROP_YEAR,
+ g_param_spec_uint ("year",
+ "Year",
+ "The currently monitored year",
+ 0, G_MAXUINT, 0,
+ G_PARAM_READWRITE));
+
+ signals [APPOINTMENTS_CHANGED] =
+ g_signal_new ("appointments-changed",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CalendarClientClass, tasks_changed),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+
+ signals [TASKS_CHANGED] =
+ g_signal_new ("tasks-changed",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CalendarClientClass, tasks_changed),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+/* Timezone code adapted from evolution/calendar/gui/calendar-config.c */
+/* The current timezone, e.g. "Europe/London". It may be NULL, in which case
+ you should assume UTC. */
+static gchar *
+calendar_client_config_get_timezone (GSettings *calendar_settings)
+{
+ gchar *timezone = NULL;
+
+ if (calendar_settings == NULL)
+ return NULL;
+
+ /* Check if we can list the keys to see if timezone exists */
+ gchar **keys = g_settings_list_keys (calendar_settings);
+ gboolean has_timezone = FALSE;
+
+ for (gint i = 0; keys[i] != NULL; i++) {
+ if (g_strcmp0 (keys[i], "timezone") == 0) {
+ has_timezone = TRUE;
+ break;
+ }
+ }
+ g_strfreev (keys);
+
+ if (has_timezone) {
+ timezone = g_settings_get_string (calendar_settings, "timezone");
+ }
+
+ return timezone;
+}
+
+static ICalTimezone *
+calendar_client_config_get_icaltimezone (CalendarClient *client)
+{
+ gchar *location = NULL;
+ ICalTimezone *zone = NULL;
+
+ if (client->priv->calendar_settings != NULL)
+ location = calendar_client_config_get_timezone (client->priv->calendar_settings);
+
+ if (!location) {
+ /* MATE panel doesn't store timezone in GSettings
+ * Since libical timezone lookup often fails, just use UTC
+ * The display code will handle local time conversion */
+ return i_cal_timezone_get_utc_timezone ();
+ }
+
+ zone = i_cal_timezone_get_builtin_timezone (location);
+ g_free (location);
+
+ return zone;
+}
+
+static void
+calendar_client_set_timezone (CalendarClient *client)
+{
+ GList *list, *link;
+
+ client->priv->zone = calendar_client_config_get_icaltimezone (client);
+
+ list = calendar_sources_get_appointment_clients (client->priv->calendar_sources);
+ for (link = list; link != NULL; link = g_list_next (link))
+ {
+ ECalClient *cal = E_CAL_CLIENT (link->data);
+
+ e_cal_client_set_default_timezone (cal, client->priv->zone);
+ }
+ g_list_free (list);
+}
+
+static void
+calendar_client_timezone_changed_cb (GSettings *calendar_settings,
+ const gchar *key,
+ CalendarClient *client)
+{
+ calendar_client_set_timezone (client);
+}
+
+static void
+load_calendars (CalendarClient *client,
+ CalendarEventType type)
+{
+ GSList *l, *clients;
+
+ switch (type)
+ {
+ case CALENDAR_EVENT_APPOINTMENT:
+ clients = client->priv->appointment_sources;
+ break;
+ case CALENDAR_EVENT_TASK:
+ clients = client->priv->task_sources;
+ break;
+ case CALENDAR_EVENT_ALL:
+ default:
+ g_assert_not_reached ();
+ }
+
+ for (l = clients; l != NULL; l = l->next)
+ {
+ if (type == CALENDAR_EVENT_APPOINTMENT)
+ calendar_client_update_appointments (client);
+ else if (type == CALENDAR_EVENT_TASK)
+ calendar_client_update_tasks (client);
+ }
+}
+
+static void
+calendar_client_init (CalendarClient *client)
+{
+ GList *list;
+
+ client->priv = calendar_client_get_instance_private (client);
+
+ client->priv->calendar_sources = calendar_sources_get ();
+
+ list = calendar_sources_get_appointment_clients (client->priv->calendar_sources);
+ client->priv->appointment_sources =
+ calendar_client_update_sources_list (client, NULL, list, signals [APPOINTMENTS_CHANGED]);
+ g_list_free (list);
+
+ list = calendar_sources_get_task_clients (client->priv->calendar_sources);
+ client->priv->task_sources = calendar_client_update_sources_list (client, NULL, list, signals [TASKS_CHANGED]);
+ g_list_free (list);
+
+ /* set the timezone before loading the clients */
+ calendar_client_set_timezone (client);
+ load_calendars (client, CALENDAR_EVENT_APPOINTMENT);
+ load_calendars (client, CALENDAR_EVENT_TASK);
+
+ g_signal_connect_swapped (client->priv->calendar_sources,
+ "appointment-sources-changed",
+ G_CALLBACK (calendar_client_appointment_sources_changed),
+ client);
+ g_signal_connect_swapped (client->priv->calendar_sources,
+ "task-sources-changed",
+ G_CALLBACK (calendar_client_task_sources_changed),
+ client);
+
+ if (client->priv->calendar_settings != NULL)
+ client->priv->zone_listener = g_signal_connect (client->priv->calendar_settings,
+ "changed::timezone",
+ G_CALLBACK (calendar_client_timezone_changed_cb),
+ client);
+
+ client->priv->day = G_MAXUINT;
+ client->priv->month = G_MAXUINT;
+ client->priv->year = G_MAXUINT;
+}
+
+static void
+calendar_client_finalize (GObject *object)
+{
+ CalendarClient *client = CALENDAR_CLIENT (object);
+ GSList *l;
+
+ if (client->priv->zone_listener)
+ {
+ g_signal_handler_disconnect (client->priv->calendar_settings,
+ client->priv->zone_listener);
+ client->priv->zone_listener = 0;
+ }
+
+ if (client->priv->calendar_settings)
+ g_object_unref (client->priv->calendar_settings);
+ client->priv->calendar_settings = NULL;
+
+ for (l = client->priv->appointment_sources; l; l = l->next)
+ {
+ calendar_client_source_finalize (l->data);
+ g_free (l->data);
+ }
+ g_slist_free (client->priv->appointment_sources);
+ client->priv->appointment_sources = NULL;
+
+ for (l = client->priv->task_sources; l; l = l->next)
+ {
+ calendar_client_source_finalize (l->data);
+ g_free (l->data);
+ }
+ g_slist_free (client->priv->task_sources);
+ client->priv->task_sources = NULL;
+
+ if (client->priv->calendar_sources)
+ g_object_unref (client->priv->calendar_sources);
+ client->priv->calendar_sources = NULL;
+
+ G_OBJECT_CLASS (calendar_client_parent_class)->finalize (object);
+}
+
+static void
+calendar_client_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CalendarClient *client = CALENDAR_CLIENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_DAY:
+ calendar_client_select_day (client, g_value_get_uint (value));
+ break;
+ case PROP_MONTH:
+ calendar_client_select_month (client,
+ g_value_get_uint (value),
+ client->priv->year);
+ break;
+ case PROP_YEAR:
+ calendar_client_select_month (client,
+ client->priv->month,
+ g_value_get_uint (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+calendar_client_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CalendarClient *client = CALENDAR_CLIENT (object);
+
+ switch (prop_id)
+ {
+ case PROP_DAY:
+ g_value_set_uint (value, client->priv->day);
+ break;
+ case PROP_MONTH:
+ g_value_set_uint (value, client->priv->month);
+ break;
+ case PROP_YEAR:
+ g_value_set_uint (value, client->priv->year);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+CalendarClient *
+calendar_client_new (GSettings *settings)
+{
+ CalendarClient *client = g_object_new (CALENDAR_TYPE_CLIENT, NULL);
+
+ /* Use the provided MATE panel settings instead of Evolution settings */
+ if (settings) {
+ client->priv->calendar_settings = g_object_ref (settings);
+ } else {
+ /* Fallback to Evolution calendar settings if available */
+ GSettingsSchemaSource *schema_source = g_settings_schema_source_get_default();
+ const gchar *evolution_calendar_schema = "org.gnome.evolution.calendar";
+ GSettingsSchema *schema = g_settings_schema_source_lookup (schema_source, evolution_calendar_schema, FALSE);
+ if (schema) {
+ client->priv->calendar_settings = g_settings_new (evolution_calendar_schema);
+ g_settings_schema_unref (schema);
+ } else {
+ /* No Evolution settings available, calendar_settings will remain NULL */
+ client->priv->calendar_settings = NULL;
+ }
+ }
+
+ return client;
+}
+
+/* @day and @month can happily be out of range as
+ * mktime() will normalize them correctly. From mktime(3):
+ *
+ * "If structure members are outside their legal interval,
+ * they will be normalized (so that, e.g., 40 October is
+ * changed into 9 November)."
+ *
+ * "What?", you say, "Something useful in libc?"
+ */
+static inline time_t
+make_time_for_day_begin (int day,
+ int month,
+ int year)
+{
+ struct tm localtime_tm = { 0, };
+
+ localtime_tm.tm_mday = day;
+ localtime_tm.tm_mon = month;
+ localtime_tm.tm_year = year - 1900;
+ localtime_tm.tm_isdst = -1;
+
+ return mktime (&localtime_tm);
+}
+
+static inline char *
+make_isodate_for_day_begin (int day,
+ int month,
+ int year)
+{
+ time_t utctime;
+
+ utctime = make_time_for_day_begin (day, month, year);
+
+ return utctime != -1 ? isodate_from_time_t (utctime) : NULL;
+}
+
+static time_t
+get_time_from_property (ICalComponent *icomp,
+ ICalPropertyKind prop_kind,
+ ICalTime * (* get_prop_func) (ICalProperty *prop),
+ ICalTimezone *default_zone)
+{
+ ICalProperty *prop;
+ ICalTime *ical_time;
+ ICalParameter *param;
+ ICalTimezone *timezone;
+ time_t retval;
+
+ prop = i_cal_component_get_first_property (icomp, prop_kind);
+ if (!prop)
+ return 0;
+
+ param = i_cal_property_get_first_parameter (prop, I_CAL_TZID_PARAMETER);
+ ical_time = get_prop_func (prop);
+ g_object_unref (prop);
+
+ if (param)
+ {
+ const char *tzid;
+
+ tzid = i_cal_parameter_get_tzid (param);
+ timezone = i_cal_timezone_get_builtin_timezone_from_tzid (tzid);
+ g_object_unref (param);
+
+ /* If timezone lookup failed, fall back to default zone */
+ if (!timezone) {
+ timezone = default_zone;
+ }
+ }
+ else if (i_cal_time_is_utc (ical_time))
+ {
+ timezone = i_cal_timezone_get_utc_timezone ();
+ }
+ else
+ {
+ timezone = default_zone;
+ }
+
+ retval = i_cal_time_as_timet_with_zone (ical_time, timezone);
+
+ g_object_unref (ical_time);
+
+ return retval;
+}
+
+static char *
+get_component_uid (ICalComponent *component)
+{
+ return g_strdup (i_cal_component_get_uid (component));
+}
+
+static char *
+get_component_rid (ICalComponent *component)
+{
+ ICalProperty *prop;
+ ICalTime *time;
+ char *rid;
+
+ prop = i_cal_component_get_first_property (component, I_CAL_RECURRENCEID_PROPERTY);
+ if (!prop)
+ return NULL;
+
+ time = i_cal_property_get_recurrenceid (prop);
+ g_object_unref (prop);
+
+ if (!i_cal_time_is_valid_time (time) || i_cal_time_is_null_time (time))
+ {
+ g_object_unref (time);
+ return NULL;
+ }
+
+ rid = g_strdup (i_cal_time_as_ical_string (time));
+ g_object_unref (time);
+
+ return rid;
+}
+
+static char *
+get_component_summary (ICalComponent *component)
+{
+ ICalProperty *prop;
+ char *summary;
+
+ prop = i_cal_component_get_first_property (component, I_CAL_SUMMARY_PROPERTY);
+ if (!prop)
+ return NULL;
+
+ summary = g_strdup (i_cal_property_get_summary (prop));
+ g_object_unref (prop);
+
+ return summary;
+}
+
+static char *
+get_component_description (ICalComponent *component)
+{
+ ICalProperty *prop;
+ char *description;
+
+ prop = i_cal_component_get_first_property (component, I_CAL_DESCRIPTION_PROPERTY);
+ if (!prop)
+ return NULL;
+
+ description = g_strdup (i_cal_property_get_description (prop));
+ g_object_unref (prop);
+
+ return description;
+}
+
+static inline time_t
+get_component_start_time (ICalComponent *component,
+ ICalTimezone *default_zone)
+{
+ return get_time_from_property (component,
+ I_CAL_DTSTART_PROPERTY,
+ i_cal_property_get_dtstart,
+ default_zone);
+}
+
+static inline time_t
+get_component_end_time (ICalComponent *component,
+ ICalTimezone *default_zone)
+{
+ return get_time_from_property (component,
+ I_CAL_DTEND_PROPERTY,
+ i_cal_property_get_dtend,
+ default_zone);
+}
+
+static gboolean
+get_component_is_all_day (ICalComponent *component,
+ time_t start_time,
+ ICalTimezone *default_zone)
+{
+ ICalTime *dtstart;
+ struct tm *start_tm;
+ time_t end_time;
+ ICalProperty *prop;
+ ICalDuration *duration;
+ gboolean is_all_day;
+
+ dtstart = i_cal_component_get_dtstart (component);
+
+ if (dtstart && i_cal_time_is_date (dtstart))
+ {
+ g_object_unref (dtstart);
+ return TRUE;
+ }
+
+ g_object_unref (dtstart);
+
+ start_tm = gmtime (&start_time);
+ if (start_tm->tm_sec != 0 ||
+ start_tm->tm_min != 0 ||
+ start_tm->tm_hour != 0)
+ return FALSE;
+
+ if ((end_time = get_component_end_time (component, default_zone)))
+ return (end_time - start_time) % 86400 == 0;
+
+ prop = i_cal_component_get_first_property (component, I_CAL_DURATION_PROPERTY);
+ if (!prop)
+ return FALSE;
+
+ duration = i_cal_property_get_duration (prop);
+ g_object_unref (prop);
+
+ is_all_day = i_cal_duration_as_int (duration) % 86400 == 0;
+ g_object_unref (duration);
+
+ return is_all_day;
+}
+
+static inline time_t
+get_component_due_time (ICalComponent *component,
+ ICalTimezone *default_zone)
+{
+ return get_time_from_property (component,
+ I_CAL_DUE_PROPERTY,
+ i_cal_property_get_due,
+ default_zone);
+}
+
+static guint
+get_component_percent_complete (ICalComponent *component)
+{
+ ICalPropertyStatus status;
+ ICalProperty *prop;
+ int percent_complete;
+
+ status = i_cal_component_get_status (component);
+ if (status == I_CAL_STATUS_COMPLETED)
+ return 100;
+
+ prop = i_cal_component_get_first_property (component, I_CAL_COMPLETED_PROPERTY);
+
+ if (prop)
+ {
+ g_object_unref (prop);
+ return 100;
+ }
+
+ prop = i_cal_component_get_first_property (component, I_CAL_PERCENTCOMPLETE_PROPERTY);
+ if (!prop)
+ return 0;
+
+ percent_complete = i_cal_property_get_percentcomplete (prop);
+ g_object_unref (prop);
+
+ return CLAMP (percent_complete, 0, 100);
+}
+
+static inline time_t
+get_component_completed_time (ICalComponent *component,
+ ICalTimezone *default_zone)
+{
+ return get_time_from_property (component,
+ I_CAL_COMPLETED_PROPERTY,
+ i_cal_property_get_completed,
+ default_zone);
+}
+
+static int
+get_component_priority (ICalComponent *component)
+{
+ ICalProperty *prop;
+ int priority;
+
+ prop = i_cal_component_get_first_property (component, I_CAL_PRIORITY_PROPERTY);
+ if (!prop)
+ return -1;
+
+ priority = i_cal_property_get_priority (prop);
+ g_object_unref (prop);
+
+ return priority;
+}
+
+static char *
+get_source_color (ECalClient *esource)
+{
+ ESource *source;
+ ECalClientSourceType source_type;
+ ESourceSelectable *extension;
+ const gchar *extension_name;
+
+ g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL);
+
+ source = e_client_get_source (E_CLIENT (esource));
+ source_type = e_cal_client_get_source_type (esource);
+
+ switch (source_type)
+ {
+ case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
+ extension_name = E_SOURCE_EXTENSION_CALENDAR;
+ break;
+ case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+ extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+ break;
+ case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+ case E_CAL_CLIENT_SOURCE_TYPE_LAST:
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ extension = e_source_get_extension (source, extension_name);
+
+ return e_source_selectable_dup_color (extension);
+}
+
+static gchar *
+get_source_backend_name (ECalClient *esource)
+{
+ ESource *source;
+ ECalClientSourceType source_type;
+ ESourceBackend *extension;
+ const gchar *extension_name;
+
+ g_return_val_if_fail (E_IS_CAL_CLIENT (esource), NULL);
+
+ source = e_client_get_source (E_CLIENT (esource));
+ source_type = e_cal_client_get_source_type (esource);
+
+ switch (source_type)
+ {
+ case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
+ extension_name = E_SOURCE_EXTENSION_CALENDAR;
+ break;
+ case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+ extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+ break;
+ case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+ case E_CAL_CLIENT_SOURCE_TYPE_LAST:
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ extension = e_source_get_extension (source, extension_name);
+
+ return e_source_backend_dup_backend_name (extension);
+}
+
+static inline gboolean
+calendar_appointment_equal (CalendarAppointment *a,
+ CalendarAppointment *b)
+{
+ GSList *la, *lb;
+
+ if (g_slist_length (a->occurrences) != g_slist_length (b->occurrences))
+ return FALSE;
+
+ for (la = a->occurrences, lb = b->occurrences; la && lb; la = la->next, lb = lb->next)
+ {
+ CalendarOccurrence *oa = la->data;
+ CalendarOccurrence *ob = lb->data;
+
+ if (oa->start_time != ob->start_time ||
+ oa->end_time != ob->end_time)
+ return FALSE;
+ }
+
+ return
+ g_strcmp0 (a->uid, b->uid) == 0 &&
+ g_strcmp0 (a->backend_name, b->backend_name) == 0 &&
+ g_strcmp0 (a->summary, b->summary) == 0 &&
+ g_strcmp0 (a->description, b->description) == 0 &&
+ g_strcmp0 (a->color_string, b->color_string) == 0 &&
+ a->start_time == b->start_time &&
+ a->end_time == b->end_time &&
+ a->is_all_day == b->is_all_day;
+}
+
+static void
+calendar_appointment_copy (CalendarAppointment *appointment,
+ CalendarAppointment *appointment_copy)
+{
+ GSList *l;
+
+ g_assert (appointment != NULL);
+ g_assert (appointment_copy != NULL);
+
+ appointment_copy->occurrences = g_slist_copy (appointment->occurrences);
+ for (l = appointment_copy->occurrences; l; l = l->next)
+ {
+ CalendarOccurrence *occurrence = l->data;
+ CalendarOccurrence *occurrence_copy;
+
+ occurrence_copy = g_new0 (CalendarOccurrence, 1);
+ occurrence_copy->start_time = occurrence->start_time;
+ occurrence_copy->end_time = occurrence->end_time;
+
+ l->data = occurrence_copy;
+ }
+
+ appointment_copy->uid = g_strdup (appointment->uid);
+ appointment_copy->backend_name = g_strdup (appointment->backend_name);
+ appointment_copy->summary = g_strdup (appointment->summary);
+ appointment_copy->description = g_strdup (appointment->description);
+ appointment_copy->color_string = g_strdup (appointment->color_string);
+ appointment_copy->start_time = appointment->start_time;
+ appointment_copy->end_time = appointment->end_time;
+ appointment_copy->is_all_day = appointment->is_all_day;
+}
+
+static void
+calendar_appointment_finalize (CalendarAppointment *appointment)
+{
+ GSList *l;
+
+ for (l = appointment->occurrences; l; l = l->next)
+ g_free (l->data);
+ g_slist_free (appointment->occurrences);
+ appointment->occurrences = NULL;
+
+ g_free (appointment->uid);
+ appointment->uid = NULL;
+
+ g_free (appointment->rid);
+ appointment->rid = NULL;
+
+ g_free (appointment->backend_name);
+ appointment->backend_name = NULL;
+
+ g_free (appointment->summary);
+ appointment->summary = NULL;
+
+ g_free (appointment->description);
+ appointment->description = NULL;
+
+ g_free (appointment->color_string);
+ appointment->color_string = NULL;
+
+ appointment->start_time = 0;
+ appointment->is_all_day = FALSE;
+}
+
+static void
+calendar_appointment_init (CalendarAppointment *appointment,
+ ICalComponent *component,
+ CalendarClientSource *source,
+ ICalTimezone *default_zone)
+{
+ appointment->uid = get_component_uid (component);
+ appointment->rid = get_component_rid (component);
+ appointment->backend_name = get_source_backend_name (source->source);
+ appointment->summary = get_component_summary (component);
+ appointment->description = get_component_description (component);
+ appointment->color_string = get_source_color (source->source);
+ appointment->start_time = get_component_start_time (component, default_zone);
+ appointment->end_time = get_component_end_time (component, default_zone);
+ appointment->is_all_day = get_component_is_all_day (component,
+ appointment->start_time,
+ default_zone);
+}
+
+static inline gboolean
+calendar_task_equal (CalendarTask *a,
+ CalendarTask *b)
+{
+ return
+ g_strcmp0 (a->uid, b->uid) == 0 &&
+ g_strcmp0 (a->summary, b->summary) == 0 &&
+ g_strcmp0 (a->description, b->description) == 0 &&
+ g_strcmp0 (a->color_string, b->color_string) == 0 &&
+ a->start_time == b->start_time &&
+ a->due_time == b->due_time &&
+ a->percent_complete == b->percent_complete &&
+ a->completed_time == b->completed_time &&
+ a->priority == b->priority;
+}
+
+static void
+calendar_task_copy (CalendarTask *task,
+ CalendarTask *task_copy)
+{
+ g_assert (task != NULL);
+ g_assert (task_copy != NULL);
+
+ task_copy->uid = g_strdup (task->uid);
+ task_copy->summary = g_strdup (task->summary);
+ task_copy->description = g_strdup (task->description);
+ task_copy->color_string = g_strdup (task->color_string);
+ task_copy->start_time = task->start_time;
+ task_copy->due_time = task->due_time;
+ task_copy->percent_complete = task->percent_complete;
+ task_copy->completed_time = task->completed_time;
+ task_copy->priority = task->priority;
+}
+
+static void
+calendar_task_finalize (CalendarTask *task)
+{
+ g_free (task->uid);
+ task->uid = NULL;
+
+ g_free (task->summary);
+ task->summary = NULL;
+
+ g_free (task->description);
+ task->description = NULL;
+
+ g_free (task->color_string);
+ task->color_string = NULL;
+
+ task->percent_complete = 0;
+}
+
+static void
+calendar_task_init (CalendarTask *task,
+ ICalComponent *component,
+ CalendarClientSource *source,
+ ICalTimezone *default_zone)
+{
+ task->uid = get_component_uid (component);
+ task->summary = get_component_summary (component);
+ task->description = get_component_description (component);
+ task->color_string = get_source_color (source->source);
+ task->start_time = get_component_start_time (component, default_zone);
+ task->due_time = get_component_due_time (component, default_zone);
+ task->percent_complete = get_component_percent_complete (component);
+ task->completed_time = get_component_completed_time (component, default_zone);
+ task->priority = get_component_priority (component);
+}
+
+void
+calendar_event_free (CalendarEvent *event)
+{
+ switch (event->type)
+ {
+ case CALENDAR_EVENT_APPOINTMENT:
+ calendar_appointment_finalize (CALENDAR_APPOINTMENT (event));
+ break;
+ case CALENDAR_EVENT_TASK:
+ calendar_task_finalize (CALENDAR_TASK (event));
+ break;
+ case CALENDAR_EVENT_ALL:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ g_free (event);
+}
+
+static CalendarEvent *
+calendar_event_new (ICalComponent *component,
+ CalendarClientSource *source,
+ ICalTimezone *default_zone)
+{
+ CalendarEvent *event;
+ ICalComponentKind component_kind;
+
+ event = g_new0 (CalendarEvent, 1);
+ component_kind = i_cal_component_isa (component);
+
+ if (component_kind == I_CAL_VEVENT_COMPONENT)
+ {
+ event->type = CALENDAR_EVENT_APPOINTMENT;
+ calendar_appointment_init (CALENDAR_APPOINTMENT (event),
+ component, source, default_zone);
+ }
+ else if (component_kind == I_CAL_VTODO_COMPONENT)
+ {
+ event->type = CALENDAR_EVENT_TASK;
+ calendar_task_init (CALENDAR_TASK (event),
+ component, source, default_zone);
+ }
+ else
+ {
+ g_warning ("Unknown calendar component type: %d\n", component_kind);
+ g_free (event);
+
+ return NULL;
+ }
+
+ return event;
+}
+
+static CalendarEvent *
+calendar_event_copy (CalendarEvent *event)
+{
+ CalendarEvent *retval;
+
+ if (!event)
+ return NULL;
+
+ retval = g_new0 (CalendarEvent, 1);
+
+ retval->type = event->type;
+
+ switch (event->type)
+ {
+ case CALENDAR_EVENT_APPOINTMENT:
+ calendar_appointment_copy (CALENDAR_APPOINTMENT (event),
+ CALENDAR_APPOINTMENT (retval));
+ break;
+ case CALENDAR_EVENT_TASK:
+ calendar_task_copy (CALENDAR_TASK (event),
+ CALENDAR_TASK (retval));
+ break;
+ case CALENDAR_EVENT_ALL:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return retval;
+}
+
+static char *
+calendar_event_get_uid (CalendarEvent *event)
+{
+ switch (event->type)
+ {
+ case CALENDAR_EVENT_APPOINTMENT:
+ return g_strdup_printf ("%s%s", CALENDAR_APPOINTMENT (event)->uid, CALENDAR_APPOINTMENT (event)->rid ? CALENDAR_APPOINTMENT (event)->rid : "");
+ break;
+ case CALENDAR_EVENT_TASK:
+ return g_strdup (CALENDAR_TASK (event)->uid);
+ break;
+ case CALENDAR_EVENT_ALL:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ return NULL;
+}
+
+static gboolean
+calendar_event_equal (CalendarEvent *a,
+ CalendarEvent *b)
+{
+ if (!a && !b)
+ return TRUE;
+
+ if ((a && !b) || (!a && b))
+ return FALSE;
+
+ if (a->type != b->type)
+ return FALSE;
+
+ switch (a->type)
+ {
+ case CALENDAR_EVENT_APPOINTMENT:
+ return calendar_appointment_equal (CALENDAR_APPOINTMENT (a),
+ CALENDAR_APPOINTMENT (b));
+ case CALENDAR_EVENT_TASK:
+ return calendar_task_equal (CALENDAR_TASK (a),
+ CALENDAR_TASK (b));
+ case CALENDAR_EVENT_ALL:
+ default:
+ break;
+ }
+
+ g_assert_not_reached ();
+
+ return FALSE;
+}
+
+static inline void
+calendar_event_debug_dump (CalendarEvent *event)
+{
+#ifdef CALENDAR_ENABLE_DEBUG
+ switch (event->type)
+ {
+ case CALENDAR_EVENT_APPOINTMENT:
+ {
+ char *start_str;
+ char *end_str;
+ GSList *l;
+
+ start_str = CALENDAR_APPOINTMENT (event)->start_time ?
+ isodate_from_time_t (CALENDAR_APPOINTMENT (event)->start_time) :
+ g_strdup ("(undefined)");
+ end_str = CALENDAR_APPOINTMENT (event)->end_time ?
+ isodate_from_time_t (CALENDAR_APPOINTMENT (event)->end_time) :
+ g_strdup ("(undefined)");
+
+ g_free (start_str);
+ g_free (end_str);
+
+ for (l = CALENDAR_APPOINTMENT (event)->occurrences; l; l = l->next)
+ {
+ CalendarOccurrence *occurrence = l->data;
+
+ start_str = occurrence->start_time ?
+ isodate_from_time_t (occurrence->start_time) :
+ g_strdup ("(undefined)");
+
+ end_str = occurrence->end_time ?
+ isodate_from_time_t (occurrence->end_time) :
+ g_strdup ("(undefined)");
+
+ g_free (start_str);
+ g_free (end_str);
+ }
+ }
+ break;
+ case CALENDAR_EVENT_TASK:
+ {
+ char *start_str;
+ char *due_str;
+ char *completed_str;
+
+ start_str = CALENDAR_TASK (event)->start_time ?
+ isodate_from_time_t (CALENDAR_TASK (event)->start_time) :
+ g_strdup ("(undefined)");
+ due_str = CALENDAR_TASK (event)->due_time ?
+ isodate_from_time_t (CALENDAR_TASK (event)->due_time) :
+ g_strdup ("(undefined)");
+ completed_str = CALENDAR_TASK (event)->completed_time ?
+ isodate_from_time_t (CALENDAR_TASK (event)->completed_time) :
+ g_strdup ("(undefined)");
+
+ g_free (completed_str);
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+#endif
+}
+
+static inline CalendarClientQuery *
+goddamn_this_is_crack (CalendarClientSource *source,
+ ECalClientView *view,
+ gboolean *emit_signal)
+{
+ g_assert (view != NULL);
+
+ if (source->completed_query.view == view)
+ {
+ if (emit_signal)
+ *emit_signal = TRUE;
+ return &source->completed_query;
+ }
+ else if (source->in_progress_query.view == view)
+ {
+ if (emit_signal)
+ *emit_signal = FALSE;
+ return &source->in_progress_query;
+ }
+
+ g_assert_not_reached ();
+
+ return NULL;
+}
+
+static void
+calendar_client_query_finalize (CalendarClientQuery *query)
+{
+ if (query->view)
+ g_object_unref (query->view);
+ query->view = NULL;
+
+ if (query->events)
+ g_hash_table_destroy (query->events);
+ query->events = NULL;
+}
+
+static void
+calendar_client_stop_query (CalendarClient *client,
+ CalendarClientSource *source,
+ CalendarClientQuery *query)
+{
+ if (query == &source->in_progress_query)
+ {
+ g_assert (source->query_in_progress != FALSE);
+
+ source->query_in_progress = FALSE;
+ }
+ else if (query == &source->completed_query)
+ {
+ g_assert (source->query_completed != FALSE);
+
+ source->query_completed = FALSE;
+ }
+ else
+ g_assert_not_reached ();
+
+ calendar_client_query_finalize (query);
+}
+
+typedef struct {
+ CalendarClient *client;
+ CalendarClientSource *source;
+ time_t start_time;
+ time_t end_time;
+ gboolean events_changed;
+ ICalTimezone *system_timezone;
+} InstanceGenerationData;
+
+static gboolean
+calendar_client_instance_cb (ICalComponent *icomp,
+ ICalTime *instance_start,
+ ICalTime *instance_end,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ InstanceGenerationData *data = user_data;
+ CalendarEvent *event;
+ CalendarEvent *old_event;
+ char *uid;
+ time_t start_time_t, end_time_t;
+
+ /* Convert instance times from their timezone to local time */
+ ICalTimezone *event_tz = i_cal_time_get_timezone(instance_start);
+
+ ICalTime *local_start = i_cal_time_clone(instance_start);
+ ICalTime *local_end = i_cal_time_clone(instance_end);
+
+ /* Convert to local timezone */
+ if (event_tz && data->system_timezone && event_tz != data->system_timezone) {
+ i_cal_time_convert_timezone(local_start, event_tz, data->system_timezone);
+ i_cal_time_convert_timezone(local_end, event_tz, data->system_timezone);
+ }
+
+ start_time_t = i_cal_time_as_timet (local_start);
+ end_time_t = i_cal_time_as_timet (local_end);
+
+ g_object_unref(local_start);
+ g_object_unref(local_end);
+
+ /* Create event from the component */
+ event = calendar_event_new (icomp, data->source, data->client->priv->zone);
+ if (!event)
+ return TRUE;
+
+ /* Override the times with the instance times (already converted to local timezone) */
+ if (event->type == CALENDAR_EVENT_APPOINTMENT) {
+ CALENDAR_APPOINTMENT (event)->start_time = start_time_t;
+ CALENDAR_APPOINTMENT (event)->end_time = end_time_t;
+
+ /* Create a single occurrence for this instance */
+ CalendarOccurrence *occurrence = g_new0 (CalendarOccurrence, 1);
+ occurrence->start_time = start_time_t;
+ occurrence->end_time = end_time_t;
+ CALENDAR_APPOINTMENT (event)->occurrences = g_slist_prepend (NULL, occurrence);
+ }
+
+ uid = calendar_event_get_uid (event);
+ old_event = g_hash_table_lookup (data->source->in_progress_query.events, uid);
+
+ if (!calendar_event_equal (event, old_event)) {
+ calendar_event_debug_dump (event);
+ g_hash_table_replace (data->source->in_progress_query.events, uid, event);
+ data->events_changed = TRUE;
+ } else {
+ calendar_event_free (event);
+ g_free (uid);
+ }
+
+ return TRUE;
+}
+
+static void
+calendar_client_start_query (CalendarClient *client,
+ CalendarClientSource *source,
+ const char *query)
+{
+ time_t month_begin, month_end;
+ GSList *objects = NULL;
+ GError *error = NULL;
+ InstanceGenerationData instance_data;
+
+ /* Validate that client is properly initialized */
+ if (client->priv->month == G_MAXUINT || client->priv->year == G_MAXUINT) {
+ return;
+ }
+
+ /* Calculate time range */
+ month_begin = make_time_for_day_begin (1, client->priv->month, client->priv->year);
+
+ /* Handle year rollover when month is December (11) */
+ if (client->priv->month == 11) { /* December */
+ month_end = make_time_for_day_begin (1, 0, client->priv->year + 1); /* January next year */
+ } else {
+ month_end = make_time_for_day_begin (1, client->priv->month + 1, client->priv->year);
+ }
+
+ /* Validate time range */
+ if (month_begin == -1 || month_end == -1) {
+ g_warning ("Invalid time range: month_begin=%ld, month_end=%ld", (long)month_begin, (long)month_end);
+ calendar_client_stop_query (client, source, &source->in_progress_query);
+ return;
+ }
+
+ if (source->query_in_progress)
+ calendar_client_stop_query (client, source, &source->in_progress_query);
+
+ source->query_in_progress = TRUE;
+ source->in_progress_query.view = NULL; /* No view needed for instance generation */
+ source->in_progress_query.events = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ (GDestroyNotify) calendar_event_free);
+
+ /* Get all objects for the month using query */
+ if (!e_cal_client_get_object_list_sync (source->source, query, &objects, NULL, &error)) {
+ g_warning ("Error getting calendar objects: %s", error->message);
+ g_error_free (error);
+ calendar_client_stop_query (client, source, &source->in_progress_query);
+ return;
+ }
+
+ /* Get system timezone once for all instances */
+ SystemTimezone *systz = system_timezone_new();
+ const char *system_tz_name = system_timezone_get(systz);
+ ICalTimezone *system_timezone = i_cal_timezone_get_builtin_timezone(system_tz_name);
+ g_object_unref(systz);
+
+ /* Set up instance generation data */
+ instance_data.client = client;
+ instance_data.source = source;
+ instance_data.start_time = month_begin;
+ instance_data.end_time = month_end;
+ instance_data.events_changed = FALSE;
+ instance_data.system_timezone = system_timezone;
+
+ /* Generate instances for each object with automatic timezone conversion */
+ for (GSList *l = objects; l; l = l->next) {
+ ICalComponent *component = l->data;
+
+ /* Some instances of recurring events may yield negative months - I think these are safe to skip */
+ if (month_begin < 0 || month_end < 0) {
+ continue;
+ }
+
+ e_cal_client_generate_instances_for_object_sync (source->source,
+ component,
+ month_begin,
+ month_end,
+ NULL, /* cancellable */
+ calendar_client_instance_cb,
+ &instance_data);
+ }
+
+ g_slist_free_full (objects, g_object_unref);
+
+ /* Query is now completed */
+ calendar_client_query_finalize (&source->completed_query);
+ source->completed_query = source->in_progress_query;
+ source->query_completed = TRUE;
+
+ source->query_in_progress = FALSE;
+ source->in_progress_query.view = NULL;
+ source->in_progress_query.events = NULL;
+
+ /* Emit signal to capture changed events */
+ if (instance_data.events_changed) {
+ g_signal_emit (source->client, source->changed_signal_id, 0);
+ }
+}
+
+static void
+calendar_client_update_appointments (CalendarClient *client)
+{
+ GSList *l;
+ char *query;
+ char *month_begin;
+ char *month_end;
+
+ if (client->priv->month == G_MAXUINT || client->priv->year == G_MAXUINT)
+ return;
+
+ month_begin = make_isodate_for_day_begin (1,
+ client->priv->month,
+ client->priv->year);
+
+ month_end = make_isodate_for_day_begin (1,
+ client->priv->month + 1,
+ client->priv->year);
+
+ query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") "
+ "(make-time \"%s\")",
+ month_begin, month_end);
+
+ for (l = client->priv->appointment_sources; l; l = l->next)
+ {
+ CalendarClientSource *cs = l->data;
+
+ calendar_client_start_query (client, cs, query);
+ }
+
+ g_free (month_begin);
+ g_free (month_end);
+ g_free (query);
+}
+
+/* FIXME:
+ * perhaps we should use evo's "hide_completed_tasks" pref?
+ */
+static void
+calendar_client_update_tasks (CalendarClient *client)
+{
+ GSList *l;
+ char *query;
+
+#ifdef FIX_BROKEN_TASKS_QUERY
+ /* FIXME: this doesn't work for tasks without a start or
+ * due date
+ * Look at filter_task() to see the behaviour we
+ * want.
+ */
+
+ char *day_begin;
+ char *day_end;
+
+ if (client->priv->day == G_MAXUINT ||
+ client->priv->month == G_MAXUINT ||
+ client->priv->year == G_MAXUINT)
+ return;
+
+ day_begin = make_isodate_for_day_begin (client->priv->day,
+ client->priv->month,
+ client->priv->year);
+
+ day_end = make_isodate_for_day_begin (client->priv->day + 1,
+ client->priv->month,
+ client->priv->year);
+ if (!day_begin || !day_end)
+ {
+ g_warning ("Cannot run query with invalid date: %dd %dy %dm\n",
+ client->priv->day,
+ client->priv->month,
+ client->priv->year);
+ g_free (day_begin);
+ g_free (day_end);
+ return;
+ }
+
+ query = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\") "
+ "(make-time \"%s\")) "
+ "(or (not is-completed?) "
+ "(and (is-completed?) "
+ "(not (completed-before? (make-time \"%s\"))))))",
+ day_begin, day_end, day_begin);
+#else
+ query = g_strdup ("#t");
+#endif /* FIX_BROKEN_TASKS_QUERY */
+
+ for (l = client->priv->task_sources; l; l = l->next)
+ {
+ CalendarClientSource *cs = l->data;
+
+ calendar_client_start_query (client, cs, query);
+ }
+
+#ifdef FIX_BROKEN_TASKS_QUERY
+ g_free (day_begin);
+ g_free (day_end);
+#endif
+ g_free (query);
+}
+
+static void
+calendar_client_source_finalize (CalendarClientSource *source)
+{
+ source->client = NULL;
+
+ if (source->source) {
+ g_object_unref (source->source);
+ }
+ source->source = NULL;
+
+ calendar_client_query_finalize (&source->completed_query);
+ calendar_client_query_finalize (&source->in_progress_query);
+
+ source->query_completed = FALSE;
+ source->query_in_progress = FALSE;
+}
+
+static int
+compare_calendar_sources (CalendarClientSource *s1,
+ CalendarClientSource *s2)
+{
+ return (s1->source == s2->source) ? 0 : 1;
+}
+
+static GSList *
+calendar_client_update_sources_list (CalendarClient *client,
+ GSList *sources,
+ GList *esources,
+ guint changed_signal_id)
+{
+ GList *link;
+ GSList *retval, *l;
+
+ retval = NULL;
+
+ for (link = esources; link != NULL; link = g_list_next (link))
+ {
+ CalendarClientSource dummy_source;
+ CalendarClientSource *new_source;
+ GSList *s;
+ ECalClient *esource = link->data;
+
+ dummy_source.source = esource;
+
+ if ((s = g_slist_find_custom (sources,
+ &dummy_source,
+ (GCompareFunc) compare_calendar_sources)))
+ {
+ new_source = s->data;
+ sources = g_slist_delete_link (sources, s);
+ }
+ else
+ {
+ new_source = g_new0 (CalendarClientSource, 1);
+ new_source->client = client;
+ new_source->source = g_object_ref (esource);
+ new_source->changed_signal_id = changed_signal_id;
+ }
+
+ retval = g_slist_prepend (retval, new_source);
+ }
+
+ for (l = sources; l; l = l->next)
+ {
+ CalendarClientSource *source = l->data;
+
+ calendar_client_source_finalize (source);
+ g_free (source);
+ }
+ g_slist_free (sources);
+
+ return retval;
+}
+
+static void
+calendar_client_appointment_sources_changed (CalendarClient *client)
+{
+ GList *list;
+
+ list = calendar_sources_get_appointment_clients (client->priv->calendar_sources);
+
+ client->priv->appointment_sources = calendar_client_update_sources_list (client,
+ client->priv->appointment_sources,
+ list,
+ signals [APPOINTMENTS_CHANGED]);
+
+ load_calendars (client, CALENDAR_EVENT_APPOINTMENT);
+ calendar_client_update_appointments (client);
+
+ g_list_free (list);
+}
+
+static void
+calendar_client_task_sources_changed (CalendarClient *client)
+{
+ GList *list;
+
+ list = calendar_sources_get_task_clients (client->priv->calendar_sources);
+
+ client->priv->task_sources = calendar_client_update_sources_list (client,
+ client->priv->task_sources,
+ list,
+ signals [TASKS_CHANGED]);
+
+ load_calendars (client, CALENDAR_EVENT_TASK);
+ calendar_client_update_tasks (client);
+
+ g_list_free (list);
+}
+
+void
+calendar_client_get_date (CalendarClient *client,
+ guint *year,
+ guint *month,
+ guint *day)
+{
+ g_return_if_fail (CALENDAR_IS_CLIENT (client));
+
+ if (year)
+ *year = client->priv->year;
+
+ if (month)
+ *month = client->priv->month;
+
+ if (day)
+ *day = client->priv->day;
+}
+
+void
+calendar_client_select_month (CalendarClient *client,
+ guint month,
+ guint year)
+{
+ g_return_if_fail (CALENDAR_IS_CLIENT (client));
+ g_return_if_fail (month <= 11);
+
+ if (client->priv->year != year || client->priv->month != month)
+ {
+ client->priv->month = month;
+ client->priv->year = year;
+
+ calendar_client_update_appointments (client);
+ calendar_client_update_tasks (client);
+
+ g_object_freeze_notify (G_OBJECT (client));
+ g_object_notify (G_OBJECT (client), "month");
+ g_object_notify (G_OBJECT (client), "year");
+ g_object_thaw_notify (G_OBJECT (client));
+ }
+}
+
+void
+calendar_client_select_day (CalendarClient *client,
+ guint day)
+{
+ g_return_if_fail (CALENDAR_IS_CLIENT (client));
+ g_return_if_fail (day <= 31);
+
+ if (client->priv->day != day)
+ {
+ client->priv->day = day;
+
+ /* don't need to update appointments unless
+ * the selected month changes
+ */
+#ifdef FIX_BROKEN_TASKS_QUERY
+ calendar_client_update_tasks (client);
+#endif
+
+ g_object_notify (G_OBJECT (client), "day");
+ }
+}
+
+typedef struct
+{
+ CalendarClient *client;
+ GSList *events;
+ time_t start_time;
+ time_t end_time;
+} FilterData;
+
+typedef void (* CalendarEventFilterFunc) (const char *uid,
+ CalendarEvent *event,
+ FilterData *filter_data);
+
+static void
+filter_appointment (const char *uid,
+ CalendarEvent *event,
+ FilterData *filter_data)
+{
+ GSList *occurrences, *l;
+
+ if (event->type != CALENDAR_EVENT_APPOINTMENT)
+ return;
+
+ occurrences = CALENDAR_APPOINTMENT (event)->occurrences;
+ CALENDAR_APPOINTMENT (event)->occurrences = NULL;
+
+ for (l = occurrences; l; l = l->next)
+ {
+ CalendarOccurrence *occurrence = l->data;
+ time_t start_time = occurrence->start_time;
+ time_t end_time = occurrence->end_time;
+
+ if ((start_time >= filter_data->start_time &&
+ start_time < filter_data->end_time) ||
+ (start_time <= filter_data->start_time &&
+ (end_time - 1) > filter_data->start_time))
+ {
+ CalendarEvent *new_event;
+
+ new_event = calendar_event_copy (event);
+
+ CALENDAR_APPOINTMENT (new_event)->start_time = occurrence->start_time;
+ CALENDAR_APPOINTMENT (new_event)->end_time = occurrence->end_time;
+
+ filter_data->events = g_slist_prepend (filter_data->events, new_event);
+ }
+ }
+
+ CALENDAR_APPOINTMENT (event)->occurrences = occurrences;
+}
+
+static void
+filter_task (const char *uid,
+ CalendarEvent *event,
+ FilterData *filter_data)
+{
+#ifdef FIX_BROKEN_TASKS_QUERY
+ CalendarTask *task;
+#endif
+
+ if (event->type != CALENDAR_EVENT_TASK)
+ return;
+
+#ifdef FIX_BROKEN_TASKS_QUERY
+ task = CALENDAR_TASK (event);
+
+ if (task->start_time && task->start_time > filter_data->start_time)
+ return;
+
+ if (task->completed_time &&
+ (task->completed_time < filter_data->start_time ||
+ task->completed_time > filter_data->end_time))
+ return;
+#endif /* FIX_BROKEN_TASKS_QUERY */
+
+ filter_data->events = g_slist_prepend (filter_data->events,
+ calendar_event_copy (event));
+}
+
+static GSList *
+calendar_client_filter_events (CalendarClient *client,
+ GSList *sources,
+ CalendarEventFilterFunc filter_func,
+ time_t start_time,
+ time_t end_time)
+{
+ FilterData filter_data;
+ GSList *l;
+ GSList *retval;
+
+ if (!sources)
+ return NULL;
+
+ filter_data.client = client;
+ filter_data.events = NULL;
+ filter_data.start_time = start_time;
+ filter_data.end_time = end_time;
+
+ retval = NULL;
+ for (l = sources; l; l = l->next)
+ {
+ CalendarClientSource *source = l->data;
+
+ if (source->query_completed)
+ {
+ filter_data.events = NULL;
+ g_hash_table_foreach (source->completed_query.events,
+ (GHFunc) filter_func,
+ &filter_data);
+
+ filter_data.events = g_slist_reverse (filter_data.events);
+
+ retval = g_slist_concat (retval, filter_data.events);
+ }
+ }
+
+ return retval;
+}
+
+GSList *
+calendar_client_get_events (CalendarClient *client,
+ CalendarEventType event_mask)
+{
+ GSList *appointments;
+ GSList *tasks;
+ time_t day_begin;
+ time_t day_end;
+
+ g_return_val_if_fail (CALENDAR_IS_CLIENT (client), NULL);
+ g_return_val_if_fail (client->priv->day != G_MAXUINT, NULL);
+ g_return_val_if_fail (client->priv->month != G_MAXUINT, NULL);
+ g_return_val_if_fail (client->priv->year != G_MAXUINT, NULL);
+
+ day_begin = make_time_for_day_begin (client->priv->day,
+ client->priv->month,
+ client->priv->year);
+ day_end = make_time_for_day_begin (client->priv->day + 1,
+ client->priv->month,
+ client->priv->year);
+
+ appointments = NULL;
+ if (event_mask & CALENDAR_EVENT_APPOINTMENT)
+ {
+ appointments = calendar_client_filter_events (client,
+ client->priv->appointment_sources,
+ filter_appointment,
+ day_begin,
+ day_end);
+ }
+
+ tasks = NULL;
+ if (event_mask & CALENDAR_EVENT_TASK)
+ {
+ tasks = calendar_client_filter_events (client,
+ client->priv->task_sources,
+ filter_task,
+ day_begin,
+ day_end);
+ }
+
+ return g_slist_concat (appointments, tasks);
+}
+
+static inline int
+day_from_time_t (time_t t)
+{
+ struct tm *tm = localtime (&t);
+
+ g_assert (tm == NULL || (tm->tm_mday >=1 && tm->tm_mday <= 31));
+
+ return tm ? tm->tm_mday : 0;
+}
+
+void
+calendar_client_foreach_appointment_day (CalendarClient *client,
+ CalendarDayIter iter_func,
+ gpointer user_data)
+{
+ GSList *appointments, *l;
+ gboolean marked_days [32] = { FALSE, };
+ time_t month_begin;
+ time_t month_end;
+ int i;
+
+ g_return_if_fail (CALENDAR_IS_CLIENT (client));
+ g_return_if_fail (iter_func != NULL);
+ g_return_if_fail (client->priv->month != G_MAXUINT);
+ g_return_if_fail (client->priv->year != G_MAXUINT);
+
+ month_begin = make_time_for_day_begin (1,
+ client->priv->month,
+ client->priv->year);
+ month_end = make_time_for_day_begin (1,
+ client->priv->month + 1,
+ client->priv->year);
+
+ appointments = calendar_client_filter_events (client,
+ client->priv->appointment_sources,
+ filter_appointment,
+ month_begin,
+ month_end);
+ for (l = appointments; l; l = l->next)
+ {
+ CalendarAppointment *appointment = l->data;
+
+ if (appointment->start_time)
+ {
+ time_t day_time = appointment->start_time;
+
+ if (day_time >= month_begin)
+ marked_days [day_from_time_t (day_time)] = TRUE;
+
+ if (appointment->end_time)
+ {
+ int day_offset;
+ int duration = appointment->end_time - appointment->start_time;
+ /* mark the days for the appointment, no need to add an extra one when duration is a multiple of 86400 */
+ for (day_offset = 1; day_offset <= duration / 86400 && duration != day_offset * 86400; day_offset++)
+ {
+ time_t day_tm = appointment->start_time + day_offset * 86400;
+
+ if (day_tm > month_end)
+ break;
+ if (day_tm >= month_begin)
+ marked_days [day_from_time_t (day_tm)] = TRUE;
+ }
+ }
+ }
+ calendar_event_free (CALENDAR_EVENT (appointment));
+ }
+
+ g_slist_free (appointments);
+
+ for (i = 1; i < 32; i++)
+ {
+ if (marked_days [i])
+ iter_func (client, i, user_data);
+ }
+}
+
+void
+calendar_client_set_task_completed (CalendarClient *client,
+ char *task_uid,
+ gboolean task_completed,
+ guint percent_complete)
+{
+ GSList *l;
+ ECalClient *esource;
+ ICalComponent *component;
+ ICalProperty *prop;
+ ICalPropertyStatus status;
+
+ g_return_if_fail (CALENDAR_IS_CLIENT (client));
+ g_return_if_fail (task_uid != NULL);
+ g_return_if_fail (task_completed == FALSE || percent_complete == 100);
+
+ component = NULL;
+ esource = NULL;
+ for (l = client->priv->task_sources; l; l = l->next)
+ {
+ CalendarClientSource *source = l->data;
+
+ esource = source->source;
+ e_cal_client_get_object_sync (esource, task_uid, NULL, &component, NULL, NULL);
+ if (component)
+ break;
+ }
+
+ if (!component)
+ {
+ g_warning ("Cannot locate task with uid = '%s'\n", task_uid);
+ return;
+ }
+
+ g_assert (esource != NULL);
+
+ /* Completed time */
+ prop = i_cal_component_get_first_property (component, I_CAL_COMPLETED_PROPERTY);
+ if (task_completed)
+ {
+ ICalTime *completed_time;
+
+ completed_time = i_cal_time_new_current_with_zone (client->priv->zone);
+ if (!prop)
+ {
+ i_cal_component_take_property (component,
+ i_cal_property_new_completed (completed_time));
+ }
+ else
+ {
+ i_cal_property_set_completed (prop, completed_time);
+ }
+ }
+ else if (prop)
+ {
+ i_cal_component_remove_property (component, prop);
+ }
+ g_clear_object (&prop);
+
+ /* Percent complete */
+ prop = i_cal_component_get_first_property (component, I_CAL_PERCENTCOMPLETE_PROPERTY);
+ if (!prop)
+ {
+ i_cal_component_take_property (component,
+ i_cal_property_new_percentcomplete (percent_complete));
+ }
+ else
+ {
+ i_cal_property_set_percentcomplete (prop, percent_complete);
+ }
+ g_clear_object (&prop);
+
+ /* Status */
+ status = task_completed ? I_CAL_STATUS_COMPLETED : I_CAL_STATUS_NEEDSACTION;
+ prop = i_cal_component_get_first_property (component, I_CAL_STATUS_PROPERTY);
+ if (prop)
+ {
+ i_cal_property_set_status (prop, status);
+ }
+ else
+ {
+ i_cal_component_take_property (component, i_cal_property_new_status (status));
+ }
+ g_clear_object (&prop);
+
+ e_cal_client_modify_object_sync (esource,
+ component,
+ E_CAL_OBJ_MOD_ALL,
+ 0,
+ NULL,
+ NULL);
+}
+
+gboolean
+calendar_client_create_task (CalendarClient *client,
+ const char *summary)
+{
+ GSList *l;
+ ECalClient *task_client = NULL;
+ ICalComponent *vtodo_component;
+ gchar *uid;
+ GError *error = NULL;
+ gboolean success = FALSE;
+
+ g_return_val_if_fail (CALENDAR_IS_CLIENT (client), FALSE);
+ g_return_val_if_fail (summary != NULL && *summary != '\0', FALSE);
+
+ /* Use the first available task source (like the existing code does) */
+ for (l = client->priv->task_sources; l; l = l->next)
+ {
+ CalendarClientSource *source = l->data;
+ task_client = source->source;
+ if (task_client)
+ break;
+ }
+
+ if (!task_client)
+ {
+ g_warning ("No task client available for task creation");
+ return FALSE;
+ }
+
+ /* Create a simple VTODO component */
+ vtodo_component = i_cal_component_new (I_CAL_VTODO_COMPONENT);
+
+ /* Generate UID */
+ uid = e_util_generate_uid ();
+ i_cal_component_set_uid (vtodo_component, uid);
+ g_free (uid);
+
+ /* Set summary */
+ i_cal_component_set_summary (vtodo_component, summary);
+
+ /* Set created time */
+ ICalTime *now = i_cal_time_new_current_with_zone (client->priv->zone);
+ i_cal_component_set_dtstamp (vtodo_component, now);
+ g_object_unref (now);
+
+ /* Create the task */
+ success = e_cal_client_create_object_sync (task_client,
+ vtodo_component,
+ E_CAL_OPERATION_FLAG_NONE,
+ NULL, /* out uid */
+ NULL, /* cancellable */
+ &error);
+
+ if (error)
+ {
+ g_warning ("Failed to create task: %s", error->message);
+ g_error_free (error);
+ success = FALSE;
+ }
+
+ /* Cleanup */
+ g_object_unref (vtodo_component);
+
+ return success;
+}
+
+#endif /* HAVE_EDS */
diff --git a/applets/clock/calendar-client.h b/applets/clock/calendar-client.h
new file mode 100644
index 00000000..7747d23f
--- /dev/null
+++ b/applets/clock/calendar-client.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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:
+ * Mark McLoughlin <[email protected]>
+ * William Jon McCann <[email protected]>
+ * Martin Grimme <[email protected]>
+ * Christian Kellner <[email protected]>
+ */
+
+#ifndef __CALENDAR_CLIENT_H__
+#define __CALENDAR_CLIENT_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ CALENDAR_EVENT_APPOINTMENT = 1 << 0,
+ CALENDAR_EVENT_TASK = 1 << 1,
+ CALENDAR_EVENT_ALL = (1 << 2) - 1
+} CalendarEventType;
+
+#define CALENDAR_TYPE_CLIENT (calendar_client_get_type ())
+#define CALENDAR_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_CLIENT, CalendarClient))
+#define CALENDAR_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_CLIENT, CalendarClientClass))
+#define CALENDAR_IS_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_CLIENT))
+#define CALENDAR_IS_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_CLIENT))
+#define CALENDAR_CLIENT_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_CLIENT, CalendarClientClass))
+
+typedef struct _CalendarClient CalendarClient;
+typedef struct _CalendarClientClass CalendarClientClass;
+typedef struct _CalendarClientPrivate CalendarClientPrivate;
+
+struct _CalendarClient
+{
+ GObject parent;
+ CalendarClientPrivate *priv;
+};
+
+struct _CalendarClientClass
+{
+ GObjectClass parent_class;
+
+ void (* appointments_changed) (CalendarClient *client);
+ void (* tasks_changed) (CalendarClient *client);
+};
+
+
+typedef struct
+{
+ time_t start_time;
+ time_t end_time;
+} CalendarOccurrence;
+
+typedef struct
+{
+ char *uid;
+ char *rid;
+ char *backend_name;
+ char *summary;
+ char *description;
+ char *color_string;
+ time_t start_time;
+ time_t end_time;
+ guint is_all_day : 1;
+
+ /* Only used internally */
+ GSList *occurrences;
+} CalendarAppointment;
+
+typedef struct
+{
+ char *uid;
+ char *summary;
+ char *description;
+ char *color_string;
+ char *url;
+ time_t start_time;
+ time_t due_time;
+ guint percent_complete;
+ time_t completed_time;
+ int priority;
+} CalendarTask;
+
+typedef struct
+{
+ union
+ {
+ CalendarAppointment appointment;
+ CalendarTask task;
+ } event;
+ CalendarEventType type;
+} CalendarEvent;
+
+#define CALENDAR_EVENT(e) ((CalendarEvent *)(e))
+#define CALENDAR_APPOINTMENT(e) ((CalendarAppointment *)(e))
+#define CALENDAR_TASK(e) ((CalendarTask *)(e))
+
+typedef void (* CalendarDayIter) (CalendarClient *client,
+ guint day,
+ gpointer user_data);
+
+
+GType calendar_client_get_type (void) G_GNUC_CONST;
+CalendarClient *calendar_client_new (GSettings *settings);
+
+void calendar_client_get_date (CalendarClient *client,
+ guint *year,
+ guint *month,
+ guint *day);
+void calendar_client_select_month (CalendarClient *client,
+ guint month,
+ guint year);
+void calendar_client_select_day (CalendarClient *client,
+ guint day);
+
+GSList *calendar_client_get_events (CalendarClient *client,
+ CalendarEventType event_mask);
+void calendar_client_foreach_appointment_day (CalendarClient *client,
+ CalendarDayIter iter_func,
+ gpointer user_data);
+
+void calendar_client_set_task_completed (CalendarClient *client,
+ char *task_uid,
+ gboolean task_completed,
+ guint percent_complete);
+
+gboolean calendar_client_create_task (CalendarClient *client,
+ const char *summary);
+
+void calendar_event_free (CalendarEvent *event);
+
+G_END_DECLS
+
+#endif /* __CALENDAR_CLIENT_H__ */
diff --git a/applets/clock/calendar-debug.h b/applets/clock/calendar-debug.h
new file mode 100644
index 00000000..39befd74
--- /dev/null
+++ b/applets/clock/calendar-debug.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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:
+ * Mark McLoughlin <[email protected]>
+ */
+
+#ifndef __CALENDAR_DEBUG_H__
+#define __CALENDAR_DEBUG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#ifdef CALENDAR_ENABLE_DEBUG
+
+#include <stdio.h>
+
+#ifdef G_HAVE_ISO_VARARGS
+# define dprintf(...) fprintf (stderr, __VA_ARGS__);
+#elif defined(G_HAVE_GNUC_VARARGS)
+# define dprintf(args...) fprintf (stderr, args);
+#endif
+
+#else /* if !defined (CALENDAR_DEBUG) */
+
+#ifdef G_HAVE_ISO_VARARGS
+# define dprintf(...)
+#elif defined(G_HAVE_GNUC_VARARGS)
+# define dprintf(args...)
+#endif
+
+#endif /* CALENDAR_ENABLE_DEBUG */
+
+G_END_DECLS
+
+#endif /* __CALENDAR_DEBUG_H__ */
diff --git a/applets/clock/calendar-sources.c b/applets/clock/calendar-sources.c
new file mode 100644
index 00000000..54c6dc8c
--- /dev/null
+++ b/applets/clock/calendar-sources.c
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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:
+ * Mark McLoughlin <[email protected]>
+ * William Jon McCann <[email protected]>
+ * Martin Grimme <[email protected]>
+ * Christian Kellner <[email protected]>
+ */
+
+#include <config.h>
+
+#include "calendar-sources.h"
+
+#include <libintl.h>
+#include <string.h>
+#define HANDLE_LIBICAL_MEMORY
+
+
+#include <libecal/libecal.h>
+
+#undef CALENDAR_ENABLE_DEBUG
+#include "calendar-debug.h"
+
+#ifndef _
+#define _(x) gettext(x)
+#endif
+
+#ifndef N_
+#define N_(x) x
+#endif
+
+typedef struct _ClientData ClientData;
+typedef struct _CalendarSourceData CalendarSourceData;
+
+struct _ClientData
+{
+ ECalClient *client;
+ gulong backend_died_id;
+};
+
+struct _CalendarSourceData
+{
+ ECalClientSourceType source_type;
+ CalendarSources *sources;
+ guint changed_signal;
+
+ /* ESource -> EClient */
+ GHashTable *clients;
+
+ guint timeout_id;
+
+ guint loaded : 1;
+};
+
+struct _CalendarSourcesPrivate
+{
+ ESourceRegistry *registry;
+ gulong source_added_id;
+ gulong source_changed_id;
+ gulong source_removed_id;
+
+ CalendarSourceData appointment_sources;
+ CalendarSourceData task_sources;
+};
+
+static void calendar_sources_finalize (GObject *object);
+
+static void backend_died_cb (EClient *client, CalendarSourceData *source_data);
+static void calendar_sources_registry_source_changed_cb (ESourceRegistry *registry,
+ ESource *source,
+ CalendarSources *sources);
+static void calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
+ ESource *source,
+ CalendarSources *sources);
+
+enum
+{
+ APPOINTMENT_SOURCES_CHANGED,
+ TASK_SOURCES_CHANGED,
+ LAST_SIGNAL
+};
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static CalendarSources *calendar_sources_singleton = NULL;
+
+static void
+client_data_free (ClientData *data)
+{
+ g_signal_handler_disconnect (data->client, data->backend_died_id);
+ g_object_unref (data->client);
+ g_free (data);
+}
+
+G_DEFINE_TYPE_WITH_PRIVATE (CalendarSources, calendar_sources, G_TYPE_OBJECT)
+
+static void
+calendar_sources_class_init (CalendarSourcesClass *klass)
+{
+ GObjectClass *gobject_class = (GObjectClass *) klass;
+
+ gobject_class->finalize = calendar_sources_finalize;
+
+ signals [APPOINTMENT_SOURCES_CHANGED] =
+ g_signal_new ("appointment-sources-changed",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CalendarSourcesClass,
+ appointment_sources_changed),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+
+ signals [TASK_SOURCES_CHANGED] =
+ g_signal_new ("task-sources-changed",
+ G_TYPE_FROM_CLASS (gobject_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (CalendarSourcesClass,
+ task_sources_changed),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+calendar_sources_init (CalendarSources *sources)
+{
+ GError *error = NULL;
+
+ sources->priv = calendar_sources_get_instance_private (sources);
+
+ /* XXX Not sure what to do if this fails.
+ * Should this class implement GInitable or pass the
+ * registry in as a G_PARAM_CONSTRUCT_ONLY property? */
+ sources->priv->registry = e_source_registry_new_sync (NULL, &error);
+ if (error != NULL) {
+ g_critical ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ sources->priv->source_added_id = g_signal_connect (sources->priv->registry,
+ "source-added",
+ G_CALLBACK (calendar_sources_registry_source_changed_cb),
+ sources);
+ sources->priv->source_changed_id = g_signal_connect (sources->priv->registry,
+ "source-changed",
+ G_CALLBACK (calendar_sources_registry_source_changed_cb),
+ sources);
+ sources->priv->source_removed_id = g_signal_connect (sources->priv->registry,
+ "source-removed",
+ G_CALLBACK (calendar_sources_registry_source_removed_cb),
+ sources);
+
+ sources->priv->appointment_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
+ sources->priv->appointment_sources.sources = sources;
+ sources->priv->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED];
+ sources->priv->appointment_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
+ (GEqualFunc) e_source_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) client_data_free);
+ sources->priv->appointment_sources.timeout_id = 0;
+
+ sources->priv->task_sources.source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
+ sources->priv->task_sources.sources = sources;
+ sources->priv->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED];
+ sources->priv->task_sources.clients = g_hash_table_new_full ((GHashFunc) e_source_hash,
+ (GEqualFunc) e_source_equal,
+ (GDestroyNotify) g_object_unref,
+ (GDestroyNotify) client_data_free);
+ sources->priv->task_sources.timeout_id = 0;
+}
+
+static void
+calendar_sources_finalize_source_data (CalendarSources *sources,
+ CalendarSourceData *source_data)
+{
+ if (source_data->loaded)
+ {
+ g_hash_table_destroy (source_data->clients);
+ source_data->clients = NULL;
+
+ if (source_data->timeout_id != 0)
+ {
+ g_source_remove (source_data->timeout_id);
+ source_data->timeout_id = 0;
+ }
+
+ source_data->loaded = FALSE;
+ }
+}
+
+static void
+calendar_sources_finalize (GObject *object)
+{
+ CalendarSources *sources = CALENDAR_SOURCES (object);
+
+ if (sources->priv->registry)
+ {
+ g_signal_handler_disconnect (sources->priv->registry,
+ sources->priv->source_added_id);
+ g_signal_handler_disconnect (sources->priv->registry,
+ sources->priv->source_changed_id);
+ g_signal_handler_disconnect (sources->priv->registry,
+ sources->priv->source_removed_id);
+ g_object_unref (sources->priv->registry);
+ }
+ sources->priv->registry = NULL;
+
+ calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources);
+ calendar_sources_finalize_source_data (sources, &sources->priv->task_sources);
+
+ G_OBJECT_CLASS (calendar_sources_parent_class)->finalize (object);
+}
+
+CalendarSources *
+calendar_sources_get (void)
+{
+ gpointer singleton_location = &calendar_sources_singleton;
+
+ if (calendar_sources_singleton)
+ return g_object_ref (calendar_sources_singleton);
+
+ calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL);
+ g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton),
+ singleton_location);
+
+ return calendar_sources_singleton;
+}
+
+/* The clients are just created here but not loaded */
+static void
+create_client_for_source (ESource *source,
+ ECalClientSourceType source_type,
+ CalendarSourceData *source_data)
+{
+ ClientData *data;
+ GError *error;
+ EClient *client;
+
+ client = g_hash_table_lookup (source_data->clients, source);
+ g_return_if_fail (client == NULL);
+
+ error = NULL;
+ client = e_cal_client_connect_sync (source, source_type, -1, NULL, &error);
+
+ if (!client)
+ {
+ g_warning ("Could not load source '%s': %s",
+ e_source_get_uid (source),
+ error->message);
+
+ g_clear_error (&error);
+ return;
+ }
+
+ data = g_new0 (ClientData, 1);
+ data->client = E_CAL_CLIENT (client); /* takes ownership */
+ data->backend_died_id = g_signal_connect (client,
+ "backend-died",
+ G_CALLBACK (backend_died_cb),
+ source_data);
+
+ g_hash_table_insert (source_data->clients, g_object_ref (source), data);
+}
+
+static inline void
+debug_dump_ecal_list (GHashTable *clients)
+{
+#ifdef CALENDAR_ENABLE_DEBUG
+ GList *list, *link;
+
+ dprintf ("Loaded clients:\n");
+ list = g_hash_table_get_keys (clients);
+ for (link = list; link != NULL; link = g_list_next (link))
+ {
+ ESource *source = E_SOURCE (link->data);
+
+ dprintf (" %s %s\n",
+ e_source_get_uid (source),
+ e_source_get_display_name (source));
+ }
+#endif
+}
+
+static void
+calendar_sources_load_esource_list (ESourceRegistry *registry,
+ CalendarSourceData *source_data);
+
+static gboolean
+backend_restart (gpointer data)
+{
+ CalendarSourceData *source_data = data;
+ ESourceRegistry *registry;
+
+ registry = source_data->sources->priv->registry;
+ calendar_sources_load_esource_list (registry, source_data);
+ g_signal_emit (source_data->sources, source_data->changed_signal, 0);
+
+ source_data->timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+backend_died_cb (EClient *client, CalendarSourceData *source_data)
+{
+ ESource *source;
+ const char *display_name;
+
+ source = e_client_get_source (client);
+ display_name = e_source_get_display_name (source);
+ g_warning ("The calendar backend for '%s' has crashed.", display_name);
+ g_hash_table_remove (source_data->clients, source);
+
+ if (source_data->timeout_id != 0)
+ {
+ g_source_remove (source_data->timeout_id);
+ source_data->timeout_id = 0;
+ }
+
+ source_data->timeout_id = g_timeout_add_seconds (2, backend_restart,
+ source_data);
+}
+
+static void
+calendar_sources_load_esource_list (ESourceRegistry *registry,
+ CalendarSourceData *source_data)
+{
+ GList *list, *link;
+ const gchar *extension_name;
+
+ switch (source_data->source_type)
+ {
+ case E_CAL_CLIENT_SOURCE_TYPE_EVENTS:
+ extension_name = E_SOURCE_EXTENSION_CALENDAR;
+ break;
+ case E_CAL_CLIENT_SOURCE_TYPE_TASKS:
+ extension_name = E_SOURCE_EXTENSION_TASK_LIST;
+ break;
+ case E_CAL_CLIENT_SOURCE_TYPE_MEMOS:
+ case E_CAL_CLIENT_SOURCE_TYPE_LAST:
+ default:
+ g_return_if_reached ();
+ }
+
+ list = e_source_registry_list_sources (registry, extension_name);
+
+ for (link = list; link != NULL; link = g_list_next (link))
+ {
+ ESource *source = E_SOURCE (link->data);
+ ESourceSelectable *extension;
+ gboolean show_source;
+
+ extension = e_source_get_extension (source, extension_name);
+ show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension);
+
+ if (show_source)
+ create_client_for_source (source, source_data->source_type, source_data);
+ }
+
+ debug_dump_ecal_list (source_data->clients);
+
+ g_list_free_full (list, g_object_unref);
+}
+
+static void
+calendar_sources_registry_source_changed_cb (ESourceRegistry *registry,
+ ESource *source,
+ CalendarSources *sources)
+{
+ if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
+ {
+ CalendarSourceData *source_data;
+ ESourceSelectable *extension;
+ gboolean have_client;
+ gboolean show_source;
+
+ source_data = &sources->priv->appointment_sources;
+ extension = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR);
+ have_client = (g_hash_table_lookup (source_data->clients, source) != NULL);
+ show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension);
+
+ if (!show_source && have_client)
+ {
+ g_hash_table_remove (source_data->clients, source);
+ g_signal_emit (sources, source_data->changed_signal, 0);
+ }
+ if (show_source && !have_client)
+ {
+ create_client_for_source (source, source_data->source_type, source_data);
+ g_signal_emit (sources, source_data->changed_signal, 0);
+ }
+ }
+
+ if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
+ {
+ CalendarSourceData *source_data;
+ ESourceSelectable *extension;
+ gboolean have_client;
+ gboolean show_source;
+
+ source_data = &sources->priv->task_sources;
+ extension = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
+ have_client = (g_hash_table_lookup (source_data->clients, source) != NULL);
+ show_source = e_source_get_enabled (source) && e_source_selectable_get_selected (extension);
+
+ if (!show_source && have_client)
+ {
+ g_hash_table_remove (source_data->clients, source);
+ g_signal_emit (sources, source_data->changed_signal, 0);
+ }
+ if (show_source && !have_client)
+ {
+ create_client_for_source (source, source_data->source_type, source_data);
+ g_signal_emit (sources, source_data->changed_signal, 0);
+ }
+ }
+}
+
+static void
+calendar_sources_registry_source_removed_cb (ESourceRegistry *registry,
+ ESource *source,
+ CalendarSources *sources)
+{
+ if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
+ {
+ CalendarSourceData *source_data;
+
+ source_data = &sources->priv->appointment_sources;
+ g_hash_table_remove (source_data->clients, source);
+ g_signal_emit (sources, source_data->changed_signal, 0);
+ }
+
+ if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
+ {
+ CalendarSourceData *source_data;
+
+ source_data = &sources->priv->task_sources;
+ g_hash_table_remove (source_data->clients, source);
+ g_signal_emit (sources, source_data->changed_signal, 0);
+ }
+}
+
+GList *
+calendar_sources_get_appointment_clients (CalendarSources *sources)
+{
+ GList *list, *link;
+
+ g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
+
+ if (!sources->priv->appointment_sources.loaded)
+ {
+ calendar_sources_load_esource_list (sources->priv->registry,
+ &sources->priv->appointment_sources);
+ sources->priv->appointment_sources.loaded = TRUE;
+ }
+
+ list = g_hash_table_get_values (sources->priv->appointment_sources.clients);
+
+ for (link = list; link != NULL; link = g_list_next (link))
+ link->data = ((ClientData *) link->data)->client;
+
+ return list;
+}
+
+GList *
+calendar_sources_get_task_clients (CalendarSources *sources)
+{
+ GList *list, *link;
+
+ g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
+
+ if (!sources->priv->task_sources.loaded)
+ {
+ calendar_sources_load_esource_list (sources->priv->registry,
+ &sources->priv->task_sources);
+ sources->priv->task_sources.loaded = TRUE;
+ }
+
+ list = g_hash_table_get_values (sources->priv->task_sources.clients);
+
+ for (link = list; link != NULL; link = g_list_next (link))
+ link->data = ((ClientData *) link->data)->client;
+
+ return list;
+}
diff --git a/applets/clock/calendar-sources.h b/applets/clock/calendar-sources.h
new file mode 100644
index 00000000..1dfc7445
--- /dev/null
+++ b/applets/clock/calendar-sources.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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:
+ * Mark McLoughlin <[email protected]>
+ * William Jon McCann <[email protected]>
+ * Martin Grimme <[email protected]>
+ * Christian Kellner <[email protected]>
+ */
+
+#ifndef __CALENDAR_SOURCES_H__
+#define __CALENDAR_SOURCES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define CALENDAR_TYPE_SOURCES (calendar_sources_get_type ())
+#define CALENDAR_SOURCES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_SOURCES, CalendarSources))
+#define CALENDAR_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_SOURCES, CalendarSourcesClass))
+#define CALENDAR_IS_SOURCES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_SOURCES))
+#define CALENDAR_IS_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_SOURCES))
+#define CALENDAR_SOURCES_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesClass))
+
+typedef struct _CalendarSources CalendarSources;
+typedef struct _CalendarSourcesClass CalendarSourcesClass;
+typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate;
+
+struct _CalendarSources
+{
+ GObject parent;
+ CalendarSourcesPrivate *priv;
+};
+
+struct _CalendarSourcesClass
+{
+ GObjectClass parent_class;
+
+ void (* appointment_sources_changed) (CalendarSources *sources);
+ void (* task_sources_changed) (CalendarSources *sources);
+};
+
+
+GType calendar_sources_get_type (void) G_GNUC_CONST;
+CalendarSources *calendar_sources_get (void);
+GList *calendar_sources_get_appointment_clients (CalendarSources *sources);
+GList *calendar_sources_get_task_clients (CalendarSources *sources);
+
+G_END_DECLS
+
+#endif /* __CALENDAR_SOURCES_H__ */
diff --git a/applets/clock/calendar-window.c b/applets/clock/calendar-window.c
index b4157344..ae9f1d7c 100644
--- a/applets/clock/calendar-window.c
+++ b/applets/clock/calendar-window.c
@@ -38,11 +38,24 @@
#include "clock.h"
#include "clock-utils.h"
#include "clock-typebuiltins.h"
+#ifdef HAVE_EDS
+#include "calendar-client.h"
+#endif
#define KEY_LOCATIONS_EXPANDED "expand-locations"
+#ifdef HAVE_EDS
+#define KEY_SHOW_CALENDAR_EVENTS "show-calendar-events"
+#define KEY_SHOW_TASKS "show-tasks"
+#define SCHEMA_CALENDAR_APP "org.mate.desktop.default-applications.office.calendar"
+#define SCHEMA_TASKS_APP "org.mate.desktop.default-applications.office.tasks"
+#endif
+
enum {
EDIT_LOCATIONS,
+#ifdef HAVE_EDS
+ PERMISSION_READY,
+#endif
LAST_SIGNAL
};
@@ -60,6 +73,33 @@ struct _CalendarWindowPrivate {
GtkWidget *locations_list;
GSettings *settings;
+
+ /* Signal handler IDs for proper cleanup */
+ gulong calendar_month_changed_id;
+ gulong calendar_day_selected_id;
+
+#ifdef HAVE_EDS
+ ClockFormat time_format;
+
+ CalendarClient *client;
+
+ GtkWidget *appointment_list;
+
+ GtkListStore *appointments_model;
+ GtkListStore *tasks_model;
+
+ GtkTreeSelection *previous_selection;
+
+ GtkTreeModelFilter *appointments_filter;
+ GtkTreeModelFilter *tasks_filter;
+
+ GtkWidget *task_list;
+ GtkWidget *task_entry;
+
+ /* EDS-specific signal handler IDs */
+ gulong client_appointments_changed_id;
+ gulong client_tasks_changed_id;
+#endif /* HAVE_EDS */
};
G_DEFINE_TYPE_WITH_PRIVATE (CalendarWindow, calendar_window, GTK_TYPE_WINDOW)
@@ -84,6 +124,63 @@ static GtkWidget * create_hig_frame (CalendarWindow *calwin,
const char *key,
GCallback callback);
+#ifdef HAVE_EDS
+enum {
+ APPOINTMENT_COLUMN_UID,
+ APPOINTMENT_COLUMN_TYPE,
+ APPOINTMENT_COLUMN_SUMMARY,
+ APPOINTMENT_COLUMN_DESCRIPTION,
+ APPOINTMENT_COLUMN_START_TIME,
+ APPOINTMENT_COLUMN_START_TEXT,
+ APPOINTMENT_COLUMN_END_TIME,
+ APPOINTMENT_COLUMN_ALL_DAY,
+ APPOINTMENT_COLUMN_COLOR,
+ N_APPOINTMENT_COLUMNS
+};
+
+enum {
+ TASK_COLUMN_UID,
+ TASK_COLUMN_TYPE,
+ TASK_COLUMN_SUMMARY,
+ TASK_COLUMN_DESCRIPTION,
+ TASK_COLUMN_START_TIME,
+ TASK_COLUMN_START_TEXT,
+ TASK_COLUMN_DUE_TIME,
+ TASK_COLUMN_DUE_TEXT,
+ TASK_COLUMN_PERCENT_COMPLETE,
+ TASK_COLUMN_PERCENT_COMPLETE_TEXT,
+ TASK_COLUMN_COMPLETED,
+ TASK_COLUMN_COMPLETED_TIME,
+ TASK_COLUMN_PRIORITY,
+ TASK_COLUMN_COLOR,
+ N_TASK_COLUMNS
+};
+
+enum {
+ APPOINTMENT_TYPE_APPOINTMENT,
+ TASK_TYPE_TASK
+};
+
+static void calendar_window_pack_pim (CalendarWindow *calwin, GtkWidget *vbox);
+static char *format_time (ClockFormat format, time_t t, gint year, gint month, gint day);
+static void update_frame_visibility (GtkWidget *frame, GtkTreeModel *model);
+static GtkWidget *create_appointment_list (CalendarWindow *calwin, GtkWidget **tree_view, GtkWidget **scrolled_window);
+static GtkWidget *create_task_list (CalendarWindow *calwin, GtkWidget **tree_view, GtkWidget **scrolled_window);
+static void calendar_window_create_appointments_model (CalendarWindow *calwin);
+static void calendar_window_create_tasks_model (CalendarWindow *calwin);
+static void handle_appointments_changed (CalendarWindow *calwin);
+static void handle_tasks_changed (CalendarWindow *calwin);
+static void mark_day_on_calendar (CalendarClient *client, guint day, CalendarWindow *calwin);
+static gboolean is_for_filter (GtkTreeModel *model, GtkTreeIter *iter, gpointer data);
+static gboolean appointment_tooltip_query_cb (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data);
+static gboolean task_tooltip_query_cb (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data);
+static void appointment_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data);
+static void task_row_activated_cb (GtkTreeView *tree_view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer user_data);
+static void task_completion_toggled_cb (GtkCellRendererToggle *cell, gchar *path_str, CalendarWindow *calwin);
+static gboolean task_entry_key_press_cb (GtkWidget *widget, GdkEventKey *event, CalendarWindow *calwin);
+static void task_entry_activate_cb (GtkEntry *entry, CalendarWindow *calwin);
+#endif /* HAVE_EDS */
+
static void calendar_mark_today(GtkCalendar *calendar)
{
time_t now;
@@ -110,7 +207,22 @@ static gboolean calendar_update(gpointer user_data)
static void calendar_month_changed_cb(GtkCalendar *calendar, gpointer user_data)
{
gtk_calendar_clear_marks(calendar);
- g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, calendar_update, user_data, NULL);
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, calendar_update, calendar, NULL);
+
+#ifdef HAVE_EDS
+ /* Update calendar client when date changes */
+ CalendarWindow *calwin = CALENDAR_WINDOW (user_data);
+ if (calwin->priv->client) {
+ guint year, month, day;
+ gtk_calendar_get_date (calendar, &year, &month, &day);
+ calendar_client_select_month (calwin->priv->client, month, year);
+ calendar_client_select_day (calwin->priv->client, day);
+
+ /* Refresh appointments and tasks for the new date */
+ handle_appointments_changed (calwin);
+ handle_tasks_changed (calwin);
+ }
+#endif
}
static GtkWidget *
@@ -135,8 +247,10 @@ calendar_window_create_calendar (CalendarWindow *calwin)
gtk_calendar_select_day (GTK_CALENDAR (calendar), (guint) tm1.tm_mday);
calendar_mark_today (GTK_CALENDAR(calendar));
- g_signal_connect(calendar, "month-changed",
- G_CALLBACK(calendar_month_changed_cb), calendar);
+ calwin->priv->calendar_month_changed_id = g_signal_connect(calendar, "month-changed",
+ G_CALLBACK(calendar_month_changed_cb), calwin);
+ calwin->priv->calendar_day_selected_id = g_signal_connect(calendar, "day-selected",
+ G_CALLBACK(calendar_month_changed_cb), calwin);
return calendar;
}
@@ -250,6 +364,52 @@ edit_locations (CalendarWindow *calwin)
g_signal_emit (calwin, signals[EDIT_LOCATIONS], 0);
}
+#ifdef HAVE_EDS
+static gboolean
+hide_task_entry_idle (gpointer user_data)
+{
+ CalendarWindow *calwin = CALENDAR_WINDOW (user_data);
+ if (calwin->priv->task_entry) {
+ gtk_widget_hide (calwin->priv->task_entry);
+ }
+ return FALSE; /* Remove the idle source */
+}
+
+static gboolean
+focus_task_entry_idle (gpointer user_data)
+{
+ CalendarWindow *calwin = CALENDAR_WINDOW (user_data);
+ if (calwin->priv->task_entry && gtk_widget_get_visible (calwin->priv->task_entry)) {
+ gtk_widget_grab_focus (calwin->priv->task_entry);
+ }
+ return FALSE; /* Remove the idle source */
+}
+
+static void
+add_task (CalendarWindow *calwin)
+{
+ if (calwin->priv->task_entry) {
+ gtk_widget_show (calwin->priv->task_entry);
+ gtk_widget_set_can_focus (calwin->priv->task_entry, TRUE);
+ gtk_widget_set_sensitive (calwin->priv->task_entry, TRUE);
+
+ /* Make sure parent window is active */
+ gtk_window_present (GTK_WINDOW (calwin));
+
+ /* Ensure widget is realized */
+ if (!gtk_widget_get_realized (calwin->priv->task_entry)) {
+ gtk_widget_realize (calwin->priv->task_entry);
+ }
+
+ /* Try to grab focus immediately */
+ gtk_widget_grab_focus (calwin->priv->task_entry);
+
+ /* Also try to grab focus in idle callback in case immediate focus fails */
+ g_idle_add (focus_task_entry_idle, calwin);
+ }
+}
+#endif
+
static void
calendar_window_pack_locations (CalendarWindow *calwin, GtkWidget *vbox)
{
@@ -287,12 +447,22 @@ calendar_window_fill (CalendarWindow *calwin)
calwin->priv->calendar = calendar_window_create_calendar (calwin);
gtk_widget_show (calwin->priv->calendar);
+#ifdef HAVE_EDS
+ /* Calendar client will be initialized later in calendar_window_pack_pim */
+#endif
+
if (!calwin->priv->invert_order) {
gtk_box_pack_start (GTK_BOX (vbox),
calwin->priv->calendar, TRUE, FALSE, 0);
+#ifdef HAVE_EDS
+ calendar_window_pack_pim (calwin, vbox);
+#endif
calendar_window_pack_locations (calwin, vbox);
} else {
calendar_window_pack_locations (calwin, vbox);
+#ifdef HAVE_EDS
+ calendar_window_pack_pim (calwin, vbox);
+#endif
gtk_box_pack_start (GTK_BOX (vbox),
calwin->priv->calendar, TRUE, FALSE, 0);
}
@@ -405,6 +575,33 @@ calendar_window_dispose (GObject *object)
g_clear_pointer (&calwin->priv->prefs_path, g_free);
+ /* Disconnect calendar signals */
+ if (calwin->priv->calendar && calwin->priv->calendar_month_changed_id > 0) {
+ g_signal_handler_disconnect (calwin->priv->calendar, calwin->priv->calendar_month_changed_id);
+ calwin->priv->calendar_month_changed_id = 0;
+ }
+ if (calwin->priv->calendar && calwin->priv->calendar_day_selected_id > 0) {
+ g_signal_handler_disconnect (calwin->priv->calendar, calwin->priv->calendar_day_selected_id);
+ calwin->priv->calendar_day_selected_id = 0;
+ }
+
+#ifdef HAVE_EDS
+ /* Disconnect client signals */
+ if (calwin->priv->client) {
+ if (calwin->priv->client_appointments_changed_id > 0) {
+ g_signal_handler_disconnect (calwin->priv->client, calwin->priv->client_appointments_changed_id);
+ calwin->priv->client_appointments_changed_id = 0;
+ }
+ if (calwin->priv->client_tasks_changed_id > 0) {
+ g_signal_handler_disconnect (calwin->priv->client, calwin->priv->client_tasks_changed_id);
+ calwin->priv->client_tasks_changed_id = 0;
+ }
+ g_signal_handlers_disconnect_by_data (calwin->priv->client, calwin);
+ g_object_unref (calwin->priv->client);
+ calwin->priv->client = NULL;
+ }
+#endif
+
if (calwin->priv->settings)
g_object_unref (calwin->priv->settings);
calwin->priv->settings = NULL;
@@ -475,6 +672,13 @@ calendar_window_init (CalendarWindow *calwin)
calwin->priv = calendar_window_get_instance_private (calwin);
+#ifdef HAVE_EDS
+ /* Initialize signal handler IDs */
+ calwin->priv->calendar_month_changed_id = 0;
+ calwin->priv->calendar_day_selected_id = 0;
+ calwin->priv->client_appointments_changed_id = 0;
+#endif
+
window = GTK_WINDOW (calwin);
gtk_window_set_type_hint (window, GDK_WINDOW_TYPE_HINT_DOCK);
gtk_window_set_decorated (window, FALSE);
@@ -488,7 +692,8 @@ calendar_window_init (CalendarWindow *calwin)
GtkWidget *
calendar_window_new (time_t *static_current_time,
const char *prefs_path,
- gboolean invert_order)
+ gboolean invert_order,
+ GSettings *settings)
{
CalendarWindow *calwin;
@@ -499,6 +704,13 @@ calendar_window_new (time_t *static_current_time,
"prefs-path", prefs_path,
NULL);
+#ifdef HAVE_EDS
+ /* Store settings for calendar client initialization in init */
+ if (settings) {
+ calwin->priv->settings = g_object_ref (settings);
+ }
+#endif
+
return GTK_WIDGET (calwin);
}
@@ -506,6 +718,16 @@ void
calendar_window_refresh (CalendarWindow *calwin)
{
g_return_if_fail (CALENDAR_IS_WINDOW (calwin));
+
+#ifdef HAVE_EDS
+ if (calwin->priv->appointments_filter && calwin->priv->appointment_list)
+ gtk_tree_model_filter_refilter (calwin->priv->appointments_filter);
+
+ /* Update frame visibility based on model content */
+ if (calwin->priv->appointment_list && calwin->priv->appointments_filter)
+ update_frame_visibility (calwin->priv->appointment_list,
+ GTK_TREE_MODEL (calwin->priv->appointments_filter));
+#endif
}
gboolean
@@ -573,7 +795,11 @@ calendar_window_get_time_format (CalendarWindow *calwin)
g_return_val_if_fail (CALENDAR_IS_WINDOW (calwin),
CLOCK_FORMAT_INVALID);
+#ifdef HAVE_EDS
+ return calwin->priv->time_format;
+#else
return CLOCK_FORMAT_INVALID;
+#endif
}
static time_t *
@@ -627,7 +853,893 @@ calendar_window_set_prefs_path (CalendarWindow *calwin,
g_object_notify (G_OBJECT (calwin), "prefs-path");
- if (calwin->priv->settings)
- g_object_unref (calwin->priv->settings);
- calwin->priv->settings = g_settings_new_with_path (CLOCK_SCHEMA, calwin->priv->prefs_path);
+ /* Only create new settings if we don't already have shared settings */
+ if (!calwin->priv->settings) {
+ calwin->priv->settings = g_settings_new_with_path (CLOCK_SCHEMA, calwin->priv->prefs_path);
+ }
+}
+
+#ifdef HAVE_EDS
+
+static char *
+format_time (ClockFormat format,
+ time_t t,
+ gint year,
+ gint month,
+ gint day)
+{
+ GDateTime *dt;
+ gchar *time;
+
+ if (!t)
+ return NULL;
+
+ /* Evolution timestamps are in UTC but represent local appointment times
+ * Since TZID lookup failed, treat UTC timestamp as local time directly */
+ dt = g_date_time_new_from_unix_utc (t);
+ time = NULL;
+
+ if (!dt)
+ return NULL;
+
+ /* Always show time since we're filtering by selected date */
+ if (format == CLOCK_FORMAT_12) {
+ /* Translators: This is a strftime format string.
+ * It is used to display the time in 12-hours format
+ * (eg, like in the US: 8:10 am). The %p expands to
+ * am/pm.
+ */
+ time = g_date_time_format (dt, _("%l:%M %p"));
+ } else {
+ /* Translators: This is a strftime format string.
+ * It is used to display the time in 24-hours format
+ * (eg, like in France: 20:10).
+ */
+ time = g_date_time_format (dt, _("%H:%M"));
+ }
+
+ g_date_time_unref (dt);
+ return time;
+}
+
+static void
+update_frame_visibility (GtkWidget *frame,
+ GtkTreeModel *model)
+{
+ GtkTreeIter iter;
+ gboolean model_empty;
+
+ if (!frame)
+ return;
+
+ model_empty = !gtk_tree_model_get_iter_first (model, &iter);
+
+ if (model_empty)
+ gtk_widget_hide (frame);
+ else
+ gtk_widget_show (frame);
+}
+
+
+static void
+calendar_window_create_appointments_model (CalendarWindow *calwin)
+{
+ calwin->priv->appointments_model = gtk_list_store_new (N_APPOINTMENT_COLUMNS,
+ G_TYPE_STRING, /* APPOINTMENT_COLUMN_UID */
+ G_TYPE_INT, /* APPOINTMENT_COLUMN_TYPE */
+ G_TYPE_STRING, /* APPOINTMENT_COLUMN_SUMMARY */
+ G_TYPE_STRING, /* APPOINTMENT_COLUMN_DESCRIPTION */
+ G_TYPE_ULONG, /* APPOINTMENT_COLUMN_START_TIME */
+ G_TYPE_STRING, /* APPOINTMENT_COLUMN_START_TEXT */
+ G_TYPE_ULONG, /* APPOINTMENT_COLUMN_END_TIME */
+ G_TYPE_BOOLEAN, /* APPOINTMENT_COLUMN_ALL_DAY */
+ G_TYPE_STRING); /* APPOINTMENT_COLUMN_COLOR */
+
+ calwin->priv->appointments_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (calwin->priv->appointments_model), NULL));
+ gtk_tree_model_filter_set_visible_func (calwin->priv->appointments_filter,
+ (GtkTreeModelFilterVisibleFunc) is_for_filter,
+ GINT_TO_POINTER (APPOINTMENT_TYPE_APPOINTMENT),
+ NULL);
+}
+
+static void
+calendar_window_create_tasks_model (CalendarWindow *calwin)
+{
+ calwin->priv->tasks_model = gtk_list_store_new (N_TASK_COLUMNS,
+ G_TYPE_STRING, /* TASK_COLUMN_UID */
+ G_TYPE_INT, /* TASK_COLUMN_TYPE */
+ G_TYPE_STRING, /* TASK_COLUMN_SUMMARY */
+ G_TYPE_STRING, /* TASK_COLUMN_DESCRIPTION */
+ G_TYPE_ULONG, /* TASK_COLUMN_START_TIME */
+ G_TYPE_STRING, /* TASK_COLUMN_START_TEXT */
+ G_TYPE_ULONG, /* TASK_COLUMN_DUE_TIME */
+ G_TYPE_STRING, /* TASK_COLUMN_DUE_TEXT */
+ G_TYPE_INT, /* TASK_COLUMN_PERCENT_COMPLETE */
+ G_TYPE_STRING, /* TASK_COLUMN_PERCENT_COMPLETE_TEXT */
+ G_TYPE_BOOLEAN, /* TASK_COLUMN_COMPLETED */
+ G_TYPE_ULONG, /* TASK_COLUMN_COMPLETED_TIME */
+ G_TYPE_INT, /* TASK_COLUMN_PRIORITY */
+ G_TYPE_STRING); /* TASK_COLUMN_COLOR */
+
+ calwin->priv->tasks_filter = GTK_TREE_MODEL_FILTER (gtk_tree_model_filter_new (GTK_TREE_MODEL (calwin->priv->tasks_model), NULL));
+ gtk_tree_model_filter_set_visible_func (calwin->priv->tasks_filter,
+ (GtkTreeModelFilterVisibleFunc) is_for_filter,
+ GINT_TO_POINTER (TASK_TYPE_TASK),
+ NULL);
+}
+
+static GtkWidget *
+create_hig_calendar_frame (CalendarWindow *calwin,
+ const char *title,
+ const char *button_label,
+ const char *key,
+ GCallback callback)
+{
+ return create_hig_frame (calwin, title, button_label, key, callback);
+}
+
+
+static GtkWidget *
+create_appointment_list (CalendarWindow *calwin,
+ GtkWidget **tree_view,
+ GtkWidget **scrolled_window)
+{
+ GtkWidget *frame;
+ GtkWidget *list;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+
+ frame = create_hig_calendar_frame (calwin, _("Appointments"), NULL,
+ KEY_SHOW_CALENDAR_EVENTS, NULL);
+
+ list = gtk_tree_view_new ();
+ gtk_tree_view_set_model (GTK_TREE_VIEW (list),
+ GTK_TREE_MODEL (calwin->priv->appointments_filter));
+
+ column = gtk_tree_view_column_new ();
+ cell = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "text", APPOINTMENT_COLUMN_START_TEXT,
+ NULL);
+
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell,
+ "wrap-mode", PANGO_WRAP_WORD_CHAR,
+ "wrap-width", 200,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "text", APPOINTMENT_COLUMN_SUMMARY,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);
+
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE);
+ gtk_widget_set_has_tooltip (list, TRUE);
+ g_signal_connect (list, "query-tooltip", G_CALLBACK (appointment_tooltip_query_cb), calwin);
+ g_signal_connect (list, "row-activated", G_CALLBACK (appointment_row_activated_cb), calwin);
+
+ *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*scrolled_window),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (*scrolled_window),
+ GTK_SHADOW_IN);
+ gtk_widget_set_size_request (*scrolled_window, -1, 150);
+ gtk_container_add (GTK_CONTAINER (*scrolled_window), list);
+
+ gtk_container_add (GTK_CONTAINER (frame), *scrolled_window);
+
+ /* Ensure the scrolled window and tree view are visible */
+ gtk_widget_show (*scrolled_window);
+ gtk_widget_show (list);
+
+ /* Appointment list widgets created */
+
+ *tree_view = list;
+ return frame;
+}
+
+static GtkWidget *
+create_task_list (CalendarWindow *calwin,
+ GtkWidget **tree_view,
+ GtkWidget **scrolled_window)
+{
+ GtkWidget *frame;
+ GtkWidget *list;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+
+ frame = create_hig_calendar_frame (calwin, _("Tasks"), _("Add"),
+ KEY_SHOW_TASKS, G_CALLBACK (add_task));
+
+ list = gtk_tree_view_new ();
+ gtk_tree_view_set_model (GTK_TREE_VIEW (list),
+ GTK_TREE_MODEL (calwin->priv->tasks_filter));
+
+ column = gtk_tree_view_column_new ();
+
+ /* Completion checkbox */
+ cell = gtk_cell_renderer_toggle_new ();
+ gtk_tree_view_column_pack_start (column, cell, FALSE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "active", TASK_COLUMN_COMPLETED,
+ NULL);
+ g_signal_connect (cell, "toggled", G_CALLBACK (task_completion_toggled_cb), calwin);
+
+
+ /* Task summary */
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell,
+ "wrap-mode", PANGO_WRAP_WORD_CHAR,
+ "wrap-width", 200,
+ "ellipsize", PANGO_ELLIPSIZE_END,
+ NULL);
+ gtk_tree_view_column_pack_start (column, cell, TRUE);
+ gtk_tree_view_column_set_attributes (column, cell,
+ "text", TASK_COLUMN_SUMMARY,
+ "strikethrough", TASK_COLUMN_COMPLETED,
+ NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);
+
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (list), FALSE);
+ gtk_widget_set_has_tooltip (list, TRUE);
+ g_signal_connect (list, "query-tooltip", G_CALLBACK (task_tooltip_query_cb), calwin);
+ g_signal_connect (list, "row-activated", G_CALLBACK (task_row_activated_cb), calwin);
+
+ *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (*scrolled_window),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (*scrolled_window),
+ GTK_SHADOW_IN);
+ gtk_widget_set_size_request (*scrolled_window, -1, 150);
+ gtk_container_add (GTK_CONTAINER (*scrolled_window), list);
+
+ gtk_container_add (GTK_CONTAINER (frame), *scrolled_window);
+
+ /* Create task entry field */
+ calwin->priv->task_entry = gtk_entry_new ();
+ gtk_entry_set_placeholder_text (GTK_ENTRY (calwin->priv->task_entry), _("Enter task description..."));
+ gtk_widget_set_can_focus (calwin->priv->task_entry, TRUE);
+ gtk_widget_set_sensitive (calwin->priv->task_entry, TRUE);
+ g_signal_connect (calwin->priv->task_entry, "key-press-event", G_CALLBACK (task_entry_key_press_cb), calwin);
+ g_signal_connect (calwin->priv->task_entry, "activate", G_CALLBACK (task_entry_activate_cb), calwin);
+ gtk_container_add (GTK_CONTAINER (frame), calwin->priv->task_entry);
+
+ /* Ensure the scrolled window and tree view are visible */
+ gtk_widget_show (*scrolled_window);
+ gtk_widget_show (list);
+
+ /* Hide task entry after all show operations are complete */
+ g_idle_add (hide_task_entry_idle, calwin);
+
+ *tree_view = list;
+ return frame;
+}
+
+static void
+calendar_window_pack_pim (CalendarWindow *calwin,
+ GtkWidget *vbox)
+{
+ GtkWidget *list;
+ GtkWidget *tree_view;
+ GtkWidget *scrolled_window;
+ gboolean show_calendar_events;
+ gboolean show_tasks;
+
+ /* Check if calendar events should be shown */
+ show_calendar_events = g_settings_get_boolean (calwin->priv->settings, KEY_SHOW_CALENDAR_EVENTS);
+ show_tasks = g_settings_get_boolean (calwin->priv->settings, KEY_SHOW_TASKS);
+
+ if (!show_calendar_events && !show_tasks) {
+ return;
+ }
+
+ /* Initialize calendar client if not already done */
+ if (!calwin->priv->client && calwin->priv->settings) {
+ calwin->priv->client = calendar_client_new (calwin->priv->settings);
+
+ if (calwin->priv->client) {
+ if (show_calendar_events) {
+ calwin->priv->client_appointments_changed_id = g_signal_connect_swapped (calwin->priv->client,
+ "appointments-changed",
+ G_CALLBACK (handle_appointments_changed),
+ calwin);
+ }
+ if (show_tasks) {
+ calwin->priv->client_tasks_changed_id = g_signal_connect_swapped (calwin->priv->client,
+ "tasks-changed",
+ G_CALLBACK (handle_tasks_changed),
+ calwin);
+ }
+ }
+ }
+
+ if (!calwin->priv->client) {
+ g_warning ("Failed to create calendar client in calendar_window_pack_pim");
+ return;
+ }
+
+ /* Create and pack appointments list if enabled */
+ if (show_calendar_events) {
+ calendar_window_create_appointments_model (calwin);
+ list = create_appointment_list (calwin, &tree_view, &scrolled_window);
+ update_frame_visibility (list,
+ GTK_TREE_MODEL (calwin->priv->appointments_filter));
+ calwin->priv->appointment_list = list;
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ calwin->priv->appointment_list,
+ TRUE, TRUE, 0);
+ }
+
+ /* Create and pack tasks list if enabled */
+ if (show_tasks) {
+ calendar_window_create_tasks_model (calwin);
+ list = create_task_list (calwin, &tree_view, &scrolled_window);
+ update_frame_visibility (list,
+ GTK_TREE_MODEL (calwin->priv->tasks_filter));
+ calwin->priv->task_list = list;
+
+ gtk_box_pack_start (GTK_BOX (vbox),
+ calwin->priv->task_list,
+ TRUE, TRUE, 0);
+ }
+
+ /* Initialize calendar client with current date now that client is ready */
+ if (calwin->priv->client && calwin->priv->calendar) {
+ guint year, month, day;
+ gtk_calendar_get_date (GTK_CALENDAR (calwin->priv->calendar), &year, &month, &day);
+ /* Set a flag to indicate we're initializing to prevent redundant calls */
+ g_object_set_data (G_OBJECT (calwin), "initializing", GINT_TO_POINTER (1));
+
+ calendar_client_select_month (calwin->priv->client, month, year);
+ calendar_client_select_day (calwin->priv->client, day);
+
+ /* Clear the initialization flag and trigger initial load */
+ g_object_set_data (G_OBJECT (calwin), "initializing", GINT_TO_POINTER (0));
+
+ /* Now trigger the initial appointments and tasks load */
+ if (show_calendar_events) {
+ handle_appointments_changed (calwin);
+ }
+ if (show_tasks) {
+ handle_tasks_changed (calwin);
+ }
+ }
}
+
+static gboolean
+is_for_filter (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ gpointer data)
+{
+ gint type;
+ gint expected_type = GPOINTER_TO_INT (data);
+
+ /* Check if this is a task model or appointment model */
+ if (expected_type == TASK_TYPE_TASK) {
+ gtk_tree_model_get (model, iter, TASK_COLUMN_TYPE, &type, -1);
+ } else {
+ gtk_tree_model_get (model, iter, APPOINTMENT_COLUMN_TYPE, &type, -1);
+ }
+ return type == expected_type;
+}
+
+static void
+mark_day_on_calendar (CalendarClient *client,
+ guint day,
+ CalendarWindow *calwin)
+{
+ gtk_calendar_mark_day (GTK_CALENDAR (calwin->priv->calendar), day);
+}
+
+
+
+static gint
+compare_appointments_by_time (const CalendarAppointment *a, const CalendarAppointment *b)
+{
+ /* Sort by start time - earlier appointments first */
+ if (a->start_time < b->start_time)
+ return -1;
+ else if (a->start_time > b->start_time)
+ return 1;
+ else
+ return 0;
+}
+
+static gint
+compare_tasks_by_due_time (const CalendarTask *a, const CalendarTask *b)
+{
+ /* Sort by due time - earlier due dates first, then by priority */
+ if (a->due_time && b->due_time) {
+ if (a->due_time < b->due_time)
+ return -1;
+ else if (a->due_time > b->due_time)
+ return 1;
+ } else if (a->due_time && !b->due_time) {
+ return -1; /* Tasks with due dates come first */
+ } else if (!a->due_time && b->due_time) {
+ return 1;
+ }
+
+ /* If due times are equal or both missing, sort by priority (higher priority first) */
+ if (a->priority > b->priority)
+ return -1;
+ else if (a->priority < b->priority)
+ return 1;
+ else
+ return 0;
+}
+
+static void
+handle_appointments_changed (CalendarWindow *calwin)
+{
+ GSList *events, *l;
+ guint year, month, day;
+
+ /* Skip redundant calls during initialization */
+ if (g_object_get_data (G_OBJECT (calwin), "initializing")) {
+ return;
+ }
+
+ if (calwin->priv->calendar) {
+ gtk_calendar_clear_marks (GTK_CALENDAR (calwin->priv->calendar));
+
+ calendar_client_foreach_appointment_day (calwin->priv->client,
+ (CalendarDayIter) mark_day_on_calendar,
+ calwin);
+ }
+
+ gtk_list_store_clear (calwin->priv->appointments_model);
+
+ calendar_client_get_date (calwin->priv->client, &year, &month, &day);
+
+ events = calendar_client_get_events (calwin->priv->client,
+ CALENDAR_EVENT_APPOINTMENT);
+
+ /* Sort appointments by start time for better display order */
+ events = g_slist_sort (events, (GCompareFunc) compare_appointments_by_time);
+
+ /* Found appointments for current date */
+ for (l = events; l; l = l->next) {
+ CalendarAppointment *appointment = l->data;
+ GtkTreeIter iter;
+ char *start_text;
+
+ g_assert (CALENDAR_EVENT (appointment)->type == CALENDAR_EVENT_APPOINTMENT);
+
+ if (appointment->is_all_day)
+ start_text = g_strdup (_("All Day"));
+ else
+ start_text = format_time (calendar_window_get_time_format (calwin),
+ appointment->start_time,
+ year, month, day);
+
+ gtk_list_store_append (calwin->priv->appointments_model, &iter);
+ /* Appointment added to model */
+ gtk_list_store_set (calwin->priv->appointments_model, &iter,
+ APPOINTMENT_COLUMN_UID, appointment->uid,
+ APPOINTMENT_COLUMN_TYPE, APPOINTMENT_TYPE_APPOINTMENT,
+ APPOINTMENT_COLUMN_SUMMARY, appointment->summary,
+ APPOINTMENT_COLUMN_DESCRIPTION, appointment->description,
+ APPOINTMENT_COLUMN_START_TIME, (gint64)appointment->start_time,
+ APPOINTMENT_COLUMN_START_TEXT, start_text,
+ APPOINTMENT_COLUMN_END_TIME, (gint64)appointment->end_time,
+ APPOINTMENT_COLUMN_ALL_DAY, appointment->is_all_day,
+ APPOINTMENT_COLUMN_COLOR, appointment->color_string,
+ -1);
+
+ g_free (start_text);
+ }
+
+ /* Refresh filter before checking visibility */
+ if (calwin->priv->appointments_filter)
+ gtk_tree_model_filter_refilter (calwin->priv->appointments_filter);
+
+ update_frame_visibility (calwin->priv->appointment_list,
+ GTK_TREE_MODEL (calwin->priv->appointments_filter));
+}
+
+static void
+handle_tasks_changed (CalendarWindow *calwin)
+{
+ GSList *events, *l;
+ guint year, month, day;
+
+ /* Skip redundant calls during initialization */
+ if (g_object_get_data (G_OBJECT (calwin), "initializing")) {
+ return;
+ }
+
+ if (!calwin->priv->tasks_model) {
+ return;
+ }
+
+ gtk_list_store_clear (calwin->priv->tasks_model);
+
+ calendar_client_get_date (calwin->priv->client, &year, &month, &day);
+
+ events = calendar_client_get_events (calwin->priv->client,
+ CALENDAR_EVENT_TASK);
+
+ /* Sort tasks by due time for better display order */
+ events = g_slist_sort (events, (GCompareFunc) compare_tasks_by_due_time);
+
+ /* Found tasks for current date */
+ for (l = events; l; l = l->next) {
+ CalendarTask *task = (CalendarTask *) l->data;
+ GtkTreeIter iter;
+ char *start_text = NULL;
+ char *due_text = NULL;
+ char *percent_complete_text = NULL;
+ gboolean completed;
+
+ g_assert (CALENDAR_EVENT (task)->type == CALENDAR_EVENT_TASK);
+
+ if (task->start_time) {
+ start_text = format_time (calendar_window_get_time_format (calwin),
+ task->start_time,
+ year, month, day);
+ } else {
+ start_text = g_strdup ("");
+ }
+
+ if (task->due_time) {
+ due_text = format_time (calendar_window_get_time_format (calwin),
+ task->due_time,
+ year, month, day);
+ } else {
+ due_text = g_strdup ("");
+ }
+
+ /* Format percent complete as text */
+ if (task->percent_complete > 0) {
+ percent_complete_text = g_strdup_printf ("%d%%", task->percent_complete);
+ } else {
+ percent_complete_text = g_strdup ("");
+ }
+
+ completed = (task->percent_complete == 100);
+
+ gtk_list_store_append (calwin->priv->tasks_model, &iter);
+ gtk_list_store_set (calwin->priv->tasks_model, &iter,
+ TASK_COLUMN_UID, task->uid,
+ TASK_COLUMN_TYPE, TASK_TYPE_TASK,
+ TASK_COLUMN_SUMMARY, task->summary,
+ TASK_COLUMN_DESCRIPTION, task->description,
+ TASK_COLUMN_START_TIME, (gint64)task->start_time,
+ TASK_COLUMN_START_TEXT, start_text,
+ TASK_COLUMN_DUE_TIME, (gint64)task->due_time,
+ TASK_COLUMN_DUE_TEXT, due_text,
+ TASK_COLUMN_PERCENT_COMPLETE, task->percent_complete,
+ TASK_COLUMN_PERCENT_COMPLETE_TEXT, percent_complete_text,
+ TASK_COLUMN_COMPLETED, completed,
+ TASK_COLUMN_COMPLETED_TIME, (gint64)task->completed_time,
+ TASK_COLUMN_PRIORITY, task->priority,
+ TASK_COLUMN_COLOR, task->color_string,
+ -1);
+
+ g_free (start_text);
+ g_free (due_text);
+ g_free (percent_complete_text);
+ }
+
+ /* Refresh filter before checking visibility */
+ if (calwin->priv->tasks_filter)
+ gtk_tree_model_filter_refilter (calwin->priv->tasks_filter);
+
+ update_frame_visibility (calwin->priv->task_list,
+ GTK_TREE_MODEL (calwin->priv->tasks_filter));
+}
+
+static void
+appointment_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GAppInfo *app_info;
+ GError *error = NULL;
+
+ /* Launch Evolution calendar */
+ app_info = g_app_info_get_default_for_type ("text/calendar", FALSE);
+ if (!app_info) {
+ /* Try launching evolution directly if no calendar app is set */
+ app_info = g_app_info_create_from_commandline ("evolution -c calendar",
+ "Evolution Calendar",
+ G_APP_INFO_CREATE_NONE,
+ &error);
+ }
+
+ if (app_info) {
+ if (!g_app_info_launch (app_info, NULL, NULL, &error)) {
+ g_warning ("Failed to launch calendar application: %s", error->message);
+ g_error_free (error);
+ }
+ g_object_unref (app_info);
+ } else {
+ g_warning ("No calendar application found");
+ if (error) {
+ g_warning ("Error: %s", error->message);
+ g_error_free (error);
+ }
+ }
+}
+
+static gboolean
+appointment_tooltip_query_cb (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_mode,
+ GtkTooltip *tooltip,
+ gpointer user_data)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gchar *summary, *description, *start_text;
+ gchar *tooltip_text, *end_text;
+ gboolean all_day;
+ gulong start_time, end_time;
+
+ if (!gtk_tree_view_get_tooltip_context (tree_view, &x, &y, keyboard_mode,
+ &model, &path, &iter)) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ APPOINTMENT_COLUMN_SUMMARY, &summary,
+ APPOINTMENT_COLUMN_DESCRIPTION, &description,
+ APPOINTMENT_COLUMN_START_TEXT, &start_text,
+ APPOINTMENT_COLUMN_START_TIME, &start_time,
+ APPOINTMENT_COLUMN_END_TIME, &end_time,
+ APPOINTMENT_COLUMN_ALL_DAY, &all_day,
+ -1);
+
+ if (!summary) {
+ gtk_tree_path_free (path);
+ return FALSE;
+ }
+
+ /* Format end time */
+ if (!all_day && end_time > 0) {
+ GDateTime *end_dt = g_date_time_new_from_unix_utc (end_time);
+ if (end_dt) {
+ end_text = g_date_time_format (end_dt, "%H:%M");
+ g_date_time_unref (end_dt);
+ } else {
+ end_text = NULL;
+ }
+ } else {
+ end_text = NULL;
+ }
+
+ if (description && strlen (description) > 0) {
+ if (all_day) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\nAll Day", summary, description);
+ } else if (end_text) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\n%s - %s", summary, description, start_text ? start_text : "", end_text);
+ } else {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\n%s", summary, description, start_text ? start_text : "");
+ }
+ } else {
+ if (all_day) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\nAll Day", summary);
+ } else if (end_text) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s - %s", summary, start_text ? start_text : "", end_text);
+ } else {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s", summary, start_text ? start_text : "");
+ }
+ }
+
+ gtk_tooltip_set_markup (tooltip, tooltip_text);
+ gtk_tree_view_set_tooltip_row (tree_view, tooltip, path);
+
+ g_free (summary);
+ g_free (description);
+ g_free (start_text);
+ g_free (end_text);
+ g_free (tooltip_text);
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
+
+static gboolean
+task_tooltip_query_cb (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_mode,
+ GtkTooltip *tooltip,
+ gpointer user_data)
+{
+ GtkTreeView *tree_view = GTK_TREE_VIEW (widget);
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gchar *summary, *description, *start_text, *due_text;
+ gchar *tooltip_text;
+ gint percent_complete, priority;
+
+ if (!gtk_tree_view_get_tooltip_context (tree_view, &x, &y, keyboard_mode,
+ &model, &path, &iter)) {
+ return FALSE;
+ }
+
+ gtk_tree_model_get (model, &iter,
+ TASK_COLUMN_SUMMARY, &summary,
+ TASK_COLUMN_DESCRIPTION, &description,
+ TASK_COLUMN_START_TEXT, &start_text,
+ TASK_COLUMN_DUE_TEXT, &due_text,
+ TASK_COLUMN_PERCENT_COMPLETE, &percent_complete,
+ TASK_COLUMN_PRIORITY, &priority,
+ -1);
+
+ if (!summary) {
+ gtk_tree_path_free (path);
+ return FALSE;
+ }
+
+ /* Build tooltip with task information */
+ if (description && strlen (description) > 0) {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\n%s\nProgress: %d%%",
+ summary, description, percent_complete);
+ } else {
+ tooltip_text = g_markup_printf_escaped ("<b>%s</b>\nProgress: %d%%",
+ summary, percent_complete);
+ }
+
+ /* Add due date if available */
+ if (due_text && strlen (due_text) > 0) {
+ gchar *temp = tooltip_text;
+ tooltip_text = g_markup_printf_escaped ("%s\nDue: %s", temp, due_text);
+ g_free (temp);
+ }
+
+ gtk_tooltip_set_markup (tooltip, tooltip_text);
+ gtk_tree_view_set_tooltip_row (tree_view, tooltip, path);
+
+ g_free (summary);
+ g_free (description);
+ g_free (start_text);
+ g_free (due_text);
+ g_free (tooltip_text);
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}
+
+static void
+task_row_activated_cb (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer user_data)
+{
+ GAppInfo *app_info;
+ GError *error = NULL;
+
+ /* Launch Evolution tasks */
+ app_info = g_app_info_get_default_for_uri_scheme ("task");
+ if (!app_info) {
+ /* Fallback to Evolution tasks directly */
+ app_info = g_app_info_create_from_commandline ("evolution --component=tasks",
+ "Evolution Tasks",
+ G_APP_INFO_CREATE_NONE,
+ &error);
+ }
+
+ if (app_info) {
+ g_app_info_launch (app_info, NULL, NULL, &error);
+ g_object_unref (app_info);
+ }
+
+ if (error) {
+ g_warning ("Failed to launch Evolution tasks: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+task_completion_toggled_cb (GtkCellRendererToggle *cell,
+ gchar *path_str,
+ CalendarWindow *calwin)
+{
+ GtkTreePath *path;
+ GtkTreeIter iter;
+ gchar *task_uid;
+ gboolean completed;
+ gint percent_complete;
+
+ path = gtk_tree_path_new_from_string (path_str);
+
+ if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (calwin->priv->tasks_filter), &iter, path)) {
+ gtk_tree_path_free (path);
+ return;
+ }
+
+ gtk_tree_model_get (GTK_TREE_MODEL (calwin->priv->tasks_filter), &iter,
+ TASK_COLUMN_UID, &task_uid,
+ TASK_COLUMN_COMPLETED, &completed,
+ TASK_COLUMN_PERCENT_COMPLETE, &percent_complete,
+ -1);
+
+ /* Toggle completion state */
+ completed = !completed;
+ percent_complete = completed ? 100 : 0;
+
+ /* Update the Evolution task */
+ if (calwin->priv->client && task_uid) {
+ calendar_client_set_task_completed (calwin->priv->client,
+ task_uid,
+ completed,
+ percent_complete);
+ }
+
+ g_free (task_uid);
+ gtk_tree_path_free (path);
+}
+
+static gboolean
+task_entry_key_press_cb (GtkWidget *widget,
+ GdkEventKey *event,
+ CalendarWindow *calwin)
+{
+ const gchar *text;
+
+ if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) {
+ /* Get the text from the entry */
+ text = gtk_entry_get_text (GTK_ENTRY (widget));
+
+ /* Create task if text is not empty */
+ if (text && *text != '\0') {
+ if (calwin->priv->client) {
+ gboolean success = calendar_client_create_task (calwin->priv->client, text);
+ if (success) {
+ /* Clear the entry and hide it */
+ gtk_entry_set_text (GTK_ENTRY (widget), "");
+ gtk_widget_hide (widget);
+ } else {
+ g_warning ("Failed to create task");
+ }
+ }
+ }
+ return TRUE; /* Event handled */
+ } else if (event->keyval == GDK_KEY_Escape) {
+ /* Clear the entry and hide it */
+ gtk_entry_set_text (GTK_ENTRY (widget), "");
+ gtk_widget_hide (widget);
+ return TRUE; /* Event handled */
+ }
+
+ return FALSE; /* Let other handlers process the event */
+}
+
+static void
+task_entry_activate_cb (GtkEntry *entry,
+ CalendarWindow *calwin)
+{
+ const gchar *text;
+
+ /* Get the text from the entry */
+ text = gtk_entry_get_text (entry);
+
+ /* Create task if text is not empty */
+ if (text && *text != '\0') {
+ if (calwin->priv->client) {
+ gboolean success = calendar_client_create_task (calwin->priv->client, text);
+ if (success) {
+ /* Clear the entry and hide it */
+ gtk_entry_set_text (entry, "");
+ gtk_widget_hide (GTK_WIDGET (entry));
+ } else {
+ g_warning ("Failed to create task");
+ }
+ }
+ }
+}
+
+#endif /* HAVE_EDS */
diff --git a/applets/clock/calendar-window.h b/applets/clock/calendar-window.h
index d4ef49dd..e24fdfc3 100644
--- a/applets/clock/calendar-window.h
+++ b/applets/clock/calendar-window.h
@@ -61,7 +61,8 @@ struct _CalendarWindowClass {
GType calendar_window_get_type (void) G_GNUC_CONST;
GtkWidget *calendar_window_new (time_t *static_current_time,
const char *prefs_dir,
- gboolean invert_order);
+ gboolean invert_order,
+ GSettings *settings);
void calendar_window_refresh (CalendarWindow *calwin);
diff --git a/applets/clock/clock-location-tile.c b/applets/clock/clock-location-tile.c
index e9260a31..385d0da1 100644
--- a/applets/clock/clock-location-tile.c
+++ b/applets/clock/clock-location-tile.c
@@ -434,7 +434,7 @@ format_time (struct tm *now,
* weekday differs from the weekday at the location
* (the %A expands to the weekday). The %p expands to
* am/pm. */
- format = _("%l:%M <small>%p (%A)</small>");
+ format = _("%_I:%M <small>%p (%A)</small>");
}
else {
/* Translators: This is a strftime format string.
@@ -451,7 +451,7 @@ format_time (struct tm *now,
* It is used to display the time in 12-hours format
* (eg, like in the US: 8:10 am). The %p expands to
* am/pm. */
- format = _("%l:%M <small>%p</small>");
+ format = _("%_I:%M <small>%p</small>");
}
else {
/* Translators: This is a strftime format string.
@@ -497,7 +497,7 @@ convert_time_to_str (time_t now, ClockFormat clock_format)
* It is used to display the time in 12-hours format (eg, like
* in the US: 8:10 am). The %p expands to am/pm.
*/
- format = _("%l:%M %p");
+ format = _("%_I:%M %p");
}
else {
/* Translators: This is a strftime format string.
diff --git a/applets/clock/clock.c b/applets/clock/clock.c
index f97e56af..cc5ea999 100644
--- a/applets/clock/clock.c
+++ b/applets/clock/clock.c
@@ -60,6 +60,11 @@
#include <gdk/gdkx.h>
#endif
+#ifndef HAVE_X11
+#include <gdk/gdkwayland.h>
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
+
#include <libmateweather/mateweather-prefs.h>
#include <libmateweather/location-entry.h>
#include <libmateweather/timezone-menu.h>
@@ -89,6 +94,8 @@
#define KEY_CITIES "cities"
#define KEY_TEMPERATURE_UNIT "temperature-unit"
#define KEY_SPEED_UNIT "speed-unit"
+#define KEY_SHOW_CALENDAR_EVENTS "show-calendar-events"
+#define KEY_SHOW_TASKS "show-tasks"
/* For watching for when the system resumes from sleep mode (e.g. suspend)
* and updating the clock as soon as that happens. */
@@ -127,6 +134,7 @@ struct _ClockData {
GtkWidget *props;
GtkWidget *calendar_popup;
+ gboolean calendar_popup_destroying;
GtkWidget *clock_vbox;
GtkSizeGroup *clock_group;
@@ -458,7 +466,7 @@ get_updated_timeformat (ClockData *cd)
/* Translators: This is a strftime format string.
* It is used to display the time in 12-hours format (eg, like
* in the US: 8:10 am). The %p expands to am/pm. */
- time_format = cd->showseconds ? _("%l:%M:%S %p") : _("%l:%M %p");
+ time_format = cd->showseconds ? _("%_I:%M:%S %p") : _("%_I:%M %p");
else
/* Translators: This is a strftime format string.
* It is used to display the time in 24-hours format (eg, like
@@ -788,9 +796,12 @@ destroy_clock (GtkWidget * widget, ClockData *cd)
gtk_widget_destroy (cd->props);
cd->props = NULL;
- if (cd->calendar_popup)
+ if (cd->calendar_popup && !cd->calendar_popup_destroying) {
+ cd->calendar_popup_destroying = TRUE;
gtk_widget_destroy (cd->calendar_popup);
+ }
cd->calendar_popup = NULL;
+ cd->calendar_popup_destroying = FALSE;
g_free (cd->timeformat);
@@ -857,11 +868,23 @@ create_calendar (ClockData *cd)
{
GtkWidget *window;
char *prefs_path;
+ char *fallback_path = NULL;
prefs_path = mate_panel_applet_get_preferences_path (MATE_PANEL_APPLET (cd->applet));
+
+ /* Provide a fallback preferences path in case the applet doesn't
+ * provide one. This happens when running outside the actual panel,
+ * like in mate-panel-test-applets */
+ if (!prefs_path || !prefs_path[0]) {
+ fallback_path = g_strdup ("/org/mate/panel/applets/clock/");
+ g_free (prefs_path);
+ prefs_path = fallback_path;
+ }
+
window = calendar_window_new (&cd->current_time,
prefs_path,
- cd->orient == MATE_PANEL_APPLET_ORIENT_UP);
+ cd->orient == MATE_PANEL_APPLET_ORIENT_UP,
+ cd->settings);
g_free (prefs_path);
calendar_window_set_show_weeks (CALENDAR_WINDOW (window),
@@ -922,8 +945,8 @@ position_calendar_popup (ClockData *cd)
button_w = allocation.width;
button_h = allocation.height;
- screen = gtk_window_get_screen (GTK_WINDOW (cd->calendar_popup));
- display = gdk_screen_get_display (screen);
+ screen = gtk_widget_get_screen (GTK_WIDGET (cd->calendar_popup));
+ display = gtk_widget_get_display (GTK_WIDGET (cd->calendar_popup));
n = gdk_display_get_n_monitors (display);
for (i = 0; i < n; i++) {
@@ -1248,6 +1271,7 @@ location_tile_need_clock_format_cb(ClockLocationTile *tile, gpointer data)
static void
create_cities_section (ClockData *cd)
{
+ GtkWidget *cities_box;
GSList *node;
GSList *cities;
GSList *l;
@@ -1261,8 +1285,16 @@ create_cities_section (ClockData *cd)
g_slist_free (cd->location_tiles);
cd->location_tiles = NULL;
- cd->cities_section = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
- gtk_container_set_border_width (GTK_CONTAINER (cd->cities_section), 0);
+ cd->cities_section = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (cd->cities_section),
+ GTK_POLICY_NEVER,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (cd->cities_section),
+ GTK_SHADOW_NONE);
+ gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (cd->cities_section),
+ TRUE);
+
+ cities_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
cities = cd->locations;
if (g_slist_length (cities) == 0) {
@@ -1284,7 +1316,7 @@ create_cities_section (ClockData *cd)
g_signal_connect (city, "need-clock-format",
G_CALLBACK (location_tile_need_clock_format_cb), cd);
- gtk_box_pack_start (GTK_BOX (cd->cities_section),
+ gtk_box_pack_start (GTK_BOX (cities_box),
GTK_WIDGET (city),
FALSE, FALSE, 0);
@@ -1295,6 +1327,8 @@ create_cities_section (ClockData *cd)
g_slist_free (node);
+ gtk_container_add (GTK_CONTAINER (cd->cities_section), cities_box);
+
gtk_box_pack_end (GTK_BOX (cd->clock_vbox),
cd->cities_section, FALSE, FALSE, 0);
@@ -1338,9 +1372,11 @@ static void
update_calendar_popup (ClockData *cd)
{
if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (cd->panel_button))) {
- if (cd->calendar_popup) {
+ if (cd->calendar_popup && !cd->calendar_popup_destroying) {
+ cd->calendar_popup_destroying = TRUE;
gtk_widget_destroy (cd->calendar_popup);
cd->calendar_popup = NULL;
+ cd->calendar_popup_destroying = FALSE;
cd->cities_section = NULL;
cd->map_widget = NULL;
cd->clock_vbox = NULL;
@@ -1353,7 +1389,7 @@ update_calendar_popup (ClockData *cd)
return;
}
- if (!cd->calendar_popup) {
+ if (!cd->calendar_popup && !cd->calendar_popup_destroying) {
cd->calendar_popup = create_calendar (cd);
g_object_add_weak_pointer (G_OBJECT (cd->calendar_popup),
(gpointer *) &cd->calendar_popup);
@@ -3276,6 +3312,35 @@ fill_prefs_window (ClockData *cd)
g_settings_bind (cd->settings, KEY_SHOW_TEMPERATURE, widget, "active",
G_SETTINGS_BIND_DEFAULT);
+#ifdef HAVE_EDS
+ /* Set the EDS calendar event checkboxes */
+ widget = _clock_get_widget (cd, "show_calendar_events_check");
+ if (widget) {
+ /* Bind to gsettings - this should sync with calendar window automatically */
+ g_settings_bind (cd->settings, KEY_SHOW_CALENDAR_EVENTS, widget, "active",
+ G_SETTINGS_BIND_DEFAULT);
+ } else {
+ g_warning ("Could not find show_calendar_events_check widget in preferences");
+ }
+
+ /* Set the EDS tasks checkboxes */
+ widget = _clock_get_widget (cd, "show_tasks_check");
+ if (widget) {
+ /* Bind to gsettings - this should sync with calendar window automatically */
+ g_settings_bind (cd->settings, KEY_SHOW_TASKS, widget, "active",
+ G_SETTINGS_BIND_DEFAULT);
+ } else {
+ g_warning ("Could not find show_tasks_check widget in preferences");
+ }
+
+#else
+ /* Hide the Calendar tab if EDS is not available */
+ widget = _clock_get_widget (cd, "vbox-calendar");
+ gtk_widget_hide (widget);
+ widget = _clock_get_widget (cd, "label-calendar-tab");
+ gtk_widget_hide (widget);
+#endif
+
/* Fill the Cities list */
widget = _clock_get_widget (cd, "cities_list");
diff --git a/applets/clock/clock.ui b/applets/clock/clock.ui
index fb62507c..2f9cfa78 100644
--- a/applets/clock/clock.ui
+++ b/applets/clock/clock.ui
@@ -606,8 +606,9 @@
<object class="GtkScrolledWindow" id="scrolledwindow10">
<property name="visible">True</property>
<property name="can-focus">True</property>
+ <property name="expand">True</property>
<property name="hscrollbar-policy">never</property>
- <property name="vscrollbar-policy">never</property>
+ <property name="vscrollbar-policy">automatic</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkTreeView" id="cities_list">
@@ -890,6 +891,101 @@
<property name="tab-fill">False</property>
</packing>
</child>
+ <child>
+ <object class="GtkBox" id="vbox-calendar">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="border-width">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">18</property>
+ <child>
+ <object class="GtkBox" id="calendar-options">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="label-calendar">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Evolution Calendar Integration</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox-calendar-options">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="show_calendar_events_check">
+ <property name="label" translatable="yes">Show calendar _events</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_tasks_check">
+ <property name="label" translatable="yes">Show _tasks</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">False</property>
+ <property name="use-underline">True</property>
+ <property name="draw-indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label-calendar-tab">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Calendar</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab-fill">False</property>
+ </packing>
+ </child>
</object>
<packing>
<property name="expand">False</property>
diff --git a/applets/clock/org.mate.panel.applet.clock.gschema.xml.in b/applets/clock/org.mate.panel.applet.clock.gschema.xml.in
index ee1b6902..ad822e46 100644
--- a/applets/clock/org.mate.panel.applet.clock.gschema.xml.in
+++ b/applets/clock/org.mate.panel.applet.clock.gschema.xml.in
@@ -84,5 +84,15 @@
<summary>Speed unit</summary>
<description>The unit to use when showing wind speed.</description>
</key>
+ <key name="show-calendar-events" type="b">
+ <default>true</default>
+ <summary>Show calendar events</summary>
+ <description>If true, display calendar events from Evolution in the calendar window.</description>
+ </key>
+ <key name="show-tasks" type="b">
+ <default>false</default>
+ <summary>Show tasks</summary>
+ <description>If true, display tasks from Evolution in the calendar window.</description>
+ </key>
</schema>
</schemalist>
diff --git a/applets/fish/fish.c b/applets/fish/fish.c
index 45ce9228..d70d250a 100644
--- a/applets/fish/fish.c
+++ b/applets/fish/fish.c
@@ -819,7 +819,6 @@ static void display_fortune_dialog(FishApplet* fish)
int argc;
char **argv;
GdkDisplay *display;
- GdkScreen *screen;
char *display_name;
/* if there is still a pipe, close it */
@@ -937,8 +936,7 @@ static void display_fortune_dialog(FishApplet* fish)
clear_fortune_text (fish);
- screen = gtk_widget_get_screen (GTK_WIDGET (fish));
- display = gdk_screen_get_display (screen);
+ display = gtk_widget_get_display (GTK_WIDGET (fish));
display_name = g_strdup (gdk_display_get_name (display));
g_spawn_async_with_pipes (NULL, /* working directory */
argv,
diff --git a/applets/notification_area/Makefile.am b/applets/notification_area/Makefile.am
index 8f6ec8d3..ff5442c6 100644
--- a/applets/notification_area/Makefile.am
+++ b/applets/notification_area/Makefile.am
@@ -1,10 +1,15 @@
SUBDIRS = \
libstatus-notifier-watcher \
- status-notifier \
- system-tray
+ status-notifier
+
+if ENABLE_X11
+SUBDIRS += \
+ system-tray
-noinst_LTLIBRARIES = libtray.la
noinst_PROGRAMS = testtray
+endif
+
+noinst_LTLIBRARIES = libtray.la
AM_CPPFLAGS = \
$(NOTIFICATION_AREA_CFLAGS) \
@@ -29,9 +34,18 @@ libtray_la_SOURCES = \
libtray_la_LIBADD = \
libstatus-notifier-watcher/libstatus-notifier-watcher.la \
- status-notifier/libstatus-notifier.la \
+ status-notifier/libstatus-notifier.la
+
+if ENABLE_X11
+libtray_la_LIBADD += \
system-tray/libsystem-tray.la
+testtray_SOURCES = testtray.c
+testtray_LDADD = \
+ libtray.la \
+ $(NOTIFICATION_AREA_LIBS)
+endif
+
NOTIFICATION_AREA_SOURCES = \
main.c \
main.h \
@@ -43,12 +57,6 @@ NOTIFICATION_AREA_LDADD = \
$(NOTIFICATION_AREA_LIBS) \
$(LIBMATE_PANEL_APPLET_LIBS)
-
-testtray_SOURCES = testtray.c
-testtray_LDADD = \
- libtray.la \
- $(NOTIFICATION_AREA_LIBS)
-
if NOTIFICATION_AREA_INPROCESS
APPLET_IN_PROCESS = true
APPLET_LOCATION = $(pkglibdir)/libnotification-area-applet.so
diff --git a/applets/notification_area/main.c b/applets/notification_area/main.c
index 71221b65..20ef2b3c 100644
--- a/applets/notification_area/main.c
+++ b/applets/notification_area/main.c
@@ -23,10 +23,6 @@
#include <config.h>
-#ifndef HAVE_X11
-#error file should only be built when HAVE_X11 is enabled
-#endif
-
#include <string.h>
#include <mate-panel-applet.h>
@@ -302,7 +298,6 @@ static void
na_tray_applet_realize (GtkWidget *widget)
{
NaTrayApplet *applet = NA_TRAY_APPLET (widget);
-
if (parent_class_realize)
parent_class_realize (widget);
diff --git a/applets/notification_area/na-grid.c b/applets/notification_area/na-grid.c
index 9a4c45d9..0fa82494 100644
--- a/applets/notification_area/na-grid.c
+++ b/applets/notification_area/na-grid.c
@@ -29,7 +29,10 @@
#include "na-grid.h"
+#ifdef HAVE_X11
#include "system-tray/na-tray.h"
+#endif
+
#include "status-notifier/sn-host-v0.h"
#define MIN_ICON_SIZE_DEFAULT 24
@@ -324,6 +327,7 @@ na_grid_realize (GtkWidget *widget)
display = gdk_display_get_default ();
/* Instantiate the hosts now we have a screen */
screen = gtk_widget_get_screen (GTK_WIDGET (self));
+#ifdef HAVE_X11
if (GDK_IS_X11_DISPLAY (display))
{
GtkOrientation orientation;
@@ -337,6 +341,7 @@ na_grid_realize (GtkWidget *widget)
add_host (self, tray_host);
}
+#endif
settings = g_settings_new ("org.mate.panel");
if (g_settings_get_boolean (settings, "enable-sni-support"))
add_host (self, sn_host_v0_new ());
diff --git a/applets/notification_area/na-grid.h b/applets/notification_area/na-grid.h
index 9c8dd340..0506c3ec 100644
--- a/applets/notification_area/na-grid.h
+++ b/applets/notification_area/na-grid.h
@@ -31,6 +31,11 @@
#include <gdk/gdkx.h>
#endif
+#ifndef HAVE_X11
+#include <gdk/gdkwayland.h>
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
+
#include <gtk/gtk.h>
G_BEGIN_DECLS
diff --git a/applets/notification_area/status-notifier/sn-item-v0.c b/applets/notification_area/status-notifier/sn-item-v0.c
index 9deab9d3..9e66f361 100644
--- a/applets/notification_area/status-notifier/sn-item-v0.c
+++ b/applets/notification_area/status-notifier/sn-item-v0.c
@@ -287,7 +287,12 @@ update (SnItemV0 *v0)
if (pixbuf && icon_size > 1)
{
/*An icon specified by path and filename may be the wrong size for the tray */
- pixbuf = gdk_pixbuf_scale_simple (pixbuf, icon_size-2, icon_size-2, GDK_INTERP_BILINEAR);
+ GdkPixbuf *scaled_pixbuf;
+
+ scaled_pixbuf = gdk_pixbuf_scale_simple (pixbuf, icon_size-2, icon_size-2, GDK_INTERP_BILINEAR);
+ g_object_unref (pixbuf);
+ pixbuf = scaled_pixbuf;
+
surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, scale, NULL);
}
if (pixbuf)
diff --git a/applets/wncklet/Makefile.am b/applets/wncklet/Makefile.am
index 14f8a5c7..a59b1192 100644
--- a/applets/wncklet/Makefile.am
+++ b/applets/wncklet/Makefile.am
@@ -19,12 +19,18 @@ WNCKLET_SOURCES = \
window-menu.h \
window-list.c \
window-list.h \
- workspace-switcher.c \
- workspace-switcher.h \
showdesktop.c \
showdesktop.h \
$(BUILT_SOURCES)
+if ENABLE_X11
+WNCKLET_SOURCES += \
+ workspace-switcher.c \
+ workspace-switcher.h
+endif
+
+
+
WNCKLET_LDADD = \
../../libmate-panel-applet/libmate-panel-applet-4.la \
$(WNCKLET_LIBS) \
@@ -93,10 +99,18 @@ ui_FILES = \
showdesktop-menu.xml \
window-list-menu.xml \
window-list.ui \
- window-menu-menu.xml \
+ window-menu-menu.xml
+
+if ENABLE_X11
+ui_FILES += \
workspace-switcher-menu.xml \
workspace-switcher.ui
+endif
+
+
+
+
wncklet-resources.c: wncklet.gresource.xml $(shell $(GLIB_COMPILE_RESOURCES) --sourcedir=$(srcdir) --generate-dependencies $(srcdir)/wncklet.gresource.xml)
$(AM_V_GEN)$(GLIB_COMPILE_RESOURCES) --target=$@ --sourcedir=$(srcdir) --generate --c-name wncklet $<
@@ -107,16 +121,10 @@ BUILT_SOURCES = \
wncklet-resources.c \
wncklet-resources.h
-if HAVE_WINDOW_PREVIEWS
wncklet_gschemas_in = \
org.mate.panel.applet.window-list.gschema.xml.in \
org.mate.panel.applet.window-list-previews.gschema.xml.in \
org.mate.panel.applet.workspace-switcher.gschema.xml.in
-else
-wncklet_gschemas_in = \
- org.mate.panel.applet.window-list.gschema.xml.in \
- org.mate.panel.applet.workspace-switcher.gschema.xml.in
-endif
gsettings_SCHEMAS = $(wncklet_gschemas_in:.xml.in=.xml)
@GSETTINGS_RULES@
diff --git a/applets/wncklet/showdesktop.c b/applets/wncklet/showdesktop.c
index 190077f1..60142953 100644
--- a/applets/wncklet/showdesktop.c
+++ b/applets/wncklet/showdesktop.c
@@ -35,6 +35,11 @@
#include <libwnck/libwnck.h>
#endif
+#ifndef HAVE_X11
+#include <gdk/gdkwayland.h>
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
+
#include "wncklet.h"
#include "showdesktop.h"
@@ -52,6 +57,9 @@ typedef struct {
GtkOrientation orient;
int size;
+#ifdef HAVE_X11
+ WnckHandle* wnck_handle;
+#endif
WnckScreen* wnck_screen;
guint showing_desktop: 1;
@@ -308,6 +316,10 @@ static void applet_destroyed(GtkWidget* applet, ShowDesktopData* sdd)
sdd->icon_theme = NULL;
}
+#ifdef HAVE_X11
+ g_clear_object(&sdd->wnck_handle);
+#endif
+
g_free (sdd);
}
@@ -371,7 +383,7 @@ static void show_desktop_applet_realized(MatePanelApplet* applet, gpointer data)
#ifdef HAVE_X11
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
{
- sdd->wnck_screen = wnck_screen_get (gdk_x11_screen_get_screen_number (screen));
+ sdd->wnck_screen = wncklet_get_screen (sdd->wnck_handle, sdd->applet);
if (sdd->wnck_screen != NULL)
wncklet_connect_while_alive (sdd->wnck_screen,
"showing_desktop_changed",
@@ -426,6 +438,13 @@ gboolean show_desktop_applet_fill(MatePanelApplet* applet)
sdd->size = mate_panel_applet_get_size(MATE_PANEL_APPLET(sdd->applet));
+#ifdef HAVE_X11
+ if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ {
+ sdd->wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
+ }
+#endif
+
g_signal_connect (sdd->applet, "realize",
G_CALLBACK (show_desktop_applet_realized),
sdd);
diff --git a/applets/wncklet/window-list.c b/applets/wncklet/window-list.c
index a87649b1..c027e3f5 100644
--- a/applets/wncklet/window-list.c
+++ b/applets/wncklet/window-list.c
@@ -25,6 +25,11 @@
#include <libwnck/libwnck.h>
#endif /* HAVE_X11 */
+#ifndef HAVE_X11
+#include <gdk/gdkwayland.h>
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
+
#ifdef HAVE_WAYLAND
#include <gdk/gdkwayland.h>
#include "wayland-backend.h"
@@ -39,9 +44,7 @@
#define WINDOW_LIST_ICON "mate-panel-window-list"
#define WINDOW_LIST_SCHEMA "org.mate.panel.applet.window-list"
-#ifdef HAVE_WINDOW_PREVIEWS
#define WINDOW_LIST_PREVIEW_SCHEMA "org.mate.panel.applet.window-list-previews"
-#endif /* HAVE_WINDOW_PREVIEWS */
typedef enum {
TASKLIST_NEVER_GROUP,
@@ -52,12 +55,14 @@ typedef enum {
typedef struct {
GtkWidget* applet;
GtkWidget* tasklist;
-#ifdef HAVE_WINDOW_PREVIEWS
GtkWidget* preview;
+#ifdef HAVE_X11
+ WnckHandle* wnck_handle;
+#endif
+
gboolean show_window_thumbnails;
gint thumbnail_size;
-#endif
gboolean include_all_workspaces;
TasklistGroupingType grouping;
@@ -71,19 +76,15 @@ typedef struct {
gboolean needs_hints;
#endif
- GtkIconTheme* icon_theme;
-
/* Properties: */
GtkWidget* properties_dialog;
GtkWidget* wayland_info_label;
GtkWidget* show_current_radio;
GtkWidget* show_all_radio;
-#ifdef HAVE_WINDOW_PREVIEWS
GtkWidget* window_thumbnail_box;
GtkWidget* show_thumbnails_check;
GtkWidget* thumbnail_size_label;
GtkWidget* thumbnail_size_spin;
-#endif
GtkWidget* never_group_radio;
GtkWidget* auto_group_radio;
GtkWidget* always_group_radio;
@@ -96,9 +97,7 @@ typedef struct {
GtkWidget* window_list_content_box;
GSettings* settings;
-#ifdef HAVE_WINDOW_PREVIEWS
GSettings* preview_settings;
-#endif
} TasklistData;
static void call_system_monitor(GtkAction* action, TasklistData* tasklist);
@@ -205,11 +204,6 @@ static void response_cb(GtkWidget* widget, int id, TasklistData* tasklist)
}
}
-static void applet_realized(MatePanelApplet* applet, TasklistData* tasklist)
-{
- tasklist->icon_theme = gtk_icon_theme_get_for_screen(gtk_widget_get_screen(tasklist->applet));
-}
-
static void applet_change_orient(MatePanelApplet* applet, MatePanelAppletOrient orient, TasklistData* tasklist)
{
GtkOrientation new_orient;
@@ -249,7 +243,6 @@ static void applet_change_background(MatePanelApplet* applet, MatePanelAppletBac
}
#ifdef HAVE_X11
-#ifdef HAVE_WINDOW_PREVIEWS
static cairo_surface_t*
preview_window_thumbnail (WnckWindow *wnck_window,
TasklistData *tasklist,
@@ -480,7 +473,7 @@ static gboolean applet_enter_notify_event (WnckTasklist *tl, GList *wnck_windows
/* Do not show preview if window is not visible nor in current workspace */
if (!wnck_window_is_visible_on_workspace (wnck_window,
- wnck_screen_get_active_workspace (wnck_screen_get_default ())))
+ wnck_screen_get_active_workspace (wncklet_get_screen (tasklist->wnck_handle, tasklist->applet))))
return FALSE;
thumbnail = preview_window_thumbnail (wnck_window, tasklist, &thumbnail_width, &thumbnail_height, &thumbnail_scale);
@@ -516,7 +509,6 @@ static gboolean applet_leave_notify_event (WnckTasklist *tl, GList *wnck_windows
return FALSE;
}
-#endif /* HAVE_WINDOW_PREVIEWS */
#endif /* HAVE_X11 */
static void applet_change_pixel_size(MatePanelApplet* applet, gint size, TasklistData* tasklist)
@@ -605,7 +597,6 @@ static void display_all_workspaces_changed(GSettings* settings, gchar* key, Task
tasklist_properties_update_content_radio(tasklist);
}
-#ifdef HAVE_WINDOW_PREVIEWS
static void tasklist_update_thumbnail_size_spin(TasklistData* tasklist)
{
GtkWidget* button;
@@ -628,7 +619,6 @@ static void thumbnail_size_changed(GSettings *settings, gchar* key, TasklistData
tasklist->thumbnail_size = g_settings_get_int(settings, key);
tasklist_update_thumbnail_size_spin(tasklist);
}
-#endif
static GtkWidget* get_grouping_button(TasklistData* tasklist, TasklistGroupingType type)
{
@@ -718,7 +708,6 @@ static void setup_gsettings(TasklistData* tasklist)
G_CALLBACK (display_all_workspaces_changed),
tasklist);
-#ifdef HAVE_WINDOW_PREVIEWS
tasklist->preview_settings = mate_panel_applet_settings_new (MATE_PANEL_APPLET (tasklist->applet), WINDOW_LIST_PREVIEW_SCHEMA);
g_signal_connect (tasklist->preview_settings,
@@ -730,7 +719,6 @@ static void setup_gsettings(TasklistData* tasklist)
"changed::thumbnail-window-size",
G_CALLBACK (thumbnail_size_changed),
tasklist);
-#endif
g_signal_connect (tasklist->settings,
"changed::group-windows",
G_CALLBACK (group_windows_changed),
@@ -781,77 +769,26 @@ static void applet_size_allocate(GtkWidget *widget, GtkAllocation *allocation, T
mate_panel_applet_set_size_hints(MATE_PANEL_APPLET(tasklist->applet), size_hints, len, 0);
}
-#ifdef HAVE_X11
-/* Currently only used on X11, but should work on Wayland as well when needed */
-static GdkPixbuf* icon_loader_func(const char* icon, int size, unsigned int flags, void* data)
-{
- TasklistData* tasklist;
- GdkPixbuf* retval;
- char* icon_no_extension;
- char* p;
-
- tasklist = data;
-
- if (icon == NULL || strcmp(icon, "") == 0)
- return NULL;
-
- if (g_path_is_absolute(icon))
- {
- if (g_file_test(icon, G_FILE_TEST_EXISTS))
- {
- return gdk_pixbuf_new_from_file_at_size(icon, size, size, NULL);
- }
- else
- {
- char* basename;
-
- basename = g_path_get_basename(icon);
- retval = icon_loader_func(basename, size, flags, data);
- g_free(basename);
-
- return retval;
- }
- }
-
- /* This is needed because some .desktop files have an icon name *and*
- * an extension as icon */
- icon_no_extension = g_strdup(icon);
- p = strrchr(icon_no_extension, '.');
-
- if (p && (strcmp(p, ".png") == 0 || strcmp(p, ".xpm") == 0 || strcmp(p, ".svg") == 0))
- {
- *p = 0;
- }
-
- retval = gtk_icon_theme_load_icon(tasklist->icon_theme, icon_no_extension, size, 0, NULL);
- g_free(icon_no_extension);
-
- return retval;
-}
-#endif /* HAVE_X11 */
-
gboolean window_list_applet_fill(MatePanelApplet* applet)
{
TasklistData* tasklist;
GtkActionGroup* action_group;
GtkCssProvider *provider;
- GdkScreen *screen;
tasklist = g_new0(TasklistData, 1);
tasklist->applet = GTK_WIDGET(applet);
provider = gtk_css_provider_new ();
- screen = gdk_screen_get_default ();
gtk_css_provider_load_from_data (provider,
".mate-panel-menu-bar button,\n"
" #tasklist-button {\n"
" padding: 0px;\n"
" margin: 0px;\n }",
-1, NULL);
- gtk_style_context_add_provider_for_screen (screen,
- GTK_STYLE_PROVIDER (provider),
- GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ gtk_style_context_add_provider_for_screen (gtk_widget_get_screen (tasklist->applet),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
g_object_unref (provider);
mate_panel_applet_set_flags(MATE_PANEL_APPLET(tasklist->applet), MATE_PANEL_APPLET_EXPAND_MAJOR | MATE_PANEL_APPLET_EXPAND_MINOR | MATE_PANEL_APPLET_HAS_HANDLE);
@@ -860,11 +797,9 @@ gboolean window_list_applet_fill(MatePanelApplet* applet)
tasklist->include_all_workspaces = g_settings_get_boolean (tasklist->settings, "display-all-workspaces");
-#ifdef HAVE_WINDOW_PREVIEWS
tasklist->show_window_thumbnails = g_settings_get_boolean (tasklist->preview_settings, "show-window-thumbnails");
tasklist->thumbnail_size = g_settings_get_int (tasklist->preview_settings, "thumbnail-window-size");
-#endif
tasklist->grouping = g_settings_get_enum (tasklist->settings, "group-windows");
@@ -896,18 +831,15 @@ gboolean window_list_applet_fill(MatePanelApplet* applet)
#ifdef HAVE_X11
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
{
- tasklist->tasklist = wnck_tasklist_new();
+ tasklist->wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
+ tasklist->tasklist = wnck_tasklist_new_with_handle(tasklist->wnck_handle);
- wnck_tasklist_set_icon_loader(WNCK_TASKLIST(tasklist->tasklist), icon_loader_func, tasklist, NULL);
-
-#ifdef HAVE_WINDOW_PREVIEWS
g_signal_connect (tasklist->tasklist, "task-enter-notify",
G_CALLBACK (applet_enter_notify_event),
tasklist);
g_signal_connect (tasklist->tasklist, "task-leave-notify",
G_CALLBACK (applet_leave_notify_event),
tasklist);
-#endif /* HAVE_WINDOW_PREVIEWS */
}
else
#endif /* HAVE_X11 */
@@ -935,9 +867,6 @@ gboolean window_list_applet_fill(MatePanelApplet* applet)
gtk_container_add(GTK_CONTAINER(tasklist->applet), tasklist->tasklist);
- g_signal_connect (tasklist->applet, "realize",
- G_CALLBACK (applet_realized),
- tasklist);
g_signal_connect (tasklist->applet, "change-orient",
G_CALLBACK (applet_change_orient),
tasklist);
@@ -1065,12 +994,10 @@ static void group_windows_toggled(GtkToggleButton* button, TasklistData* tasklis
}
}
-#ifdef HAVE_WINDOW_PREVIEWS
static void thumbnail_size_spin_changed(GtkSpinButton* button, TasklistData* tasklist)
{
g_settings_set_int(tasklist->preview_settings, "thumbnail-window-size", gtk_spin_button_get_value_as_int(button));
}
-#endif
static void move_minimized_toggled(GtkToggleButton* button, TasklistData* tasklist)
{
@@ -1121,9 +1048,7 @@ static void setup_dialog_wayland(TasklistData* tasklist)
gtk_widget_set_sensitive(tasklist->window_grouping_box, FALSE);
gtk_widget_set_sensitive(tasklist->minimized_windows_box, FALSE);
-#ifdef HAVE_WINDOW_PREVIEWS
gtk_widget_set_sensitive(tasklist->window_thumbnail_box, FALSE);
-#endif /* HAVE_WINDOW_PREVIEWS */
}
#endif /* HAVE_WAYLAND */
@@ -1143,7 +1068,6 @@ static void setup_dialog(GtkBuilder* builder, TasklistData* tasklist)
setup_sensitivity(tasklist, builder, "never_group_radio", "auto_group_radio", "always_group_radio", "group-windows" /* key */);
-#ifdef HAVE_WINDOW_PREVIEWS
tasklist->window_thumbnail_box = WID("window_thumbnail_box");
tasklist->show_thumbnails_check = WID("show_thumbnails_check");
tasklist->thumbnail_size_label = WID("thumbnail_size_label");
@@ -1160,10 +1084,6 @@ static void setup_dialog(GtkBuilder* builder, TasklistData* tasklist)
g_object_bind_property(tasklist->show_thumbnails_check, "active", tasklist->thumbnail_size_label, "sensitive", G_BINDING_DEFAULT);
g_object_bind_property(tasklist->show_thumbnails_check, "active", tasklist->thumbnail_size_spin, "sensitive", G_BINDING_DEFAULT);
-#else
- gtk_widget_hide(WID("window_thumbnail_box"));
-#endif
-
tasklist->move_minimized_radio = WID("move_minimized_radio");
tasklist->change_workspace_radio = WID("change_workspace_radio");
tasklist->mouse_scroll_check = WID("mouse_scroll_check");
@@ -1205,13 +1125,11 @@ static void setup_dialog(GtkBuilder* builder, TasklistData* tasklist)
"active",
G_SETTINGS_BIND_DEFAULT);
-#ifdef HAVE_WINDOW_PREVIEWS
/* change thumbnail size: */
tasklist_update_thumbnail_size_spin(tasklist);
g_signal_connect (tasklist->thumbnail_size_spin, "value-changed",
(GCallback) thumbnail_size_spin_changed,
tasklist);
-#endif
/* move window when unminimizing: */
tasklist_update_unminimization_radio(tasklist);
@@ -1269,11 +1187,9 @@ static void destroy_tasklist(GtkWidget* widget, TasklistData* tasklist)
{
g_signal_handlers_disconnect_by_data (G_OBJECT (tasklist->applet), tasklist);
-#ifdef HAVE_WINDOW_PREVIEWS
g_signal_handlers_disconnect_by_data (G_OBJECT (tasklist->tasklist), tasklist);
g_signal_handlers_disconnect_by_data (tasklist->preview_settings, tasklist);
g_object_unref(tasklist->preview_settings);
-#endif
g_signal_handlers_disconnect_by_data (tasklist->settings, tasklist);
@@ -1282,9 +1198,11 @@ static void destroy_tasklist(GtkWidget* widget, TasklistData* tasklist)
if (tasklist->properties_dialog)
gtk_widget_destroy(tasklist->properties_dialog);
-#ifdef HAVE_WINDOW_PREVIEWS
if (tasklist->preview)
gtk_widget_destroy(tasklist->preview);
+
+#ifdef HAVE_X11
+ g_clear_object(&tasklist->wnck_handle);
#endif
g_free(tasklist);
diff --git a/applets/wncklet/window-list.ui b/applets/wncklet/window-list.ui
index 4cc49f9c..2ed06c0e 100644
--- a/applets/wncklet/window-list.ui
+++ b/applets/wncklet/window-list.ui
@@ -76,6 +76,7 @@
<object class="GtkNotebook">
<property name="visible">True</property>
<property name="can-focus">True</property>
+ <property name="border_width">5</property>
<child>
<object class="GtkBox" id="behaviour_vbox">
<property name="visible">True</property>
diff --git a/applets/wncklet/window-menu.c b/applets/wncklet/window-menu.c
index 4b4e48dc..18f33f19 100644
--- a/applets/wncklet/window-menu.c
+++ b/applets/wncklet/window-menu.c
@@ -45,6 +45,10 @@
#include <gdk/gdkwayland.h>
#endif /* HAVE_WAYLAND */
+#ifndef HAVE_X11
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
+
#include "wncklet.h"
#include "window-menu.h"
@@ -53,6 +57,9 @@
typedef struct {
GtkWidget* applet;
GtkWidget* selector;
+#ifdef HAVE_X11
+ WnckHandle* wnck_handle;
+#endif
int size;
MatePanelAppletOrient orient;
} WindowMenu;
@@ -119,6 +126,9 @@ static const GtkActionEntry window_menu_actions[] = {
static void window_menu_destroy(GtkWidget* widget, WindowMenu* window_menu)
{
+#ifdef HAVE_X11
+ g_clear_object(&window_menu->wnck_handle);
+#endif
g_free(window_menu);
}
@@ -252,7 +262,8 @@ gboolean window_menu_applet_fill(MatePanelApplet* applet)
#ifdef HAVE_X11
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
{
- window_menu->selector = wnck_selector_new();
+ window_menu->wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
+ window_menu->selector = wnck_selector_new_with_handle(window_menu->wnck_handle);
}
else
#endif /* HAVE_X11 */
diff --git a/applets/wncklet/wncklet.c b/applets/wncklet/wncklet.c
index 3b185910..1ced4507 100644
--- a/applets/wncklet/wncklet.c
+++ b/applets/wncklet/wncklet.c
@@ -35,11 +35,15 @@
#include <gdk/gdkx.h>
#define WNCK_I_KNOW_THIS_IS_UNSTABLE
#include <libwnck/libwnck.h>
+#include "workspace-switcher.h"
+#endif
+#ifndef HAVE_X11
+#include <gdk/gdkwayland.h>
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
#endif
#include "wncklet.h"
#include "window-menu.h"
-#include "workspace-switcher.h"
#include "window-list.h"
#include "showdesktop.h"
@@ -96,18 +100,19 @@ void wncklet_display_help(GtkWidget* widget, const char* doc_id, const char* lin
}
#ifdef HAVE_X11
-WnckScreen* wncklet_get_screen(GtkWidget* applet)
+WnckScreen* wncklet_get_screen(WnckHandle* handle, GtkWidget* applet)
{
+ g_return_val_if_fail (WNCK_IS_HANDLE (handle), NULL);
g_return_val_if_fail (GDK_IS_X11_DISPLAY (gdk_display_get_default ()), NULL);
int screen_num;
if (!gtk_widget_has_screen(applet))
- return wnck_screen_get_default();
+ return wnck_handle_get_default_screen(handle);
screen_num = gdk_x11_screen_get_screen_number(gtk_widget_get_screen(applet));
- return wnck_screen_get(screen_num);
+ return wnck_handle_get_screen(handle, screen_num);
}
#endif /* HAVE_X11 */
@@ -124,22 +129,12 @@ static gboolean wncklet_factory(MatePanelApplet* applet, const char* iid, gpoint
{
gboolean retval = FALSE;
-#ifdef HAVE_X11
- if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
- {
- static gboolean type_registered = FALSE;
- if (!type_registered)
- {
- wnck_set_client_type(WNCK_CLIENT_TYPE_PAGER);
- type_registered = TRUE;
- }
- }
-#endif /* HAVE_X11 */
-
if (!strcmp(iid, "WindowMenuApplet"))
retval = window_menu_applet_fill(applet);
+#ifdef HAVE_X11
else if (!strcmp(iid, "WorkspaceSwitcherApplet") || !strcmp(iid, "PagerApplet"))
retval = workspace_switcher_applet_fill(applet);
+#endif
else if (!strcmp(iid, "WindowListApplet") || !strcmp(iid, "TasklistApplet"))
retval = window_list_applet_fill(applet);
else if (!strcmp(iid, "ShowDesktopApplet"))
diff --git a/applets/wncklet/wncklet.h b/applets/wncklet/wncklet.h
index 145cbce3..4b1dccea 100644
--- a/applets/wncklet/wncklet.h
+++ b/applets/wncklet/wncklet.h
@@ -28,17 +28,18 @@
#include <gtk/gtk.h>
#include <mate-panel-applet.h>
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include <libwnck/libwnck.h>
+
#define WNCKLET_RESOURCE_PATH "/org/mate/panel/applet/wncklet/"
#ifdef __cplusplus
extern "C" {
#endif
-typedef struct _WnckScreen WnckScreen;
-
void wncklet_display_help(GtkWidget* widget, const char* doc_id, const char* link_id, const char* icon_name);
-WnckScreen* wncklet_get_screen(GtkWidget* applet);
+WnckScreen* wncklet_get_screen(WnckHandle* handle, GtkWidget* applet);
void wncklet_connect_while_alive(gpointer object, const char* signal, GCallback func, gpointer func_data, gpointer alive_object);
diff --git a/applets/wncklet/workspace-switcher.c b/applets/wncklet/workspace-switcher.c
index e3fda355..cba0ecbd 100644
--- a/applets/wncklet/workspace-switcher.c
+++ b/applets/wncklet/workspace-switcher.c
@@ -215,6 +215,9 @@ typedef struct {
GtkWidget* pager_container;
GtkWidget* pager;
+#ifdef HAVE_X11
+ WnckHandle* wnck_handle;
+#endif
WnckScreen* screen;
PagerWM wm;
@@ -399,7 +402,7 @@ static void applet_realized(MatePanelApplet* applet, PagerData* pager)
#ifdef HAVE_X11
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
{
- pager->screen = wncklet_get_screen(GTK_WIDGET(applet));
+ pager->screen = wncklet_get_screen(pager->wnck_handle, GTK_WIDGET(applet));
wncklet_connect_while_alive(pager->screen, "window_manager_changed", G_CALLBACK(window_manager_changed), pager, pager->applet);
}
#endif /* HAVE_X11 */
@@ -772,7 +775,8 @@ gboolean workspace_switcher_applet_fill(MatePanelApplet* applet)
#ifdef HAVE_X11
if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
{
- pager->pager = wnck_pager_new();
+ pager->wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
+ pager->pager = wnck_pager_new_with_handle(pager->wnck_handle);
wnck_pager_set_shadow_type(WNCK_PAGER(pager->pager), GTK_SHADOW_IN);
}
else
@@ -1288,5 +1292,10 @@ static void destroy_pager(GtkWidget* widget, PagerData* pager)
if (pager->properties_dialog)
gtk_widget_destroy(pager->properties_dialog);
+
+#ifdef HAVE_X11
+ g_clear_object(&pager->wnck_handle);
+#endif
+
g_free(pager);
}
diff --git a/configure.ac b/configure.ac
index 97db8b60..f610cc46 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,4 +1,4 @@
-AC_INIT([mate-panel], [1.28.1], [https://github.com/mate-desktop/mate-panel/issues],
+AC_INIT([mate-panel], [1.29.0], [https://github.com/mate-desktop/mate-panel/issues],
[mate-panel], [https://mate-desktop.org])
AC_CONFIG_HEADERS(config.h)
AM_INIT_AUTOMAKE([1.9 no-dist-gzip dist-xz tar-ustar -Wno-portability check-news])
@@ -60,9 +60,8 @@ GLIB_REQUIRED=2.50.0
LIBMATE_MENU_REQUIRED=1.21.0
CAIRO_REQUIRED=1.0.0
DCONF_REQUIRED=0.13.4
-GTK_REQUIRED=3.22.0
-LIBWNCK_REQUIRED=3.4.6
-LIBWNCK_PREVIEWS_OPTIONAL=3.32.0
+GTK_REQUIRED=3.24.0
+LIBWNCK_REQUIRED=43.0
WEATHER_REQUIRED=1.17.0
dnl pkg-config dependency checks
@@ -88,16 +87,9 @@ PKG_CHECK_MODULES(NOTIFICATION_AREA, gtk+-3.0 >= $GTK_REQUIRED mate-desktop-2.0
AC_SUBST(NOTIFICATION_AREA_CFLAGS)
AC_SUBST(NOTIFICATION_AREA_LIBS)
-# Check if we have a version of libwnck that allows for window previews
-PKG_CHECK_MODULES(WNCKLET, gtk+-3.0 >= $GTK_REQUIRED libwnck-3.0 >= $LIBWNCK_PREVIEWS_OPTIONAL mate-desktop-2.0 >= $LIBMATE_DESKTOP_REQUIRED, have_window_previews=yes, [
- PKG_CHECK_MODULES(WNCKLET, gtk+-3.0 >= $GTK_REQUIRED libwnck-3.0 >= $LIBWNCK_REQUIRED mate-desktop-2.0 >= $LIBMATE_DESKTOP_REQUIRED, have_window_previews=no)
-])
+PKG_CHECK_MODULES(WNCKLET, gtk+-3.0 >= $GTK_REQUIRED libwnck-3.0 >= $LIBWNCK_REQUIRED mate-desktop-2.0 >= $LIBMATE_DESKTOP_REQUIRED)
AC_SUBST(WNCKLET_CFLAGS)
AC_SUBST(WNCKLET_LIBS)
-AM_CONDITIONAL(HAVE_WINDOW_PREVIEWS, [test "x$have_window_previews" = "xyes"])
-if test "x$have_window_previews" = "xyes"; then
- AC_DEFINE([HAVE_WINDOW_PREVIEWS], 1, [Defined when using a version of libwnck that provides window-list previews])
-fi
AC_CHECK_HEADERS(langinfo.h)
AC_CHECK_FUNCS(nl_langinfo)
@@ -110,6 +102,27 @@ PKG_CHECK_MODULES(CLOCK, pango >= $PANGO_REQUIRED gtk+-3.0 >= $GTK_REQUIRED glib
AC_SUBST(CLOCK_CFLAGS)
AC_SUBST(CLOCK_LIBS)
+# Check for Evolution Data Server support for calendar integration
+AC_ARG_ENABLE([eds],
+ AS_HELP_STRING([--enable-eds], [Enable Evolution Data Server calendar integration @<:@default=auto@:>@]),
+ [enable_eds=$enableval],
+ [enable_eds=auto])
+
+if test "x$enable_eds" != "xno"; then
+ PKG_CHECK_MODULES(EDS, [libecal-2.0 >= 3.33.2 libedataserver-1.2 >= 3.5.3], have_eds=yes, have_eds=no)
+ if test "x$have_eds" = "xyes"; then
+ AC_DEFINE(HAVE_EDS, 1, [Define if evolution-data-server is available])
+ elif test "x$enable_eds" = "xyes"; then
+ AC_MSG_ERROR([EDS support requested but evolution-data-server development libraries not found])
+ fi
+else
+ have_eds=no
+fi
+
+AM_CONDITIONAL(HAVE_EDS, test "x$have_eds" = "xyes")
+AC_SUBST(EDS_CFLAGS)
+AC_SUBST(EDS_LIBS)
+
# Make it possible to compile the applets in-process
PANEL_INPROCESS_NONE=
PANEL_INPROCESS_ALL=
@@ -381,6 +394,7 @@ echo "
Wayland support: ${have_wayland}
X11 support: ${have_x11}
XRandr support: ${have_randr}
+ Evolution Data Server support: ${have_eds}
Build introspection support: ${found_introspection}
Build gtk-doc documentation: ${enable_gtk_doc}
diff --git a/libmate-panel-applet/mate-panel-applet-gsettings.c b/libmate-panel-applet/mate-panel-applet-gsettings.c
index 82867639..93d0b4ce 100644
--- a/libmate-panel-applet/mate-panel-applet-gsettings.c
+++ b/libmate-panel-applet/mate-panel-applet-gsettings.c
@@ -90,8 +90,21 @@ add_to_dict (GVariant *dict, const gchar *schema, const gchar *path)
static void
register_dconf_editor_relocatable_schema (const gchar *schema, const gchar *path)
{
- GSettings *dconf_editor_settings;
- dconf_editor_settings = g_settings_new ("ca.desrt.dconf-editor.Settings");
+ GSettingsSchemaSource *source;
+ GSettingsSchema *dconf_editor_schema;
+ GSettings *dconf_editor_settings;
+
+ source = g_settings_schema_source_get_default ();
+
+ if (! source)
+ return;
+
+ dconf_editor_schema = g_settings_schema_source_lookup (source, "ca.desrt.dconf-editor.Settings", FALSE);
+
+ if (! dconf_editor_schema)
+ return;
+
+ dconf_editor_settings = g_settings_new_full (dconf_editor_schema, NULL, NULL);
if (dconf_editor_settings && g_settings_is_writable (dconf_editor_settings, "relocatable-schemas-user-paths")) {
GVariant *relocatable_schemas = g_settings_get_value (dconf_editor_settings, "relocatable-schemas-user-paths");
@@ -108,6 +121,7 @@ register_dconf_editor_relocatable_schema (const gchar *schema, const gchar *path
}
g_object_unref (dconf_editor_settings);
+ g_settings_schema_unref (dconf_editor_schema);
}
GSettings *
diff --git a/libmate-panel-applet/mate-panel-applet.c b/libmate-panel-applet/mate-panel-applet.c
index 48e12622..897f1726 100644
--- a/libmate-panel-applet/mate-panel-applet.c
+++ b/libmate-panel-applet/mate-panel-applet.c
@@ -45,6 +45,10 @@
#include <X11/Xatom.h>
#include "panel-plug-private.h"
#endif
+#ifndef HAVE_X11
+#include <gdk/gdkwayland.h>
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
#include "mate-panel-applet.h"
#include "panel-applet-private.h"
diff --git a/mate-panel/libmate-panel-applet-private/panel-applet-frame-dbus.c b/mate-panel/libmate-panel-applet-private/panel-applet-frame-dbus.c
index 99346221..a11d43f4 100644
--- a/mate-panel/libmate-panel-applet-private/panel-applet-frame-dbus.c
+++ b/mate-panel/libmate-panel-applet-private/panel-applet-frame-dbus.c
@@ -245,6 +245,7 @@ mate_panel_applet_frame_dbus_change_background (MatePanelAppletFrame *frame,
MatePanelAppletFrameDBusPrivate *priv = dbus_frame->priv;
char *bg_str;
+#ifdef HAVE_X11
bg_str = _mate_panel_applet_frame_get_background_string (
frame, PANEL_WIDGET (gtk_widget_get_parent (GTK_WIDGET (frame))), type);
@@ -261,6 +262,7 @@ mate_panel_applet_frame_dbus_change_background (MatePanelAppletFrame *frame,
g_free (bg_str);
}
+#endif
}
static void
diff --git a/mate-panel/libmate-panel-applet-private/panel-applets-manager-dbus.c b/mate-panel/libmate-panel-applet-private/panel-applets-manager-dbus.c
index b65fe4a7..d78742e3 100644
--- a/mate-panel/libmate-panel-applet-private/panel-applets-manager-dbus.c
+++ b/mate-panel/libmate-panel-applet-private/panel-applets-manager-dbus.c
@@ -38,6 +38,9 @@
#ifdef HAVE_WAYLAND
#include <gdk/gdkwayland.h>
#endif
+#ifndef HAVE_X11
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
struct _MatePanelAppletsManagerDBusPrivate
{
diff --git a/mate-panel/panel-action-button.c b/mate-panel/panel-action-button.c
index 6fad288f..137399f5 100644
--- a/mate-panel/panel-action-button.c
+++ b/mate-panel/panel-action-button.c
@@ -65,6 +65,9 @@
#include <gdk/gdkwayland.h>
#endif
+#ifndef HAVE_X11
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
enum {
PROP_0,
@@ -343,7 +346,7 @@ static void
panel_action_shutdown (GtkWidget *widget)
{
#ifdef HAVE_WAYLAND
- GdkDisplay *display = gdk_screen_get_display (gdk_screen_get_default ());
+ GdkDisplay *display = gdk_display_get_default ();
if (GDK_IS_WAYLAND_DISPLAY (display))
{
GtkWidget *dialog, *hbox, *buttonbox, *shutdown_btn, *label;
@@ -424,7 +427,7 @@ panel_action_shutdown_reboot_is_disabled (void)
if (panel_lockdown_get_disable_log_out())
return TRUE;
#ifdef HAVE_WAYLAND
- GdkDisplay *display = gdk_screen_get_display (gdk_screen_get_default());
+ GdkDisplay *display = gdk_display_get_default ();
if (!(panel_lockdown_get_disable_log_out()) && (GDK_IS_WAYLAND_DISPLAY (display)))
return FALSE;
#endif
diff --git a/mate-panel/panel-applet-frame.c b/mate-panel/panel-applet-frame.c
index 2b0e4b7f..f0b5bfa9 100644
--- a/mate-panel/panel-applet-frame.c
+++ b/mate-panel/panel-applet-frame.c
@@ -645,6 +645,7 @@ _mate_panel_applet_frame_update_size_hints (MatePanelAppletFrame *frame,
n_elements);
}
+#ifdef HAVE_X11
char *
_mate_panel_applet_frame_get_background_string (MatePanelAppletFrame *frame,
PanelWidget *panel,
@@ -679,6 +680,8 @@ _mate_panel_applet_frame_get_background_string (MatePanelAppletFrame *frame,
return panel_background_make_string (&panel->toplevel->background, x, y);
}
+#endif
+
static void
mate_panel_applet_frame_reload_response (GtkWidget *dialog,
@@ -962,8 +965,8 @@ mate_panel_applet_frame_loading_failed (const char *iid,
_("Do you want to delete the applet "
"from your configuration?"));
- gtk_dialog_add_button (GTK_DIALOG (dialog),
- PANEL_STOCK_DONT_DELETE, LOADING_FAILED_RESPONSE_DONT_DELETE);
+ panel_dialog_add_button (GTK_DIALOG (dialog),
+ _("_Don't Delete"), "gtk-cancel", LOADING_FAILED_RESPONSE_DONT_DELETE);
panel_dialog_add_button (GTK_DIALOG (dialog),
_("_Delete"), "edit-delete", LOADING_FAILED_RESPONSE_DELETE);
diff --git a/mate-panel/panel-background.c b/mate-panel/panel-background.c
index b5c475d7..e9f3a6c2 100644
--- a/mate-panel/panel-background.c
+++ b/mate-panel/panel-background.c
@@ -763,6 +763,7 @@ panel_background_free (PanelBackground *background)
background->default_pattern = NULL;
}
+#ifdef HAVE_X11
char *
panel_background_make_string (PanelBackground *background,
int x,
@@ -799,6 +800,7 @@ panel_background_make_string (PanelBackground *background,
return retval;
}
+#endif
PanelBackgroundType
panel_background_get_type (PanelBackground *background)
diff --git a/mate-panel/panel-force-quit.c b/mate-panel/panel-force-quit.c
index 13b0139f..50a3be7d 100644
--- a/mate-panel/panel-force-quit.c
+++ b/mate-panel/panel-force-quit.c
@@ -249,9 +249,9 @@ kill_window_question (gpointer window)
_("_Cancel"), "process-stop",
GTK_RESPONSE_CANCEL);
- gtk_dialog_add_button (GTK_DIALOG (dialog),
- PANEL_STOCK_FORCE_QUIT,
- GTK_RESPONSE_ACCEPT);
+ panel_dialog_add_button (GTK_DIALOG (dialog),
+ _("_Force Quit"), "process-stop",
+ GTK_RESPONSE_ACCEPT);
gtk_dialog_set_default_response (GTK_DIALOG (dialog),
GTK_RESPONSE_CANCEL);
diff --git a/mate-panel/panel-layout.c b/mate-panel/panel-layout.c
index d1710f9e..d3bda7de 100644
--- a/mate-panel/panel-layout.c
+++ b/mate-panel/panel-layout.c
@@ -34,6 +34,11 @@
#include <gdk/gdkx.h>
#endif
+#ifndef HAVE_X11
+#include <gdk/gdkwayland.h>
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
+
#include <libmate-desktop/mate-dconf.h>
#include <libmate-desktop/mate-gsettings.h>
diff --git a/mate-panel/panel-menu-button.c b/mate-panel/panel-menu-button.c
index 92925fd8..e65a99d6 100644
--- a/mate-panel/panel-menu-button.c
+++ b/mate-panel/panel-menu-button.c
@@ -694,6 +694,8 @@ panel_menu_button_load (const char *menu_path,
return;
}
+ gtk_widget_set_name (GTK_WIDGET (button), "mate-panel-main-menu-button");
+
button->priv->applet_id = g_strdup (info->id);
mate_panel_applet_add_callback (info, "help", "help-browser", _("_Help"), NULL);
diff --git a/mate-panel/panel-multimonitor.c b/mate-panel/panel-multimonitor.c
index c7ac14cc..ca392d6b 100644
--- a/mate-panel/panel-multimonitor.c
+++ b/mate-panel/panel-multimonitor.c
@@ -33,6 +33,11 @@
#include <gdk/gdkx.h>
#endif /* HAVE_X11 */
+#ifndef HAVE_X11
+#include <gdk/gdkwayland.h>
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
+
#include "panel-multimonitor.h"
#include <string.h>
diff --git a/mate-panel/panel-profile.c b/mate-panel/panel-profile.c
index b637563a..08174fe3 100644
--- a/mate-panel/panel-profile.c
+++ b/mate-panel/panel-profile.c
@@ -36,6 +36,11 @@
#include <gdk/gdkx.h>
#endif
+#ifndef HAVE_X11
+#include <gdk/gdkwayland.h>
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
+
#include <libpanel-util/panel-list.h>
#include <libmate-desktop/mate-dconf.h>
#include <libmate-desktop/mate-gsettings.h>
@@ -862,8 +867,21 @@ remove_from_dict (GVariant *dict, const gchar *path)
static void
unregister_dconf_editor_relocatable_schema (const gchar *path)
{
- GSettings *dconf_editor_settings;
- dconf_editor_settings = g_settings_new ("ca.desrt.dconf-editor.Settings");
+ GSettingsSchemaSource *source;
+ GSettingsSchema *dconf_editor_schema;
+ GSettings *dconf_editor_settings;
+
+ source = g_settings_schema_source_get_default ();
+
+ if (! source)
+ return;
+
+ dconf_editor_schema = g_settings_schema_source_lookup (source, "ca.desrt.dconf-editor.Settings", FALSE);
+
+ if (! dconf_editor_schema)
+ return;
+
+ dconf_editor_settings = g_settings_new_full (dconf_editor_schema, NULL, NULL);
if (dconf_editor_settings && g_settings_is_writable (dconf_editor_settings, "relocatable-schemas-user-paths")) {
GVariant *relocatable_schemas = g_settings_get_value (dconf_editor_settings, "relocatable-schemas-user-paths");
@@ -878,6 +896,7 @@ unregister_dconf_editor_relocatable_schema (const gchar *path)
}
g_object_unref (dconf_editor_settings);
+ g_settings_schema_unref (dconf_editor_schema);
}
diff --git a/mate-panel/panel-recent.c b/mate-panel/panel-recent.c
index 2ed4715e..5fe25ddb 100644
--- a/mate-panel/panel-recent.c
+++ b/mate-panel/panel-recent.c
@@ -156,9 +156,9 @@ recent_documents_clear_cb (GtkMenuItem *menuitem,
_("_Cancel"), "process-stop",
GTK_RESPONSE_CANCEL);
- gtk_dialog_add_button (GTK_DIALOG (clear_recent_dialog),
- PANEL_STOCK_CLEAR,
- GTK_RESPONSE_ACCEPT);
+ panel_dialog_add_button (GTK_DIALOG (clear_recent_dialog),
+ _("_Clear"), "edit-clear",
+ GTK_RESPONSE_ACCEPT);
gtk_container_set_border_width (GTK_CONTAINER (clear_recent_dialog), 6);
diff --git a/mate-panel/panel-stock-icons.c b/mate-panel/panel-stock-icons.c
index 8e124709..bab01a33 100644
--- a/mate-panel/panel-stock-icons.c
+++ b/mate-panel/panel-stock-icons.c
@@ -54,83 +54,10 @@ GtkIconSize panel_add_to_icon_get_size(void)
return panel_add_to_icon_size;
}
-typedef struct {
- char *stock_id;
- char *icon;
-} PanelStockIcon;
-
-static PanelStockIcon stock_icons [] = {
- { PANEL_STOCK_FORCE_QUIT, PANEL_ICON_FORCE_QUIT }
-};
-
-static void
-panel_init_stock_icons (GtkIconFactory *factory)
-{
- GtkIconSource *source;
- gsize i;
-
- source = gtk_icon_source_new ();
-
- for (i = 0; i < G_N_ELEMENTS (stock_icons); i++) {
- GtkIconSet *set;
-
- gtk_icon_source_set_icon_name (source, stock_icons [i].icon);
-
- set = gtk_icon_set_new ();
- gtk_icon_set_add_source (set, source);
-
- gtk_icon_factory_add (factory, stock_icons [i].stock_id, set);
- gtk_icon_set_unref (set);
- }
-
- gtk_icon_source_free (source);
-
-}
-
-typedef struct {
- char *stock_id;
- char *stock_icon_id;
- char *label;
-} PanelStockItem;
-
-static PanelStockItem stock_items [] = {
- { PANEL_STOCK_EXECUTE, "gtk-execute", N_("_Run") },
- { PANEL_STOCK_FORCE_QUIT, PANEL_STOCK_FORCE_QUIT, N_("_Force quit") },
- { PANEL_STOCK_CLEAR, "gtk-clear", N_("C_lear") },
- { PANEL_STOCK_DONT_DELETE, "gtk-cancel", N_("D_on't Delete") }
-};
-
-static void
-panel_init_stock_items (GtkIconFactory *factory)
-{
- GtkStockItem *items;
- gsize n_items;
- gsize i;
-
- n_items = G_N_ELEMENTS (stock_items);
- items = g_new (GtkStockItem, n_items);
-
- for (i = 0; i < n_items; i++) {
- GtkIconSet *icon_set;
-
- items [i].stock_id = g_strdup (stock_items [i].stock_id);
- items [i].label = g_strdup (stock_items [i].label);
- items [i].modifier = 0;
- items [i].keyval = 0;
- items [i].translation_domain = g_strdup (GETTEXT_PACKAGE);
-
- /* FIXME: does this take into account the theme? */
- icon_set = gtk_icon_factory_lookup_default (stock_items [i].stock_icon_id);
- gtk_icon_factory_add (factory, stock_items [i].stock_id, icon_set);
- }
-
- gtk_stock_add_static (items, n_items);
-}
void
panel_init_stock_icons_and_items (void)
{
- GtkIconFactory *factory;
GSettings *settings;
gint icon_size;
@@ -163,12 +90,6 @@ panel_init_stock_icons_and_items (void)
PANEL_ADD_TO_DEFAULT_ICON_SIZE,
PANEL_ADD_TO_DEFAULT_ICON_SIZE);
- factory = gtk_icon_factory_new ();
- gtk_icon_factory_add_default (factory);
-
- panel_init_stock_icons (factory);
- panel_init_stock_items (factory);
- g_object_unref (factory);
g_object_unref (settings);
}
diff --git a/mate-panel/panel-stock-icons.h b/mate-panel/panel-stock-icons.h
index a43ca699..eeb536e8 100644
--- a/mate-panel/panel-stock-icons.h
+++ b/mate-panel/panel-stock-icons.h
@@ -41,18 +41,6 @@ extern "C" {
#define PANEL_ADD_TO_DEFAULT_ICON_SIZE 32
-/* stock icons */
-#define PANEL_STOCK_FORCE_QUIT "mate-panel-force-quit"
-
-/* stock items - no point in theme the icons one these,
- * they use stock gtk icons and just modify the text
- * for the stock item.
- */
-#define PANEL_STOCK_EXECUTE "panel-execute"
-#define PANEL_STOCK_CLEAR "panel-clear"
-#define PANEL_STOCK_DONT_DELETE "panel-dont-delete"
-/* FIXME: put a more representative icon here */
-#define PANEL_STOCK_DEFAULT_ICON "application-default-icon"
void panel_init_stock_icons_and_items (void);
GtkIconSize panel_menu_icon_get_size (void);
diff --git a/mate-panel/panel-test-applets.c b/mate-panel/panel-test-applets.c
index 79b48d36..9314a84f 100644
--- a/mate-panel/panel-test-applets.c
+++ b/mate-panel/panel-test-applets.c
@@ -161,7 +161,7 @@ load_applet_into_window (const char *title,
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{sv}",
- "prefs-path", g_variant_new_string (prefs_path));
+ "prefs-path", g_variant_new_string (prefs_path ? prefs_path : ""));
g_variant_builder_add (&builder, "{sv}",
"size", g_variant_new_uint32 (size));
g_variant_builder_add (&builder, "{sv}",
diff --git a/mate-panel/panel-toplevel.c b/mate-panel/panel-toplevel.c
index 2eb6e77a..bbe4980d 100644
--- a/mate-panel/panel-toplevel.c
+++ b/mate-panel/panel-toplevel.c
@@ -61,6 +61,9 @@
#ifdef HAVE_WAYLAND
#include "wayland-backend.h"
#endif
+#ifndef HAVE_X11
+#define GDK_IS_X11_DISPLAY(object) !(G_TYPE_CHECK_INSTANCE_TYPE ((object), GDK_TYPE_WAYLAND_DISPLAY))
+#endif
#define DEFAULT_SIZE 48
#define DEFAULT_AUTO_HIDE_SIZE 1
@@ -475,6 +478,16 @@ static void panel_toplevel_begin_grab_op(PanelToplevel* toplevel, PanelGrabOpTyp
GdkSeat *seat;
GdkSeatCapabilities capabilities;
+#ifdef HAVE_WAYLAND
+ /*FIXME: this disables dragging the panel in wayland entirely
+ *But it hasn't worked properly in wayland ever (panel "jumps to top or left)
+ *and a nonexpanded panel is forcibly centered by gtk-layer-shell so cannot
+ *be dragged along the screen edge
+ */
+ if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default()))
+ return;
+#endif
+
if (toplevel->priv->state != PANEL_STATE_NORMAL ||
toplevel->priv->grab_op != PANEL_GRAB_OP_NONE)
return;
@@ -857,7 +870,7 @@ static gboolean panel_toplevel_warp_pointer_increment(PanelToplevel* toplevel, i
GdkDevice *device;
int new_x, new_y;
- screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
+ screen = gtk_widget_get_screen (GTK_WIDGET (toplevel));
g_return_val_if_fail (GDK_IS_X11_SCREEN (screen), FALSE);
root_window = gdk_screen_get_root_window (screen);
device = gdk_seat_get_pointer (gdk_display_get_default_seat (gtk_widget_get_display (GTK_WIDGET(root_window))));
@@ -3499,6 +3512,15 @@ static gboolean
panel_toplevel_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event)
{
+#ifdef HAVE_WAYLAND
+ /*FIXME: this disables dragging the panel in wayland entirely
+ *But it hasn't worked properly in wayland ever (panel "jumps to top or left)
+ *and a nonexpanded panel is forcibly centered by gtk-layer-shell so cannot
+ *be dragged along the screen edge
+ */
+ if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default()))
+ return FALSE;
+#endif
if (gdk_event_get_screen ((GdkEvent *)event) ==
gtk_window_get_screen (GTK_WINDOW (widget)))
return panel_toplevel_handle_grab_op_motion_event (
diff --git a/mate-panel/panel.c b/mate-panel/panel.c
index e854cf73..065d2090 100644
--- a/mate-panel/panel.c
+++ b/mate-panel/panel.c
@@ -454,7 +454,8 @@ set_background_image_from_uri (PanelToplevel *toplevel,
static gboolean
set_background_color (PanelToplevel *toplevel,
- guint16 *dropped)
+ guint16 *dropped,
+ gint length)
{
GdkRGBA color;
@@ -465,10 +466,11 @@ set_background_color (PanelToplevel *toplevel,
! panel_profile_background_key_is_writable (toplevel, "type"))
return FALSE;
- color.red = dropped [0];
- color.green = dropped [1];
- color.blue = dropped [2];
- color.alpha = 1.;
+ const gdouble scale = 1.0 / 65535.0;
+ color.red = dropped[0] * scale;
+ color.green = dropped[1] * scale;
+ color.blue = dropped[2] * scale;
+ color.alpha = (length > 3) ? dropped[3] * scale : 1.0;
panel_profile_set_background_color (toplevel, &color);
panel_profile_set_background_type (toplevel, PANEL_BACK_COLOR);
@@ -1232,7 +1234,9 @@ panel_receive_dnd_data (PanelWidget *panel,
success = drop_url (panel, pos, (char *)data);
break;
case TARGET_COLOR:
- success = set_background_color (panel->toplevel, (guint16 *) data);
+ gint n_bytes = gtk_selection_data_get_length (selection_data);
+ gint length = n_bytes / sizeof (guint16);
+ success = set_background_color (panel->toplevel, (guint16 *) data, length);
break;
case TARGET_BGIMAGE:
success = set_background_image_from_uri (panel->toplevel, (char *) data);