diff options
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" @@ -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); |