aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt4
-rw-r--r--MERGE-REVIEW19
-rw-r--r--data/com.canonical.indicator.datetime3
-rw-r--r--debian/changelog76
-rw-r--r--debian/control2
-rw-r--r--include/datetime/actions-live.h1
-rw-r--r--include/datetime/actions.h1
-rw-r--r--include/datetime/clock-watcher.h75
-rw-r--r--include/datetime/date-time.h8
-rw-r--r--include/datetime/engine-eds.h75
-rw-r--r--include/datetime/engine-mock.h68
-rw-r--r--include/datetime/engine.h68
-rw-r--r--include/datetime/planner-month.h56
-rw-r--r--include/datetime/planner-range.h84
-rw-r--r--include/datetime/planner-upcoming.h56
-rw-r--r--include/datetime/planner.h29
-rw-r--r--include/datetime/snap.h51
-rw-r--r--include/datetime/state.h14
-rw-r--r--include/datetime/timezone-file.h4
-rw-r--r--po/POTFILES.in2
-rw-r--r--src/CMakeLists.txt7
-rw-r--r--src/actions-live.cpp32
-rw-r--r--src/actions.cpp13
-rw-r--r--src/clock-watcher.cpp81
-rw-r--r--src/date-time.cpp57
-rw-r--r--src/engine-eds.cpp (renamed from src/planner-eds.cpp)466
-rw-r--r--src/main.cpp48
-rw-r--r--src/menu.cpp137
-rw-r--r--src/planner-month.cpp66
-rw-r--r--src/planner-range.cpp105
-rw-r--r--src/planner-upcoming.cpp61
-rw-r--r--src/snap.cpp328
-rw-r--r--src/timezone-file.cpp27
-rw-r--r--src/utils.c4
-rw-r--r--tests/CMakeLists.txt5
-rw-r--r--tests/actions-mock.h5
-rw-r--r--tests/geoclue-fixture.h2
-rw-r--r--tests/manual24
-rw-r--r--tests/manual-test-snap.cpp63
-rw-r--r--tests/planner-mock.h24
-rw-r--r--tests/state-mock.h12
-rw-r--r--tests/test-actions.cpp12
-rw-r--r--tests/test-clock-watcher.cpp172
-rw-r--r--tests/test-clock.cpp4
-rw-r--r--tests/test-live-actions.cpp11
-rw-r--r--tests/test-menus.cpp62
-rw-r--r--tests/test-planner.cpp23
-rw-r--r--tests/test-settings.cpp4
-rw-r--r--tests/test-utils.cpp2
-rw-r--r--tests/timezone-mock.h (renamed from include/datetime/planner-eds.h)25
50 files changed, 2167 insertions, 411 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fcee8d5..9b468a5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -38,10 +38,10 @@ pkg_check_modules (SERVICE_DEPS REQUIRED
libical>=0.48
libecal-1.2>=3.5
libedataserver-1.2>=3.5
+ libcanberra>=0.12
libnotify>=0.7.6
url-dispatcher-1>=1
- properties-cpp>=0.0.1
- json-glib-1.0>=0.16.2)
+ properties-cpp>=0.0.1)
include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS})
##
diff --git a/MERGE-REVIEW b/MERGE-REVIEW
new file mode 100644
index 0000000..5e40f45
--- /dev/null
+++ b/MERGE-REVIEW
@@ -0,0 +1,19 @@
+
+This documents the expections that the project has on what both submitters
+and reviewers should ensure that they've done for a merge into the project.
+
+== Submitter Responsibilities ==
+
+ * Ensure the project compiles and the test suite executes without error
+ * Ensure that non-obvious code has comments explaining it
+ * If the change works on specific profiles, please include those in the merge description.
+
+== Reviewer Responsibilities ==
+
+ * Did the Jenkins build compile? Pass? Run unit tests successfully?
+ * Are there appropriate tests to cover any new functionality?
+ * If the description says this effects the phone profile:
+ * Run tests indicator-datetime/unity8*
+ * If the description says this effects the desktop profile:
+ * Run tests indicator-datetime/unity7*
+
diff --git a/data/com.canonical.indicator.datetime b/data/com.canonical.indicator.datetime
index 7fa1e34..b9de626 100644
--- a/data/com.canonical.indicator.datetime
+++ b/data/com.canonical.indicator.datetime
@@ -9,6 +9,9 @@ ObjectPath=/com/canonical/indicator/datetime/desktop
[desktop_greeter]
ObjectPath=/com/canonical/indicator/datetime/desktop_greeter
+[desktop_lockscreen]
+ObjectPath=/com/canonical/indicator/datetime/desktop_greeter
+
[phone]
ObjectPath=/com/canonical/indicator/datetime/phone
diff --git a/debian/changelog b/debian/changelog
index 097df7e..09f58b4 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,79 @@
+indicator-datetime (13.10.0+14.04.20140321-0ubuntu1) trusty; urgency=low
+
+ [ Charles Kerr ]
+ * If we notify_notification_show() fails, we shouldn't play the alarm
+ sound. The alarm sound loops until the user dismisses it, which will
+ never happen if we can't talk to unity-notiifications. So the
+ outcome in this error case is that the alarm plays forever and can't
+ be dismissed by the user. (LP: #1295237)
+ * Add debug logging of what capabilities the notification server said
+ it supports. (LP: #1295271)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 21 Mar 2014 05:23:04 +0000
+
+indicator-datetime (13.10.0+14.04.20140314.1-0ubuntu1) trusty; urgency=low
+
+ [ Charles Kerr ]
+ * When the user clicks on a date in the calendar, update the "Upcoming
+ Events" section to show events starting at that date. (LP: #1290169)
+ * Don't use EDS if we're in the greeter. (LP: #1256130)
+ * Don't show the "Add Event..." button if the calendar app can't be
+ found. (LP: #1250632)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Fri, 14 Mar 2014 17:37:32 +0000
+
+indicator-datetime (13.10.0+14.04.20140311.1-0ubuntu1) trusty; urgency=low
+
+ [ Lars Uebernickel ]
+ * add desktop_lockscreen profile
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 11 Mar 2014 18:16:48 +0000
+
+indicator-datetime (13.10.0+14.04.20140227.1-0ubuntu1) trusty; urgency=low
+
+ [ Charles Kerr ]
+ * When the notification engine is notify-osd, use bubble notifications
+ instead of the phone's snap decisions. (LP: #1283142)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 27 Feb 2014 18:44:10 +0000
+
+indicator-datetime (13.10.0+14.04.20140227-0ubuntu1) trusty; urgency=low
+
+ [ Charles Kerr ]
+ * Don't log E_CLIENT_ERROR_NOT_SUPPORTED errors returned by
+ e_cal_client_get_attachment_uris() -- we can silently interpret that
+ as 'no attachments' (LP: #1285212)
+ * DateTime::format(), don't pass NULL to a std::string's assignment
+ operator (LP: #1285243)
+ * In EdsPlanner's get_appointments(), sort 'em before returning them
+ to the caller. (LP: #1285249)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Thu, 27 Feb 2014 10:59:26 +0000
+
+indicator-datetime (13.10.0+14.04.20140225-0ubuntu1) trusty; urgency=low
+
+ [ Charles Kerr ]
+ * Test the EClient's color hint string for NULL before passing it to a
+ std::string constructor. (LP: #1283834)
+ * Test the EDS component summary for NULL before using it in a
+ std::string. (LP: #1280341)
+ * In the alarms menu, don't let iterations of recurring events drown
+ out everything else.
+ * Fix g_assert_if_reached() in EdsPlanner::on_source_enabled(). (LP:
+ #1283610)
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 25 Feb 2014 16:58:43 +0000
+
+indicator-datetime (13.10.0+14.04.20140219.1-0ubuntu1) trusty; urgency=low
+
+ [ Charles Kerr ]
+ * support for ubuntu-clock-app's alarms (LP: #1233176)
+
+ [ Ted Gould ]
+ * Adding acceptance tests and merge review policies
+
+ -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Wed, 19 Feb 2014 18:02:43 +0000
+
indicator-datetime (13.10.0+14.04.20140217-0ubuntu1) trusty; urgency=low
[ Robert Ancell ]
diff --git a/debian/control b/debian/control
index 423f8b8..84f76ca 100644
--- a/debian/control
+++ b/debian/control
@@ -16,13 +16,13 @@ Build-Depends: cmake,
libgtest-dev,
libglib2.0-dev (>= 2.35.4),
libnotify-dev (>= 0.7.6),
+ libcanberra-dev,
libido3-0.1-dev (>= 0.2.90),
libgeoclue-dev (>= 0.12.0),
libecal1.2-dev (>= 3.5),
libical-dev (>= 1.0),
libgtk-3-dev (>= 3.1.4),
libcairo2-dev (>= 1.10),
- libjson-glib-dev,
libpolkit-gobject-1-dev,
libedataserver1.2-dev (>= 3.5),
libgconf2-dev (>= 2.31),
diff --git a/include/datetime/actions-live.h b/include/datetime/actions-live.h
index 3607836..a24b844 100644
--- a/include/datetime/actions-live.h
+++ b/include/datetime/actions-live.h
@@ -42,6 +42,7 @@ public:
void open_desktop_settings();
void open_phone_settings();
void open_phone_clock_app();
+ bool can_open_planner() const;
void open_planner();
void open_planner_at(const DateTime&);
void open_appointment(const std::string& uid);
diff --git a/include/datetime/actions.h b/include/datetime/actions.h
index 99e78f5..2c4217c 100644
--- a/include/datetime/actions.h
+++ b/include/datetime/actions.h
@@ -45,6 +45,7 @@ public:
virtual void open_desktop_settings() =0;
virtual void open_phone_settings() =0;
virtual void open_phone_clock_app() =0;
+ virtual bool can_open_planner() const = 0;
virtual void open_planner() =0;
virtual void open_planner_at(const DateTime&) =0;
virtual void open_appointment(const std::string& uid) =0;
diff --git a/include/datetime/clock-watcher.h b/include/datetime/clock-watcher.h
new file mode 100644
index 0000000..90bbb63
--- /dev/null
+++ b/include/datetime/clock-watcher.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_CLOCK_WATCHER_H
+#define INDICATOR_DATETIME_CLOCK_WATCHER_H
+
+#include <datetime/appointment.h>
+#include <datetime/clock.h>
+#include <datetime/planner-upcoming.h>
+
+#include <core/signal.h>
+
+#include <memory>
+#include <set>
+#include <string>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+
+/**
+ * \brief Watches the clock and appointments to notify when an
+ * appointment's time is reached.
+ */
+class ClockWatcher
+{
+public:
+ ClockWatcher() =default;
+ virtual ~ClockWatcher() =default;
+ virtual core::Signal<const Appointment&>& alarm_reached() = 0;
+};
+
+
+/**
+ * \brief A #ClockWatcher implementation
+ */
+class ClockWatcherImpl: public ClockWatcher
+{
+public:
+ ClockWatcherImpl(const std::shared_ptr<Clock>& clock,
+ const std::shared_ptr<UpcomingPlanner>& upcoming_planner);
+ ~ClockWatcherImpl() =default;
+ core::Signal<const Appointment&>& alarm_reached();
+
+private:
+ void pulse();
+ std::set<std::string> m_triggered;
+ const std::shared_ptr<Clock> m_clock;
+ const std::shared_ptr<UpcomingPlanner> m_upcoming_planner;
+ core::Signal<const Appointment&> m_alarm_reached;
+};
+
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_CLOCK_WATCHER_H
diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h
index 2ad7856..f861c2e 100644
--- a/include/datetime/date-time.h
+++ b/include/datetime/date-time.h
@@ -36,21 +36,29 @@ class DateTime
{
public:
static DateTime NowLocal();
+ static DateTime Local(int years, int months, int days, int hours, int minutes, int seconds);
+
explicit DateTime(time_t t);
explicit DateTime(GDateTime* in=nullptr);
DateTime& operator=(GDateTime* in);
DateTime& operator=(const DateTime& in);
DateTime to_timezone(const std::string& zone) const;
+ DateTime add_full(int years, int months, int days, int hours, int minutes, double seconds) const;
void reset(GDateTime* in=nullptr);
GDateTime* get() const;
GDateTime* operator()() const {return get();}
std::string format(const std::string& fmt) const;
+ void ymd(int& year, int& month, int& day) const;
int day_of_month() const;
+ int hour() const;
+ int minute() const;
+ double seconds() const;
int64_t to_unix() const;
bool operator<(const DateTime& that) const;
+ bool operator<=(const DateTime& that) const;
bool operator!=(const DateTime& that) const;
bool operator==(const DateTime& that) const;
diff --git a/include/datetime/engine-eds.h b/include/datetime/engine-eds.h
new file mode 100644
index 0000000..4b260a8
--- /dev/null
+++ b/include/datetime/engine-eds.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_ENGINE_EDS__H
+#define INDICATOR_DATETIME_ENGINE_EDS__H
+
+#include <datetime/engine.h>
+
+#include <datetime/appointment.h>
+#include <datetime/date-time.h>
+#include <datetime/timezone.h>
+
+#include <functional>
+#include <vector>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/****
+*****
+****/
+
+/**
+ * Class wrapper around EDS so multiple #EdsPlanners can share resources
+ *
+ * @see EdsPlanner
+ */
+class EdsEngine: public Engine
+{
+public:
+ EdsEngine();
+ ~EdsEngine();
+
+ void get_appointments(const DateTime& begin,
+ const DateTime& end,
+ const Timezone& default_timezone,
+ std::function<void(const std::vector<Appointment>&)> appointment_func);
+
+ core::Signal<>& changed();
+
+private:
+ class Impl;
+ std::unique_ptr<Impl> p;
+
+ // we've got a unique_ptr here, disable copying...
+ EdsEngine(const EdsEngine&) =delete;
+ EdsEngine& operator=(const EdsEngine&) =delete;
+};
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_ENGINE_EDS__H
diff --git a/include/datetime/engine-mock.h b/include/datetime/engine-mock.h
new file mode 100644
index 0000000..ecbf102
--- /dev/null
+++ b/include/datetime/engine-mock.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_ENGINE_MOCK__H
+#define INDICATOR_DATETIME_ENGINE_MOCK__H
+
+#include <datetime/engine.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/****
+*****
+****/
+
+/**
+ * A no-op #Engine
+ *
+ * @see Engine
+ */
+class MockEngine: public Engine
+{
+public:
+ MockEngine() =default;
+ ~MockEngine() =default;
+
+ void get_appointments(const DateTime& /*begin*/,
+ const DateTime& /*end*/,
+ const Timezone& /*default_timezone*/,
+ std::function<void(const std::vector<Appointment>&)> appointment_func) {
+ appointment_func(m_appointments);
+ }
+
+ core::Signal<>& changed() {
+ return m_changed;
+ }
+
+private:
+ core::Signal<> m_changed;
+ std::vector<Appointment> m_appointments;
+};
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_ENGINE_NOOP__H
diff --git a/include/datetime/engine.h b/include/datetime/engine.h
new file mode 100644
index 0000000..2e8237e
--- /dev/null
+++ b/include/datetime/engine.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_ENGINE__H
+#define INDICATOR_DATETIME_ENGINE__H
+
+#include <datetime/appointment.h>
+#include <datetime/date-time.h>
+#include <datetime/timezone.h>
+
+#include <functional>
+#include <vector>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/****
+*****
+****/
+
+/**
+ * Class wrapper around the backend that generates appointments
+ *
+ * @see EdsEngine
+ * @see EdsPlanner
+ */
+class Engine
+{
+public:
+ virtual ~Engine() =default;
+
+ virtual void get_appointments(const DateTime& begin,
+ const DateTime& end,
+ const Timezone& default_timezone,
+ std::function<void(const std::vector<Appointment>&)> appointment_func) =0;
+
+ virtual core::Signal<>& changed() =0;
+
+protected:
+ Engine() =default;
+};
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_ENGINE__H
diff --git a/include/datetime/planner-month.h b/include/datetime/planner-month.h
new file mode 100644
index 0000000..492529f
--- /dev/null
+++ b/include/datetime/planner-month.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_PLANNER_MONTH_H
+#define INDICATOR_DATETIME_PLANNER_MONTH_H
+
+#include <datetime/planner.h>
+
+#include <datetime/date-time.h>
+#include <datetime/planner-range.h>
+
+#include <memory> // std::shared_ptr
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/**
+ * \brief A #Planner that contains appointments for a specified calendar month
+ */
+class MonthPlanner: public Planner
+{
+public:
+ MonthPlanner(const std::shared_ptr<RangePlanner>& range_planner,
+ const DateTime& month_in);
+ ~MonthPlanner() =default;
+
+ core::Property<std::vector<Appointment>>& appointments();
+ core::Property<DateTime>& month();
+
+private:
+ std::shared_ptr<RangePlanner> m_range_planner;
+ core::Property<DateTime> m_month;
+};
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_PLANNER_MONTH_H
diff --git a/include/datetime/planner-range.h b/include/datetime/planner-range.h
new file mode 100644
index 0000000..25334a6
--- /dev/null
+++ b/include/datetime/planner-range.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_PLANNER_RANGE_H
+#define INDICATOR_DATETIME_PLANNER_RANGE_H
+
+#include <datetime/planner.h>
+
+#include <datetime/date-time.h>
+#include <datetime/engine.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/**
+ * \brief A #Planner that contains appointments in a specified date range
+ *
+ * @see Planner
+ */
+class RangePlanner: public Planner
+{
+public:
+ virtual ~RangePlanner() =default;
+ virtual core::Property<std::pair<DateTime,DateTime>>& range() =0;
+
+protected:
+ RangePlanner() =default;
+};
+
+/**
+ * \brief A #RangePlanner that uses an #Engine to generate appointments
+ *
+ * @see Planner
+ */
+class SimpleRangePlanner: public RangePlanner
+{
+public:
+ SimpleRangePlanner(const std::shared_ptr<Engine>& engine,
+ const std::shared_ptr<Timezone>& timezone);
+ virtual ~SimpleRangePlanner();
+
+ core::Property<std::vector<Appointment>>& appointments();
+ core::Property<std::pair<DateTime,DateTime>>& range();
+
+private:
+ // rebuild scaffolding
+ void rebuild_soon();
+ virtual void rebuild_now();
+ static gboolean rebuild_now_static(gpointer);
+ guint m_rebuild_tag = 0;
+
+ std::shared_ptr<Engine> m_engine;
+ std::shared_ptr<Timezone> m_timezone;
+ core::Property<std::pair<DateTime,DateTime>> m_range;
+ core::Property<std::vector<Appointment>> m_appointments;
+
+ // we've got a GSignal tag here, so disable copying
+ SimpleRangePlanner(const RangePlanner&) =delete;
+ SimpleRangePlanner& operator=(const RangePlanner&) =delete;
+};
+
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_PLANNER_RANGE_H
diff --git a/include/datetime/planner-upcoming.h b/include/datetime/planner-upcoming.h
new file mode 100644
index 0000000..683543f
--- /dev/null
+++ b/include/datetime/planner-upcoming.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_PLANNER_UPCOMING_H
+#define INDICATOR_DATETIME_PLANNER_UPCOMING_H
+
+#include <datetime/planner.h>
+
+#include <datetime/date-time.h>
+#include <datetime/planner-range.h>
+
+#include <memory> // std::shared_ptr
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/**
+ * \brief A collection of upcoming appointments starting from the specified date
+ */
+class UpcomingPlanner: public Planner
+{
+public:
+ UpcomingPlanner(const std::shared_ptr<RangePlanner>& range_planner,
+ const DateTime& date);
+ ~UpcomingPlanner() =default;
+
+ core::Property<std::vector<Appointment>>& appointments();
+ core::Property<DateTime>& date();
+
+private:
+ std::shared_ptr<RangePlanner> m_range_planner;
+ core::Property<DateTime> m_date;
+};
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_PLANNER_UPCOMING_H
diff --git a/include/datetime/planner.h b/include/datetime/planner.h
index 376a31f..e6ef927 100644
--- a/include/datetime/planner.h
+++ b/include/datetime/planner.h
@@ -32,41 +32,16 @@ namespace indicator {
namespace datetime {
/**
- * \brief Simple appointment book
- *
- * @see EdsPlanner
- * @see State
+ * \brief Simple collection of appointments
*/
class Planner
{
public:
virtual ~Planner() =default;
-
- /**
- * \brief Timestamp used to determine the appointments in the `upcoming' and `this_month' properties.
- * Setting this value will cause the planner to re-query its backend and
- * update the `upcoming' and `this_month' properties.
- */
- core::Property<DateTime> time;
-
- /**
- * \brief The next few appointments that follow the time specified in the time property.
- */
- core::Property<std::vector<Appointment>> upcoming;
-
- /**
- * \brief The appointments that occur in the same month as the time property
- */
- core::Property<std::vector<Appointment>> this_month;
+ virtual core::Property<std::vector<Appointment>>& appointments() =0;
protected:
Planner() =default;
-
-private:
-
- // disable copying
- Planner(const Planner&) =delete;
- Planner& operator=(const Planner&) =delete;
};
} // namespace datetime
diff --git a/include/datetime/snap.h b/include/datetime/snap.h
new file mode 100644
index 0000000..a493772
--- /dev/null
+++ b/include/datetime/snap.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#ifndef INDICATOR_DATETIME_SNAP_H
+#define INDICATOR_DATETIME_SNAP_H
+
+#include <datetime/appointment.h>
+
+#include <memory>
+#include <functional>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/**
+ * \brief Pops up Snap Decisions for appointments
+ */
+class Snap
+{
+public:
+ Snap();
+ virtual ~Snap();
+
+ typedef std::function<void(const Appointment&)> appointment_func;
+ void operator()(const Appointment& appointment,
+ appointment_func show,
+ appointment_func dismiss);
+};
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
+
+#endif // INDICATOR_DATETIME_SNAP_H
diff --git a/include/datetime/state.h b/include/datetime/state.h
index 414be32..0e1043c 100644
--- a/include/datetime/state.h
+++ b/include/datetime/state.h
@@ -22,7 +22,8 @@
#include <datetime/clock.h>
#include <datetime/locations.h>
-#include <datetime/planner.h>
+#include <datetime/planner-month.h>
+#include <datetime/planner-upcoming.h>
#include <datetime/settings.h>
#include <datetime/timezones.h>
@@ -60,9 +61,14 @@ struct State
section of the #Menu */
std::shared_ptr<Locations> locations;
- /** \brief The appointments to be displayed in the Calendar and
- Appointments sections of the #Menu */
- std::shared_ptr<Planner> planner;
+ /** \brief Appointments in the month that's being displayed
+ in the calendar section of the #Menu */
+ std::shared_ptr<MonthPlanner> calendar_month;
+
+ /** \brief The next appointments that follow highlighed date
+ highlighted in the calendar section of the #Menu
+ (default date = today) */
+ std::shared_ptr<UpcomingPlanner> calendar_upcoming;
/** \brief Configuration options that modify the view */
std::shared_ptr<Settings> settings;
diff --git a/include/datetime/timezone-file.h b/include/datetime/timezone-file.h
index d77aaae..a67c01a 100644
--- a/include/datetime/timezone-file.h
+++ b/include/datetime/timezone-file.h
@@ -42,8 +42,8 @@ public:
~FileTimezone();
private:
- void setFilename(const std::string& filename);
- static void onFileChanged(gpointer gself);
+ void set_filename(const std::string& filename);
+ static void on_file_changed(gpointer gself);
void clear();
void reload();
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1b1ee58..3faca0b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,3 +1,5 @@
src/formatter.cpp
src/formatter-desktop.cpp
src/menu.cpp
+src/snap.cpp
+src/utils.c
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2cea786..9bc22f2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -13,15 +13,20 @@ add_library (${SERVICE_LIB} STATIC
appointment.cpp
clock.cpp
clock-live.cpp
+ clock-watcher.cpp
date-time.cpp
+ engine-eds.cpp
exporter.cpp
formatter.cpp
formatter-desktop.cpp
locations.cpp
locations-settings.cpp
menu.cpp
- planner-eds.cpp
+ planner-month.cpp
+ planner-range.cpp
+ planner-upcoming.cpp
settings-live.cpp
+ snap.cpp
timezone-file.cpp
timezone-geoclue.cpp
timezones-live.cpp
diff --git a/src/actions-live.cpp b/src/actions-live.cpp
index f510ed1..97b12db 100644
--- a/src/actions-live.cpp
+++ b/src/actions-live.cpp
@@ -74,6 +74,30 @@ void LiveActions::open_desktop_settings()
g_free (path);
}
+bool LiveActions::can_open_planner() const
+{
+ static bool inited = false;
+ static bool have_calendar = false;
+
+ if (G_UNLIKELY(!inited))
+ {
+ inited = true;
+
+ auto all = g_app_info_get_all_for_type ("text/calendar");
+ for(auto l=all; !have_calendar && l!=nullptr; l=l->next)
+ {
+ auto app_info = static_cast<GAppInfo*>(l->data);
+
+ if (!g_strcmp0("evolution.desktop", g_app_info_get_id(app_info)))
+ have_calendar = true;
+ }
+
+ g_list_free_full(all, (GDestroyNotify)g_object_unref);
+ }
+
+ return have_calendar;
+}
+
void LiveActions::open_planner()
{
execute_command("evolution -c calendar");
@@ -91,13 +115,15 @@ void LiveActions::open_phone_clock_app()
void LiveActions::open_planner_at(const DateTime& dt)
{
- auto cmd = dt.format("evolution \"calendar:///?startdate=%Y%m%d\"");
+ const auto day_begins = dt.add_full(0, 0, 0, -dt.hour(), -dt.minute(), -dt.seconds());
+ const auto gmt = day_begins.to_timezone("UTC");
+ auto cmd = gmt.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\"");
execute_command(cmd.c_str());
}
void LiveActions::open_appointment(const std::string& uid)
{
- for(const auto& appt : state()->planner->upcoming.get())
+ for(const auto& appt : state()->calendar_upcoming->appointments().get())
{
if(appt.uid != uid)
continue;
@@ -156,7 +182,7 @@ on_datetime1_proxy_ready (GObject * object G_GNUC_UNUSED,
GError * err = nullptr;
auto proxy = g_dbus_proxy_new_for_bus_finish(res, &err);
- if (err != NULL)
+ if (err != nullptr)
{
if (!g_error_matches(err, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning("Could not grab DBus proxy for timedated: %s", err->message);
diff --git a/src/actions.cpp b/src/actions.cpp
index d6fa698..c9c6286 100644
--- a/src/actions.cpp
+++ b/src/actions.cpp
@@ -65,7 +65,7 @@ void on_activate_appointment(GSimpleAction * /*action*/,
g_return_if_fail(uid && *uid);
// find url of the upcoming appointment with this uid
- for (const auto& appt : self->state()->planner->upcoming.get())
+ for (const auto& appt : self->state()->calendar_upcoming->appointments().get())
{
if (appt.uid == uid)
{
@@ -146,7 +146,7 @@ GVariant* create_default_header_state()
GVariant* create_calendar_state(const std::shared_ptr<State>& state)
{
gboolean days[32] = { 0 };
- for (const auto& appt : state->planner->this_month.get())
+ for (const auto& appt : state->calendar_month->appointments().get())
days[appt.begin.day_of_month()] = true;
GVariantBuilder day_builder;
@@ -163,7 +163,7 @@ GVariant* create_calendar_state(const std::shared_ptr<State>& state)
g_variant_builder_add(&dict_builder, "{sv}", key, v);
key = "calendar-day";
- v = g_variant_new_int64(state->planner->time.get().to_unix());
+ v = g_variant_new_int64(state->calendar_month->month().get().to_unix());
g_variant_builder_add(&dict_builder, "{sv}", key, v);
key = "show-week-numbers";
@@ -219,10 +219,10 @@ Actions::Actions(const std::shared_ptr<State>& state):
/// Keep our GActionGroup's action's states in sync with m_state
///
- m_state->planner->time.changed().connect([this](const DateTime&){
+ m_state->calendar_month->month().changed().connect([this](const DateTime&){
update_calendar_state();
});
- m_state->planner->this_month.changed().connect([this](const std::vector<Appointment>&){
+ m_state->calendar_month->appointments().changed().connect([this](const std::vector<Appointment>&){
update_calendar_state();
});
m_state->settings->show_week_numbers.changed().connect([this](bool){
@@ -246,7 +246,8 @@ void Actions::update_calendar_state()
void Actions::set_calendar_date(const DateTime& date)
{
- m_state->planner->time.set(date);
+ m_state->calendar_month->month().set(date);
+ m_state->calendar_upcoming->date().set(date);
}
GActionGroup* Actions::action_group()
diff --git a/src/clock-watcher.cpp b/src/clock-watcher.cpp
new file mode 100644
index 0000000..5da66c8
--- /dev/null
+++ b/src/clock-watcher.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/clock-watcher.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<Clock>& clock,
+ const std::shared_ptr<UpcomingPlanner>& upcoming_planner):
+ m_clock(clock),
+ m_upcoming_planner(upcoming_planner)
+{
+ m_clock->date_changed.connect([this](){
+ const auto now = m_clock->localtime();
+ g_debug("ClockWatcher %p refretching appointments due to date change: %s", this, now.format("%F %T").c_str());
+ m_upcoming_planner->date().set(now);
+ });
+
+ m_clock->minute_changed.connect([this](){
+ g_debug("ClockWatcher %p calling pulse() due to clock minute_changed", this);
+ pulse();
+ });
+
+ m_upcoming_planner->appointments().changed().connect([this](const std::vector<Appointment>&){
+ g_debug("ClockWatcher %p calling pulse() due to appointments changed", this);
+ pulse();
+ });
+
+ pulse();
+}
+
+core::Signal<const Appointment&>& ClockWatcherImpl::alarm_reached()
+{
+ return m_alarm_reached;
+}
+
+void ClockWatcherImpl::pulse()
+{
+ const auto now = m_clock->localtime();
+
+ for(const auto& appointment : m_upcoming_planner->appointments().get())
+ {
+ if (m_triggered.count(appointment.uid))
+ continue;
+ if (!DateTime::is_same_minute(now, appointment.begin))
+ continue;
+
+ m_triggered.insert(appointment.uid);
+ m_alarm_reached(appointment);
+ }
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/date-time.cpp b/src/date-time.cpp
index a634c5e..a1c1d1b 100644
--- a/src/date-time.cpp
+++ b/src/date-time.cpp
@@ -46,14 +46,22 @@ DateTime& DateTime::operator=(const DateTime& that)
DateTime::DateTime(time_t t)
{
- GDateTime * gdt = g_date_time_new_from_unix_local(t);
+ auto gdt = g_date_time_new_from_unix_local(t);
reset(gdt);
g_date_time_unref(gdt);
}
DateTime DateTime::NowLocal()
{
- GDateTime * gdt = g_date_time_new_now_local();
+ auto gdt = g_date_time_new_now_local();
+ DateTime dt(gdt);
+ g_date_time_unref(gdt);
+ return dt;
+}
+
+DateTime DateTime::Local(int year, int month, int day, int hour, int minute, int seconds)
+{
+ auto gdt = g_date_time_new_local (year, month, day, hour, minute, seconds);
DateTime dt(gdt);
g_date_time_unref(gdt);
return dt;
@@ -69,6 +77,14 @@ DateTime DateTime::to_timezone(const std::string& zone) const
return dt;
}
+DateTime DateTime::add_full(int years, int months, int days, int hours, int minutes, double seconds) const
+{
+ auto gdt = g_date_time_add_full(get(), years, months, days, hours, minutes, seconds);
+ DateTime dt(gdt);
+ g_date_time_unref(gdt);
+ return dt;
+}
+
GDateTime* DateTime::get() const
{
g_assert(m_dt);
@@ -77,17 +93,43 @@ GDateTime* DateTime::get() const
std::string DateTime::format(const std::string& fmt) const
{
- const auto str = g_date_time_format(get(), fmt.c_str());
- std::string ret = str;
- g_free(str);
+ std::string ret;
+
+ gchar* str = g_date_time_format(get(), fmt.c_str());
+ if (str)
+ {
+ ret = str;
+ g_free(str);
+ }
+
return ret;
}
+void DateTime::ymd(int& year, int& month, int& day) const
+{
+ g_date_time_get_ymd(get(), &year, &month, &day);
+}
+
int DateTime::day_of_month() const
{
return g_date_time_get_day_of_month(get());
}
+int DateTime::hour() const
+{
+ return g_date_time_get_hour(get());
+}
+
+int DateTime::minute() const
+{
+ return g_date_time_get_minute(get());
+}
+
+double DateTime::seconds() const
+{
+ return g_date_time_get_seconds(get());
+}
+
int64_t DateTime::to_unix() const
{
return g_date_time_to_unix(get());
@@ -112,6 +154,11 @@ bool DateTime::operator<(const DateTime& that) const
return g_date_time_compare(get(), that.get()) < 0;
}
+bool DateTime::operator<=(const DateTime& that) const
+{
+ return g_date_time_compare(get(), that.get()) <= 0;
+}
+
bool DateTime::operator!=(const DateTime& that) const
{
// return true if this isn't set, or if it's not equal
diff --git a/src/planner-eds.cpp b/src/engine-eds.cpp
index cb42d6e..c557857 100644
--- a/src/planner-eds.cpp
+++ b/src/engine-eds.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 Canonical Ltd.
+ * Copyright 2014 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3, as published
@@ -17,15 +17,17 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
-#include <datetime/planner-eds.h>
-
-#include <datetime/appointment.h>
+#include <datetime/engine-eds.h>
#include <libical/ical.h>
#include <libical/icaltime.h>
#include <libecal/libecal.h>
#include <libedataserver/libedataserver.h>
+#include <algorithm> // std::sort()
+#include <map>
+#include <set>
+
namespace unity {
namespace indicator {
namespace datetime {
@@ -34,25 +36,15 @@ namespace datetime {
*****
****/
-G_DEFINE_QUARK("source-client", source_client)
-
-
-class PlannerEds::Impl
+class EdsEngine::Impl
{
public:
- Impl(PlannerEds& owner):
+ Impl(EdsEngine& owner):
m_owner(owner),
m_cancellable(g_cancellable_new())
{
e_source_registry_new(m_cancellable, on_source_registry_ready, this);
-
- m_owner.time.changed().connect([this](const DateTime& dt) {
- g_debug("planner's datetime property changed to %s; calling rebuildSoon()", dt.format("%F %T").c_str());
- rebuildSoon();
- });
-
- rebuildSoon();
}
~Impl()
@@ -60,6 +52,9 @@ public:
g_cancellable_cancel(m_cancellable);
g_clear_object(&m_cancellable);
+ while(!m_sources.empty())
+ remove_source(*m_sources.begin());
+
if (m_rebuild_tag)
g_source_remove(m_rebuild_tag);
@@ -68,8 +63,99 @@ public:
g_clear_object(&m_source_registry);
}
+ core::Signal<>& changed()
+ {
+ return m_changed;
+ }
+
+ void get_appointments(const DateTime& begin,
+ const DateTime& end,
+ const Timezone& timezone,
+ std::function<void(const std::vector<Appointment>&)> func)
+ {
+ const auto begin_timet = begin.to_unix();
+ const auto end_timet = end.to_unix();
+
+ const auto b_str = begin.format("%F %T");
+ const auto e_str = end.format("%F %T");
+ g_debug("getting all appointments from [%s ... %s]", b_str.c_str(), e_str.c_str());
+
+ /**
+ *** init the default timezone
+ **/
+
+ icaltimezone * default_timezone = nullptr;
+ const auto tz = timezone.timezone.get().c_str();
+ if (tz && *tz)
+ {
+ default_timezone = icaltimezone_get_builtin_timezone(tz);
+
+ if (default_timezone == nullptr) // maybe str is a tzid?
+ default_timezone = icaltimezone_get_builtin_timezone_from_tzid(tz);
+
+ g_debug("default_timezone is %p", (void*)default_timezone);
+ }
+
+ /**
+ *** walk through the sources to build the appointment list
+ **/
+
+ auto task_deleter = [](Task* task){
+ // give the caller the (sorted) finished product
+ auto& a = task->appointments;
+ std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;});
+ task->func(a);
+ // we're done; delete the task
+ g_debug("time to delete task %p", (void*)task);
+ delete task;
+ };
+
+ std::shared_ptr<Task> main_task(new Task(this, func), task_deleter);
+
+ for (auto& kv : m_clients)
+ {
+ auto& client = kv.second;
+ if (default_timezone != nullptr)
+ e_cal_client_set_default_timezone(client, default_timezone);
+
+ // start a new subtask to enumerate all the components in this client.
+ auto& source = kv.first;
+ auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
+ const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));
+ g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
+ e_cal_client_generate_instances(client,
+ begin_timet,
+ end_timet,
+ m_cancellable,
+ my_get_appointments_foreach,
+ new AppointmentSubtask (main_task, client, color),
+ [](gpointer g){delete static_cast<AppointmentSubtask*>(g);});
+ }
+ }
+
private:
+ void set_dirty_now()
+ {
+ m_changed();
+ }
+
+ static gboolean set_dirty_now_static (gpointer gself)
+ {
+ auto self = static_cast<Impl*>(gself);
+ self->m_rebuild_tag = 0;
+ self->set_dirty_now();
+ return G_SOURCE_REMOVE;
+ }
+
+ void set_dirty_soon()
+ {
+ static const int ARBITRARY_BATCH_MSEC = 200;
+
+ if (m_rebuild_tag == 0)
+ m_rebuild_tag = g_timeout_add(ARBITRARY_BATCH_MSEC, set_dirty_now_static, this);
+ }
+
static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself)
{
GError * error = nullptr;
@@ -83,26 +169,31 @@ private:
}
else
{
- auto self = static_cast<Impl*>(gself);
-
- g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), self);
- g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), self);
- g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), self);
- g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), self);
- g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), self);
+ g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), gself);
+ g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), gself);
+ g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), gself);
+ g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself);
+ g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), gself);
+ auto self = static_cast<Impl*>(gself);
self->m_source_registry = r;
-
- GList* sources = e_source_registry_list_sources(r, E_SOURCE_EXTENSION_CALENDAR);
- for (auto l=sources; l!=nullptr; l=l->next)
- on_source_added(r, E_SOURCE(l->data), gself);
- g_list_free_full(sources, g_object_unref);
+ self->add_sources_by_extension(E_SOURCE_EXTENSION_CALENDAR);
+ self->add_sources_by_extension(E_SOURCE_EXTENSION_TASK_LIST);
}
}
+ void add_sources_by_extension(const char* extension)
+ {
+ auto& r = m_source_registry;
+ auto sources = e_source_registry_list_sources(r, extension);
+ for (auto l=sources; l!=nullptr; l=l->next)
+ on_source_added(r, E_SOURCE(l->data), this);
+ g_list_free_full(sources, g_object_unref);
+ }
+
static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself)
{
- auto self = static_cast<PlannerEds::Impl*>(gself);
+ auto self = static_cast<Impl*>(gself);
self->m_sources.insert(E_SOURCE(g_object_ref(source)));
@@ -112,13 +203,35 @@ private:
static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
{
- auto self = static_cast<PlannerEds::Impl*>(gself);
+ auto self = static_cast<Impl*>(gself);
+ ECalClientSourceType source_type;
+ bool client_wanted = false;
+
+ if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR))
+ {
+ source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS;
+ client_wanted = true;
+ }
+ else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST))
+ {
+ source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS;
+ client_wanted = true;
+ }
- e_cal_client_connect(source,
- E_CAL_CLIENT_SOURCE_TYPE_EVENTS,
- self->m_cancellable,
- on_client_connected,
- gself);
+ const auto source_uid = e_source_get_uid(source);
+ if (client_wanted)
+ {
+ g_debug("%s connecting a client to source %s", G_STRFUNC, source_uid);
+ e_cal_client_connect(source,
+ source_type,
+ self->m_cancellable,
+ on_client_connected,
+ gself);
+ }
+ else
+ {
+ g_debug("%s not using source %s -- no tasks/calendar", G_STRFUNC, source_uid);
+ }
}
static void on_client_connected(GObject* /*source*/, GAsyncResult * res, gpointer gself)
@@ -134,45 +247,118 @@ private:
}
else
{
- // we've got a new connected ECalClient, so store it & notify clients
- g_object_set_qdata_full(G_OBJECT(e_client_get_source(client)),
- source_client_quark(),
- client,
- g_object_unref);
-
- g_debug("client connected; calling rebuildSoon()");
- static_cast<Impl*>(gself)->rebuildSoon();
+ // add the client to our collection
+ auto self = static_cast<Impl*>(gself);
+ g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
+ self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client);
+
+ // now create a view for it so that we can listen for changes
+ e_cal_client_get_view (E_CAL_CLIENT(client),
+ "#t", // match all
+ self->m_cancellable,
+ on_client_view_ready,
+ self);
+
+ g_debug("client connected; calling set_dirty_soon()");
+ self->set_dirty_soon();
}
}
- static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
+ static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself)
{
- gpointer e_cal_client;
+ GError* error = nullptr;
+ ECalClientView* view = nullptr;
- // if this source has a connected ECalClient, remove it & notify clients
- if ((e_cal_client = g_object_steal_qdata(G_OBJECT(source), source_client_quark())))
+ if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error))
{
- g_object_unref(e_cal_client);
+ // add the view to our collection
+ e_cal_client_view_start(view, &error);
+ g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client)));
+ auto self = static_cast<Impl*>(gself);
+ self->m_views[e_client_get_source(E_CLIENT(client))] = view;
- g_debug("source disabled; calling rebuildSoon()");
- static_cast<Impl*>(gself)->rebuildSoon();
+ g_signal_connect(view, "objects-added", G_CALLBACK(on_view_objects_added), self);
+ g_signal_connect(view, "objects-modified", G_CALLBACK(on_view_objects_modified), self);
+ g_signal_connect(view, "objects-removed", G_CALLBACK(on_view_objects_removed), self);
+ g_debug("view connected; calling set_dirty_soon()");
+ self->set_dirty_soon();
+ }
+ else if(error != nullptr)
+ {
+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning("indicator-datetime cannot get View to EDS client: %s", error->message);
+
+ g_error_free(error);
}
}
- static void on_source_removed(ESourceRegistry* registry, ESource* source, gpointer gself)
+ static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
{
- auto self = static_cast<PlannerEds::Impl*>(gself);
+ g_debug("%s", G_STRFUNC);
+ static_cast<Impl*>(gself)->set_dirty_soon();
+ }
+ static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+ {
+ g_debug("%s", G_STRFUNC);
+ static_cast<Impl*>(gself)->set_dirty_soon();
+ }
+ static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself)
+ {
+ g_debug("%s", G_STRFUNC);
+ static_cast<Impl*>(gself)->set_dirty_soon();
+ }
+
+ static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
+ {
+ static_cast<Impl*>(gself)->disable_source(source);
+ }
+ void disable_source(ESource* source)
+ {
+ // if an ECalClientView is associated with this source, remove it
+ auto vit = m_views.find(source);
+ if (vit != m_views.end())
+ {
+ auto& view = vit->second;
+ e_cal_client_view_stop(view, nullptr);
+ const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this);
+ g_warn_if_fail(n_disconnected == 3);
+ g_object_unref(view);
+ m_views.erase(vit);
+ set_dirty_soon();
+ }
+
+ // if an ECalClient is associated with this source, remove it
+ auto cit = m_clients.find(source);
+ if (cit != m_clients.end())
+ {
+ auto& client = cit->second;
+ g_object_unref(client);
+ m_clients.erase(cit);
+ set_dirty_soon();
+ }
+ }
- on_source_disabled(registry, source, gself);
+ static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself)
+ {
+ static_cast<Impl*>(gself)->remove_source(source);
+ }
+ void remove_source(ESource* source)
+ {
+ disable_source(source);
- self->m_sources.erase(source);
- g_object_unref(source);
+ auto sit = m_sources.find(source);
+ if (sit != m_sources.end())
+ {
+ g_object_unref(*sit);
+ m_sources.erase(sit);
+ set_dirty_soon();
+ }
}
static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself)
{
- g_debug("source changed; calling rebuildSoon()");
- static_cast<Impl*>(gself)->rebuildSoon();
+ g_debug("source changed; calling set_dirty_soon()");
+ static_cast<Impl*>(gself)->set_dirty_soon();
}
private:
@@ -193,121 +379,12 @@ private:
ECalClient* client;
std::string color;
AppointmentSubtask(const std::shared_ptr<Task>& task_in, ECalClient* client_in, const char* color_in):
- task(task_in), client(client_in), color(color_in) {}
- };
-
- void rebuildSoon()
- {
- const static guint ARBITRARY_INTERVAL_SECS = 2;
-
- if (m_rebuild_tag == 0)
- m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuildNowStatic, this);
- }
-
- static gboolean rebuildNowStatic(gpointer gself)
- {
- auto self = static_cast<Impl*>(gself);
- self->m_rebuild_tag = 0;
- self->rebuildNow();
- return G_SOURCE_REMOVE;
- }
-
- void rebuildNow()
- {
- const auto calendar_date = m_owner.time.get().get();
- GDateTime* begin;
- GDateTime* end;
- int y, m, d;
-
- // get all the appointments in the calendar month
- g_date_time_get_ymd(calendar_date, &y, &m, &d);
- begin = g_date_time_new_local(y, m, 1, 0, 0, 0.1);
- end = g_date_time_new_local(y, m, g_date_get_days_in_month(GDateMonth(m),GDateYear(y)), 23, 59, 59.9);
- if (begin && end)
+ task(task_in), client(client_in)
{
- getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) {
- g_debug("got %d appointments in this calendar month", (int)appointments.size());
- m_owner.this_month.set(appointments);
- });
+ if (color_in)
+ color = color_in;
}
- g_clear_pointer(&begin, g_date_time_unref);
- g_clear_pointer(&end, g_date_time_unref);
-
- // get the upcoming appointments
- begin = g_date_time_ref(calendar_date);
- end = g_date_time_add_months(begin, 1);
- if (begin && end)
- {
- getAppointments(begin, end, [this](const std::vector<Appointment>& appointments) {
- g_debug("got %d upcoming appointments", (int)appointments.size());
- m_owner.upcoming.set(appointments);
- });
- }
- g_clear_pointer(&begin, g_date_time_unref);
- g_clear_pointer(&end, g_date_time_unref);
- }
-
- void getAppointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func)
- {
- const auto begin = g_date_time_to_unix(begin_dt);
- const auto end = g_date_time_to_unix(end_dt);
-
- auto begin_str = g_date_time_format(begin_dt, "%F %T");
- auto end_str = g_date_time_format(end_dt, "%F %T");
- g_debug("getting all appointments from [%s ... %s]", begin_str, end_str);
- g_free(begin_str);
- g_free(end_str);
-
- /**
- *** init the default timezone
- **/
-
- icaltimezone * default_timezone = nullptr;
-
- const auto tz = g_date_time_get_timezone_abbreviation(m_owner.time.get().get());
- g_debug("%s tz is %s", G_STRLOC, tz);
- if (tz && *tz)
- {
- default_timezone = icaltimezone_get_builtin_timezone(tz);
-
- if (default_timezone == nullptr) // maybe str is a tzid?
- default_timezone = icaltimezone_get_builtin_timezone_from_tzid(tz);
-
- g_debug("default_timezone is %p", (void*)default_timezone);
- }
-
- /**
- *** walk through the sources to build the appointment list
- **/
-
- std::shared_ptr<Task> main_task(new Task(this, func), [](Task* task){
- g_debug("time to delete task %p", (void*)task);
- task->func(task->appointments);
- delete task;
- });
-
- for (auto& source : m_sources)
- {
- auto client = E_CAL_CLIENT(g_object_get_qdata(G_OBJECT(source), source_client_quark()));
- if (client == nullptr)
- continue;
-
- if (default_timezone != nullptr)
- e_cal_client_set_default_timezone(client, default_timezone);
-
- // start a new subtask to enumerate all the components in this client.
- auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR);
- const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension));
- g_debug("calling e_cal_client_generate_instances for %p", (void*)client);
- e_cal_client_generate_instances(client,
- begin,
- end,
- m_cancellable,
- my_get_appointments_foreach,
- new AppointmentSubtask (main_task, client, color),
- [](gpointer g){delete static_cast<AppointmentSubtask*>(g);});
- }
- }
+ };
struct UrlSubtask
{
@@ -355,14 +432,15 @@ private:
e_cal_component_free_recur_list(recur_list);
ECalComponentText text;
- text.value = "";
+ text.value = nullptr;
e_cal_component_get_summary(component, &text);
+ if (text.value)
+ appointment.summary = text.value;
appointment.begin = DateTime(begin);
appointment.end = DateTime(end);
appointment.color = subtask->color;
appointment.is_event = vtype == E_CAL_COMPONENT_EVENT;
- appointment.summary = text.value;
appointment.uid = uid;
GList * alarm_uids = e_cal_component_get_alarm_uids(component);
@@ -390,8 +468,11 @@ private:
e_cal_client_get_attachment_uris_finish(E_CAL_CLIENT(client), res, &uris, &error);
if (error != nullptr)
{
- if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
+ !g_error_matches(error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED))
+ {
g_warning("Error getting appointment uris: %s", error->message);
+ }
g_error_free(error);
}
@@ -407,18 +488,43 @@ private:
delete subtask;
}
-private:
-
- PlannerEds& m_owner;
+ EdsEngine& m_owner;
+ core::Signal<> m_changed;
std::set<ESource*> m_sources;
- GCancellable * m_cancellable = nullptr;
- ESourceRegistry * m_source_registry = nullptr;
+ std::map<ESource*,ECalClient*> m_clients;
+ std::map<ESource*,ECalClientView*> m_views;
+ GCancellable* m_cancellable = nullptr;
+ ESourceRegistry* m_source_registry = nullptr;
guint m_rebuild_tag = 0;
};
-PlannerEds::PlannerEds(): p(new Impl(*this)) {}
+/***
+****
+***/
+
+EdsEngine::EdsEngine():
+ p(new Impl(*this))
+{
+}
+
+EdsEngine::~EdsEngine() =default;
+
+core::Signal<>& EdsEngine::changed()
+{
+ return p->changed();
+}
+
+void EdsEngine::get_appointments(const DateTime& begin,
+ const DateTime& end,
+ const Timezone& tz,
+ std::function<void(const std::vector<Appointment>&)> func)
+{
+ p->get_appointments(begin, end, tz, func);
+}
-PlannerEds::~PlannerEds() =default;
+/***
+****
+***/
} // namespace datetime
} // namespace indicator
diff --git a/src/main.cpp b/src/main.cpp
index 1534777..c7b35e5 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,24 +17,28 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-
-
#include <datetime/actions-live.h>
#include <datetime/clock.h>
+#include <datetime/clock-watcher.h>
+#include <datetime/engine-mock.h>
+#include <datetime/engine-eds.h>
#include <datetime/exporter.h>
#include <datetime/locations-settings.h>
#include <datetime/menu.h>
-#include <datetime/planner-eds.h>
+#include <datetime/planner-range.h>
#include <datetime/settings-live.h>
+#include <datetime/snap.h>
#include <datetime/state.h>
+#include <datetime/timezone-file.h>
#include <datetime/timezones-live.h>
#include <glib/gi18n.h> // bindtextdomain()
#include <gio/gio.h>
-#include <libnotify/notify.h>
+
+#include <url-dispatcher.h>
#include <locale.h>
-#include <stdlib.h> // exit()
+#include <cstdlib> // exit()
using namespace unity::indicator::datetime;
@@ -50,23 +54,47 @@ main(int /*argc*/, char** /*argv*/)
bindtextdomain(GETTEXT_PACKAGE, GNOMELOCALEDIR);
textdomain(GETTEXT_PACKAGE);
- // init libnotify
- if(!notify_init("indicator-datetime-service"))
- g_critical("libnotify initialization failed");
+ // we don't show appointments in the greeter,
+ // so no need to connect to EDS there...
+ std::shared_ptr<Engine> engine;
+ if (!g_strcmp0("lightdm", g_get_user_name()))
+ engine.reset(new MockEngine);
+ else
+ engine.reset(new EdsEngine);
// build the state, actions, and menufactory
std::shared_ptr<State> state(new State);
std::shared_ptr<Settings> live_settings(new LiveSettings);
std::shared_ptr<Timezones> live_timezones(new LiveTimezones(live_settings, TIMEZONE_FILE));
std::shared_ptr<Clock> live_clock(new LiveClock(live_timezones));
+ std::shared_ptr<Timezone> file_timezone(new FileTimezone(TIMEZONE_FILE));
+ const auto now = live_clock->localtime();
state->settings = live_settings;
state->clock = live_clock;
state->locations.reset(new SettingsLocations(live_settings, live_timezones));
- state->planner.reset(new PlannerEds);
- state->planner->time = live_clock->localtime();
+ auto calendar_month = new MonthPlanner(std::shared_ptr<RangePlanner>(new SimpleRangePlanner(engine, file_timezone)), now);
+ state->calendar_month.reset(calendar_month);
+ state->calendar_upcoming.reset(new UpcomingPlanner(std::shared_ptr<RangePlanner>(new SimpleRangePlanner(engine, file_timezone)), now));
std::shared_ptr<Actions> actions(new LiveActions(state));
MenuFactory factory(actions, state);
+ // snap decisions
+ std::shared_ptr<UpcomingPlanner> upcoming_planner(new UpcomingPlanner(std::shared_ptr<RangePlanner>(new SimpleRangePlanner(engine, file_timezone)), now));
+ ClockWatcherImpl clock_watcher(live_clock, upcoming_planner);
+ Snap snap;
+ clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){
+ auto snap_show = [](const Appointment& a){
+ const char* url;
+ if(!a.url.empty())
+ url = a.url.c_str();
+ else // alarm doesn't have a URl associated with it; use a fallback
+ url = "appid://com.ubuntu.clock/clock/current-user-version";
+ url_dispatch_send(url, nullptr, nullptr);
+ };
+ auto snap_dismiss = [](const Appointment&){};
+ snap(appt, snap_show, snap_dismiss);
+ });
+
// create the menus
std::vector<std::shared_ptr<Menu>> menus;
for(int i=0, n=Menu::NUM_PROFILES; i<n; i++)
diff --git a/src/menu.cpp b/src/menu.cpp
index bdf92c3..90ef41f 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -22,11 +22,11 @@
#include <datetime/formatter.h>
#include <datetime/state.h>
-#include <json-glib/json-glib.h>
-
#include <glib/gi18n.h>
#include <gio/gio.h>
+#include <vector>
+
namespace unity {
namespace indicator {
namespace datetime {
@@ -62,7 +62,7 @@ GMenuModel* Menu::menu_model()
****/
-#define FALLBACK_ALARM_CLOCK_ICON_NAME "clock"
+#define ALARM_ICON_NAME "alarm-clock"
#define CALENDAR_ICON_NAME "calendar"
class MenuImpl: public Menu
@@ -104,13 +104,16 @@ protected:
m_state->settings->show_events.changed().connect([this](bool){
update_section(Appointments); // showing events got toggled
});
- m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){
- update_section(Appointments); // "upcoming" is the list of Appointments we show
+ m_state->calendar_upcoming->appointments().changed().connect([this](const std::vector<Appointment>&){
+ update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
});
m_state->clock->date_changed.connect([this](){
update_section(Calendar); // need to update the Date menuitem
update_section(Locations); // locations' relative time may have changed
});
+ m_state->clock->minute_changed.connect([this](){
+ update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time
+ });
m_state->locations->locations.changed().connect([this](const std::vector<Location>&) {
update_section(Locations); // "locations" is the list of Locations we show
});
@@ -133,6 +136,30 @@ protected:
g_action_group_change_action_state(action_group, action_name.c_str(), state);
}
+ void update_upcoming()
+ {
+ // show upcoming appointments that occur after "calendar_next_minute",
+ // where that is the wallclock time on the specified calendar day
+ const auto calendar_day = m_state->calendar_month->month().get();
+ const auto now = m_state->clock->localtime();
+ int y, m, d;
+ calendar_day.ymd(y, m, d);
+ const auto calendar_now = DateTime::Local(y, m, d, now.hour(), now.minute(), now.seconds());
+ const auto calendar_next_minute = calendar_now.add_full(0, 0, 0, 0, 1, -now.seconds());
+
+ std::vector<Appointment> upcoming;
+ for(const auto& a : m_state->calendar_upcoming->appointments().get())
+ if (calendar_next_minute <= a.begin)
+ upcoming.push_back(a);
+
+ if (m_upcoming != upcoming)
+ {
+ m_upcoming.swap(upcoming);
+ update_header(); // show an 'alarm' icon if there are upcoming alarms
+ update_section(Appointments); // "upcoming" is the list of Appointments we show
+ }
+ }
+
std::shared_ptr<const State> m_state;
std::shared_ptr<Actions> m_actions;
std::shared_ptr<const Formatter> m_formatter;
@@ -141,71 +168,19 @@ protected:
GVariant* get_serialized_alarm_icon()
{
if (G_UNLIKELY(m_serialized_alarm_icon == nullptr))
- m_serialized_alarm_icon = create_alarm_icon();
-
- return m_serialized_alarm_icon;
- }
-
-private:
-
- /* try to get the clock app's filename from click. (/$pkgdir/$icon) */
- static GVariant* create_alarm_icon()
- {
- GVariant* serialized = nullptr;
- gchar* icon_filename = nullptr;
- gchar* standard_error = nullptr;
- gchar* pkgdir = nullptr;
-
- g_spawn_command_line_sync("click pkgdir com.ubuntu.clock", &pkgdir, &standard_error, nullptr, nullptr);
- g_clear_pointer(&standard_error, g_free);
- if (pkgdir != nullptr)
- {
- gchar* manifest = nullptr;
- g_strstrip(pkgdir);
- g_spawn_command_line_sync("click info com.ubuntu.clock", &manifest, &standard_error, nullptr, nullptr);
- g_clear_pointer(&standard_error, g_free);
- if (manifest != nullptr)
- {
- JsonParser* parser = json_parser_new();
- if (json_parser_load_from_data(parser, manifest, -1, nullptr))
- {
- JsonNode* root = json_parser_get_root(parser); /* transfer-none */
- if ((root != nullptr) && (JSON_NODE_TYPE(root) == JSON_NODE_OBJECT))
- {
- JsonObject* o = json_node_get_object(root); /* transfer-none */
- const gchar* icon_name = json_object_get_string_member(o, "icon");
- if (icon_name != nullptr)
- icon_filename = g_build_filename(pkgdir, icon_name, nullptr);
- }
- }
- g_object_unref(parser);
- g_free(manifest);
- }
- g_free(pkgdir);
- }
-
- if (icon_filename != nullptr)
- {
- GFile* file = g_file_new_for_path(icon_filename);
- GIcon* icon = g_file_icon_new(file);
-
- serialized = g_icon_serialize(icon);
-
- g_object_unref(icon);
- g_object_unref(file);
- g_free(icon_filename);
- }
-
- if (serialized == nullptr)
{
- auto i = g_themed_icon_new_with_default_fallbacks(FALLBACK_ALARM_CLOCK_ICON_NAME);
- serialized = g_icon_serialize(i);
+ auto i = g_themed_icon_new_with_default_fallbacks(ALARM_ICON_NAME);
+ m_serialized_alarm_icon = g_icon_serialize(i);
g_object_unref(i);
}
- return serialized;
+ return m_serialized_alarm_icon;
}
+ std::vector<Appointment> m_upcoming;
+
+private:
+
GVariant* get_serialized_calendar_icon()
{
if (G_UNLIKELY(m_serialized_calendar_icon == nullptr))
@@ -273,7 +248,7 @@ private:
// add calendar
if (show_calendar)
{
- item = g_menu_item_new ("[calendar]", NULL);
+ item = g_menu_item_new ("[calendar]", nullptr);
v = g_variant_new_int64(0);
g_menu_item_set_action_and_target_value (item, "indicator.calendar", v);
g_menu_item_set_attribute (item, "x-canonical-type",
@@ -292,18 +267,19 @@ private:
void add_appointments(GMenu* menu, Profile profile)
{
- int n = 0;
const int MAX_APPTS = 5;
std::set<std::string> added;
- for (const auto& appt : m_state->planner->upcoming.get())
+ for (const auto& appt : m_upcoming)
{
- if (n++ >= MAX_APPTS)
- break;
-
+ // don't show duplicates
if (added.count(appt.uid))
continue;
+ // don't show too many
+ if (g_menu_model_get_n_items (G_MENU_MODEL(menu)) >= MAX_APPTS)
+ break;
+
added.insert(appt.uid);
GDateTime* begin = appt.begin();
@@ -332,7 +308,7 @@ private:
g_menu_item_set_action_and_target_value (menu_item,
"indicator.activate-appointment",
g_variant_new_string (appt.uid.c_str()));
- else
+ else if (m_actions->can_open_planner())
g_menu_item_set_action_and_target_value (menu_item,
"indicator.activate-planner",
g_variant_new_int64 (unix_time));
@@ -349,13 +325,16 @@ private:
{
add_appointments (menu, profile);
- // add the 'Add Event…' menuitem
- auto menu_item = g_menu_item_new(_("Add Event…"), nullptr);
- const gchar* action_name = "indicator.activate-planner";
- auto v = g_variant_new_int64(0);
- g_menu_item_set_action_and_target_value(menu_item, action_name, v);
- g_menu_append_item(menu, menu_item);
- g_object_unref(menu_item);
+ if (m_actions->can_open_planner())
+ {
+ // add the 'Add Event…' menuitem
+ auto menu_item = g_menu_item_new(_("Add Event…"), nullptr);
+ const gchar* action_name = "indicator.activate-planner";
+ auto v = g_variant_new_int64(0);
+ g_menu_item_set_action_and_target_value(menu_item, action_name, v);
+ g_menu_append_item(menu, menu_item);
+ g_object_unref(menu_item);
+ }
}
else if (profile==Phone)
{
@@ -508,7 +487,7 @@ protected:
{
// are there alarms?
bool has_alarms = false;
- for(const auto& appointment : m_state->planner->upcoming.get())
+ for(const auto& appointment : m_upcoming)
if((has_alarms = appointment.has_alarms))
break;
diff --git a/src/planner-month.cpp b/src/planner-month.cpp
new file mode 100644
index 0000000..5920daa
--- /dev/null
+++ b/src/planner-month.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/planner-month.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+MonthPlanner::MonthPlanner(const std::shared_ptr<RangePlanner>& range_planner,
+ const DateTime& month_in):
+ m_range_planner(range_planner)
+{
+ month().changed().connect([this](const DateTime& m){
+ auto month_begin = m.add_full(0, // no years
+ 0, // no months
+ -(m.day_of_month()-1),
+ -m.hour(),
+ -m.minute(),
+ -m.seconds());
+ auto month_end = month_begin.add_full(0, 1, 0, 0, 0, -0.1);
+ g_debug("PlannerMonth %p setting calendar month range: [%s..%s]", this, month_begin.format("%F %T").c_str(), month_end.format("%F %T").c_str());
+ m_range_planner->range().set(std::pair<DateTime,DateTime>(month_begin,month_end));
+ });
+
+ month().set(month_in);
+}
+
+core::Property<DateTime>& MonthPlanner::month()
+{
+ return m_month;
+}
+
+core::Property<std::vector<Appointment>>& MonthPlanner::appointments()
+{
+ return m_range_planner->appointments();
+}
+
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/planner-range.cpp b/src/planner-range.cpp
new file mode 100644
index 0000000..93946e0
--- /dev/null
+++ b/src/planner-range.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/planner-range.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+SimpleRangePlanner::SimpleRangePlanner(const std::shared_ptr<Engine>& engine,
+ const std::shared_ptr<Timezone>& timezone):
+ m_engine(engine),
+ m_timezone(timezone),
+ m_range(std::pair<DateTime,DateTime>(DateTime::NowLocal(), DateTime::NowLocal()))
+{
+ engine->changed().connect([this](){
+ g_debug("RangePlanner %p rebuilding soon because Engine %p emitted 'changed' signal%p", this, m_engine.get());
+ rebuild_soon();
+ });
+
+ range().changed().connect([this](const std::pair<DateTime,DateTime>&){
+ g_debug("rebuilding because the date range changed");
+ rebuild_soon();
+ });
+}
+
+SimpleRangePlanner::~SimpleRangePlanner()
+{
+ if (m_rebuild_tag)
+ g_source_remove(m_rebuild_tag);
+}
+
+/***
+****
+***/
+
+void SimpleRangePlanner::rebuild_now()
+{
+ const auto& r = range().get();
+
+ auto on_appointments_fetched = [this](const std::vector<Appointment>& a){
+ g_debug("RangePlanner %p got %zu appointments", this, a.size());
+ appointments().set(a);
+ };
+
+ m_engine->get_appointments(r.first, r.second, *m_timezone.get(), on_appointments_fetched);
+}
+
+void SimpleRangePlanner::rebuild_soon()
+{
+ static const int ARBITRARY_BATCH_MSEC = 200;
+
+ if (m_rebuild_tag == 0)
+ m_rebuild_tag = g_timeout_add(ARBITRARY_BATCH_MSEC, rebuild_now_static, this);
+}
+
+gboolean SimpleRangePlanner::rebuild_now_static(gpointer gself)
+{
+ auto self = static_cast<SimpleRangePlanner*>(gself);
+ self->m_rebuild_tag = 0;
+ self->rebuild_now();
+ return G_SOURCE_REMOVE;
+}
+
+/***
+****
+***/
+
+core::Property<std::vector<Appointment>>& SimpleRangePlanner::appointments()
+{
+ return m_appointments;
+}
+
+core::Property<std::pair<DateTime,DateTime>>& SimpleRangePlanner::range()
+{
+ return m_range;
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/planner-upcoming.cpp b/src/planner-upcoming.cpp
new file mode 100644
index 0000000..4e5af6f
--- /dev/null
+++ b/src/planner-upcoming.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/planner-upcoming.h>
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+UpcomingPlanner::UpcomingPlanner(const std::shared_ptr<RangePlanner>& range_planner,
+ const DateTime& date_in):
+ m_range_planner(range_planner)
+{
+ date().changed().connect([this](const DateTime& dt){
+ // set the range to the upcoming month
+ const auto b = dt.add_full(0, 0, -1, 0, 0, 0);
+ const auto e = dt.add_full(0, 1, 0, 0, 0, 0);
+ g_debug("%p setting date range to [%s..%s]", this, b.format("%F %T").c_str(), e.format("%F %T").c_str());
+ m_range_planner->range().set(std::pair<DateTime,DateTime>(b,e));
+ });
+
+ date().set(date_in);
+}
+
+core::Property<DateTime>& UpcomingPlanner::date()
+{
+ return m_date;
+}
+
+core::Property<std::vector<Appointment>>& UpcomingPlanner::appointments()
+{
+ return m_range_planner->appointments();
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/snap.cpp b/src/snap.cpp
new file mode 100644
index 0000000..3ab2941
--- /dev/null
+++ b/src/snap.cpp
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/appointment.h>
+#include <datetime/formatter.h>
+#include <datetime/snap.h>
+
+#include <canberra.h>
+#include <libnotify/notify.h>
+
+#include <glib/gi18n.h>
+#include <glib.h>
+
+#include <set>
+#include <string>
+
+#define ALARM_SOUND_FILENAME "/usr/share/sounds/ubuntu/stereo/phone-incoming-call.ogg"
+
+namespace unity {
+namespace indicator {
+namespace datetime {
+
+/***
+****
+***/
+
+namespace
+{
+
+/**
+*** libcanberra -- play sounds
+**/
+
+// arbitrary number, but we need a consistent id for play/cancel
+const int32_t alarm_ca_id = 1;
+
+gboolean media_cached = FALSE;
+ca_context *c_context = nullptr;
+guint timeout_tag = 0;
+
+ca_context* get_ca_context()
+{
+ if (G_UNLIKELY(c_context == nullptr))
+ {
+ int rv;
+
+ if ((rv = ca_context_create(&c_context)) != CA_SUCCESS)
+ {
+ g_warning("Failed to create canberra context: %s\n", ca_strerror(rv));
+ c_context = nullptr;
+ }
+ else
+ {
+ const char* filename = ALARM_SOUND_FILENAME;
+ rv = ca_context_cache(c_context,
+ CA_PROP_EVENT_ID, "alarm",
+ CA_PROP_MEDIA_FILENAME, filename,
+ CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
+ NULL);
+ media_cached = rv == CA_SUCCESS;
+ if (rv != CA_SUCCESS)
+ g_warning("Couldn't add '%s' to canberra cache: %s", filename, ca_strerror(rv));
+ }
+ }
+
+ return c_context;
+}
+
+void play_alarm_sound();
+
+gboolean play_alarm_sound_idle (gpointer)
+{
+ timeout_tag = 0;
+ play_alarm_sound();
+ return G_SOURCE_REMOVE;
+}
+
+void on_alarm_play_done (ca_context* /*context*/, uint32_t /*id*/, int rv, void* /*user_data*/)
+{
+ // wait one second, then play it again
+ if ((rv == CA_SUCCESS) && (timeout_tag == 0))
+ timeout_tag = g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr);
+}
+
+void play_alarm_sound()
+{
+ const gchar* filename = ALARM_SOUND_FILENAME;
+ auto context = get_ca_context();
+ g_return_if_fail(context != nullptr);
+
+ ca_proplist* props = nullptr;
+ ca_proplist_create(&props);
+ if (media_cached)
+ ca_proplist_sets(props, CA_PROP_EVENT_ID, "alarm");
+ ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, filename);
+
+ const auto rv = ca_context_play_full(context, alarm_ca_id, props, on_alarm_play_done, nullptr);
+ if (rv != CA_SUCCESS)
+ g_warning("Failed to play file '%s': %s", filename, ca_strerror(rv));
+
+ g_clear_pointer(&props, ca_proplist_destroy);
+}
+
+void stop_alarm_sound()
+{
+ auto context = get_ca_context();
+ if (context != nullptr)
+ {
+ const auto rv = ca_context_cancel(context, alarm_ca_id);
+ if (rv != CA_SUCCESS)
+ g_warning("Failed to cancel alarm sound: %s", ca_strerror(rv));
+ }
+
+ if (timeout_tag != 0)
+ {
+ g_source_remove(timeout_tag);
+ timeout_tag = 0;
+ }
+}
+
+/**
+*** libnotify -- snap decisions
+**/
+
+void first_time_init()
+{
+ static bool inited = false;
+
+ if (G_UNLIKELY(!inited))
+ {
+ inited = true;
+
+ if(!notify_init("indicator-datetime-service"))
+ g_critical("libnotify initialization failed");
+ }
+}
+
+struct SnapData
+{
+ Snap::appointment_func show;
+ Snap::appointment_func dismiss;
+ Appointment appointment;
+};
+
+void on_snap_show(NotifyNotification*, gchar* /*action*/, gpointer gdata)
+{
+ stop_alarm_sound();
+ auto data = static_cast<SnapData*>(gdata);
+ data->show(data->appointment);
+}
+
+void on_snap_dismiss(NotifyNotification*, gchar* /*action*/, gpointer gdata)
+{
+ stop_alarm_sound();
+ auto data = static_cast<SnapData*>(gdata);
+ data->dismiss(data->appointment);
+}
+
+void on_snap_closed(NotifyNotification*, gpointer)
+{
+ stop_alarm_sound();
+}
+
+void snap_data_destroy_notify(gpointer gdata)
+{
+ delete static_cast<SnapData*>(gdata);
+}
+
+std::set<std::string> get_server_caps()
+{
+ std::set<std::string> caps_set;
+ auto caps_gl = notify_get_server_caps();
+ std::string caps_str;
+ for(auto l=caps_gl; l!=nullptr; l=l->next)
+ {
+ caps_set.insert((const char*)l->data);
+
+ caps_str += (const char*) l->data;;
+ if (l->next != nullptr)
+ caps_str += ", ";
+ }
+ g_debug ("%s notify_get_server() returned [%s]", G_STRFUNC, caps_str.c_str());
+ g_list_free_full(caps_gl, g_free);
+ return caps_set;
+}
+
+typedef enum
+{
+ // just a bubble... no actions, no audio
+ NOTIFY_MODE_BUBBLE,
+
+ // a snap decision popup dialog + audio
+ NOTIFY_MODE_SNAP
+}
+NotifyMode;
+
+NotifyMode get_notify_mode()
+{
+ static NotifyMode mode;
+ static bool mode_inited = false;
+
+ if (G_UNLIKELY(!mode_inited))
+ {
+ const auto caps = get_server_caps();
+
+ if (caps.count("actions"))
+ mode = NOTIFY_MODE_SNAP;
+ else
+ mode = NOTIFY_MODE_BUBBLE;
+
+ mode_inited = true;
+ }
+
+ return mode;
+}
+
+bool show_notification (SnapData* data, NotifyMode mode)
+{
+ const Appointment& appointment = data->appointment;
+
+ const auto timestr = appointment.begin.format("%a, %X");
+ auto title = g_strdup_printf(_("Alarm %s"), timestr.c_str());
+ const auto body = appointment.summary;
+ const gchar* icon_name = "alarm-clock";
+
+ auto nn = notify_notification_new(title, body.c_str(), icon_name);
+ if (mode == NOTIFY_MODE_SNAP)
+ {
+ notify_notification_set_hint_string(nn, "x-canonical-snap-decisions", "true");
+ notify_notification_set_hint_string(nn, "x-canonical-private-button-tint", "true");
+ notify_notification_add_action(nn, "show", _("Show"), on_snap_show, data, nullptr);
+ notify_notification_add_action(nn, "dismiss", _("Dismiss"), on_snap_dismiss, data, nullptr);
+ g_signal_connect(G_OBJECT(nn), "closed", G_CALLBACK(on_snap_closed), data);
+ }
+ g_object_set_data_full(G_OBJECT(nn), "snap-data", data, snap_data_destroy_notify);
+
+ bool shown = true;
+ GError * error = nullptr;
+ notify_notification_show(nn, &error);
+ if (error != NULL)
+ {
+ g_critical("Unable to show snap decision for '%s': %s", body.c_str(), error->message);
+ g_error_free(error);
+ data->show(data->appointment);
+ shown = false;
+ }
+
+ g_free(title);
+ return shown;
+}
+
+/**
+***
+**/
+
+void notify(const Appointment& appointment,
+ Snap::appointment_func show,
+ Snap::appointment_func dismiss)
+{
+ auto data = new SnapData;
+ data->appointment = appointment;
+ data->show = show;
+ data->dismiss = dismiss;
+
+ switch (get_notify_mode())
+ {
+ case NOTIFY_MODE_BUBBLE:
+ show_notification(data, NOTIFY_MODE_BUBBLE);
+ break;
+
+ default:
+ if (show_notification(data, NOTIFY_MODE_SNAP))
+ play_alarm_sound();
+ break;
+ }
+}
+
+} // unnamed namespace
+
+
+/***
+****
+***/
+
+Snap::Snap()
+{
+ first_time_init();
+}
+
+Snap::~Snap()
+{
+ media_cached = false;
+ g_clear_pointer(&c_context, ca_context_destroy);
+}
+
+void Snap::operator()(const Appointment& appointment,
+ appointment_func show,
+ appointment_func dismiss)
+{
+ if (appointment.has_alarms)
+ notify(appointment, show, dismiss);
+ else
+ dismiss(appointment);
+}
+
+/***
+****
+***/
+
+} // namespace datetime
+} // namespace indicator
+} // namespace unity
diff --git a/src/timezone-file.cpp b/src/timezone-file.cpp
index 76737b4..c99897a 100644
--- a/src/timezone-file.cpp
+++ b/src/timezone-file.cpp
@@ -19,6 +19,9 @@
#include <datetime/timezone-file.h>
+#include <cerrno>
+#include <cstdlib>
+
namespace unity {
namespace indicator {
namespace datetime {
@@ -29,7 +32,7 @@ FileTimezone::FileTimezone()
FileTimezone::FileTimezone(const std::string& filename)
{
- setFilename(filename);
+ set_filename(filename);
}
FileTimezone::~FileTimezone()
@@ -49,13 +52,23 @@ FileTimezone::clear()
}
void
-FileTimezone::setFilename(const std::string& filename)
+FileTimezone::set_filename(const std::string& filename)
{
clear();
- m_filename = filename;
+ auto tmp = realpath(filename.c_str(), nullptr);
+ if(tmp != nullptr)
+ {
+ m_filename = tmp;
+ free(tmp);
+ }
+ else
+ {
+ g_warning("Unable to resolve path '%s': %s", filename.c_str(), g_strerror(errno));
+ m_filename = filename; // better than nothing?
+ }
- auto file = g_file_new_for_path(filename.c_str());
+ auto file = g_file_new_for_path(m_filename.c_str());
GError * err = nullptr;
m_monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, nullptr, &err);
g_object_unref(file);
@@ -66,15 +79,15 @@ FileTimezone::setFilename(const std::string& filename)
}
else
{
- m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(onFileChanged), this);
- g_debug("%s Monitoring timezone file '%s'", G_STRLOC, filename.c_str());
+ m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(on_file_changed), this);
+ g_debug("%s Monitoring timezone file '%s'", G_STRLOC, m_filename.c_str());
}
reload();
}
void
-FileTimezone::onFileChanged(gpointer gself)
+FileTimezone::on_file_changed(gpointer gself)
{
static_cast<FileTimezone*>(gself)->reload();
}
diff --git a/src/utils.c b/src/utils.c
index f4eb53f..c9107ce 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -159,6 +159,10 @@ getDateProximity(GDateTime* now, GDateTime* time)
gint now_year, now_month, now_day;
gint time_year, time_month, time_day;
+ // did it already happen?
+ if (g_date_time_difference(time, now) < -G_USEC_PER_SEC)
+ return DATE_PROXIMITY_FAR;
+
// does it happen today?
g_date_time_get_ymd(now, &now_year, &now_month, &now_day);
g_date_time_get_ymd(time, &time_year, &time_month, &time_day);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 3dcd151..7d590c9 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -42,6 +42,7 @@ function(add_test_by_name name)
endfunction()
add_test_by_name(test-actions)
add_test_by_name(test-clock)
+add_test_by_name(test-clock-watcher)
add_test_by_name(test-exporter)
add_test_by_name(test-formatter)
add_test_by_name(test-live-actions)
@@ -52,6 +53,10 @@ add_test_by_name(test-settings)
add_test_by_name(test-timezone-file)
add_test_by_name(test-utils)
+set (TEST_NAME manual-test-snap)
+add_executable (${TEST_NAME} ${TEST_NAME}.cpp)
+add_dependencies (${TEST_NAME} libindicatordatetimeservice)
+target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
# disabling the timezone unit tests because they require
# https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724
diff --git a/tests/actions-mock.h b/tests/actions-mock.h
index da93cb9..ebd8a4d 100644
--- a/tests/actions-mock.h
+++ b/tests/actions-mock.h
@@ -49,6 +49,8 @@ public:
void open_phone_clock_app() { m_history.push_back(OpenPhoneClockApp); }
+ bool can_open_planner() const { return m_can_open_planner; }
+
void open_planner() { m_history.push_back(OpenPlanner); }
void open_planner_at(const DateTime& date_time_) {
@@ -67,7 +69,10 @@ public:
m_url = url_;
}
+ void set_can_open_planner(bool b) { m_can_open_planner = b; }
+
private:
+ bool m_can_open_planner = true;
std::string m_url;
std::string m_zone;
std::string m_name;
diff --git a/tests/geoclue-fixture.h b/tests/geoclue-fixture.h
index 7e29018..0c597d3 100644
--- a/tests/geoclue-fixture.h
+++ b/tests/geoclue-fixture.h
@@ -95,7 +95,7 @@ class GeoclueFixture : public GlibFixture
// I've looked and can't find where this extra ref is coming from.
// is there an unbalanced ref to the bus in the test harness?!
- while (bus != NULL)
+ while (bus != nullptr)
{
g_object_unref (bus);
wait_msec (1000);
diff --git a/tests/manual b/tests/manual
new file mode 100644
index 0000000..17b4778
--- /dev/null
+++ b/tests/manual
@@ -0,0 +1,24 @@
+
+Test-case indicator-datetime/unity7-items-check
+<dl>
+ <dt>Log in to a Unity 7 user session</dt>
+ <dt>Go to the panel and click on the DateTime indicator</dt>
+ <dd>Ensure there are items in the menu</dd>
+</dl>
+
+Test-case indicator-datetime/unity7-greeter-items-check
+<dl>
+ <dt>Start a system and wait for the greeter or logout of the current user session</dt>
+ <dt>Go to the panel and click on the DateTime indicator</dt>
+ <dd>Ensure there are items in the menu</dd>
+</dl>
+
+Test-case indicator-datetime/unity8-items-check
+<dl>
+ <dt>Login to a user session running Unity 8</dt>
+ <dt>Pull down the top panel until it sticks open</dt>
+ <dt>Navigate through the tabs until "Upcoming" is shown</dt>
+ <dd>Upcoming is at the top of the menu</dd>
+ <dd>The menu is populated with items</dd>
+</dl>
+
diff --git a/tests/manual-test-snap.cpp b/tests/manual-test-snap.cpp
new file mode 100644
index 0000000..51556cd
--- /dev/null
+++ b/tests/manual-test-snap.cpp
@@ -0,0 +1,63 @@
+
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <datetime/appointment.h>
+#include <datetime/snap.h>
+
+#include <glib.h>
+
+using namespace unity::indicator::datetime;
+
+/***
+****
+***/
+
+int main()
+{
+ Appointment a;
+ a.color = "green";
+ a.summary = "Alarm";
+ a.url = "alarm:///hello-world";
+ a.uid = "D4B57D50247291478ED31DED17FF0A9838DED402";
+ a.is_event = false;
+ a.is_daily = false;
+ a.has_alarms = true;
+ auto begin = g_date_time_new_local(2014,12,25,0,0,0);
+ auto end = g_date_time_add_full(begin,0,0,1,0,0,-1);
+ a.begin = begin;
+ a.end = end;
+ g_date_time_unref(end);
+ g_date_time_unref(begin);
+
+ auto loop = g_main_loop_new(nullptr, false);
+ auto show = [loop](const Appointment& appt){
+ g_message("You clicked 'show' for appt url '%s'", appt.url.c_str());
+ g_main_loop_quit(loop);
+ };
+ auto dismiss = [loop](const Appointment&){
+ g_message("You clicked 'dismiss'");
+ g_main_loop_quit(loop);
+ };
+
+ Snap snap;
+ snap(a, show, dismiss);
+ g_main_loop_run(loop);
+ return 0;
+}
diff --git a/tests/planner-mock.h b/tests/planner-mock.h
index 44d30c7..53109cf 100644
--- a/tests/planner-mock.h
+++ b/tests/planner-mock.h
@@ -20,22 +20,34 @@
#ifndef INDICATOR_DATETIME_PLANNER_MOCK_H
#define INDICATOR_DATETIME_PLANNER_MOCK_H
-#include <datetime/planner.h>
+#include <datetime/planner-range.h>
namespace unity {
namespace indicator {
namespace datetime {
/**
- * \brief Planner which does nothing on its own.
- * It requires its client must set its appointments property.
+ * \brief #RangePlanner which does nothing on its own.
+ * Its controller must set its appointments property.
*/
-class MockPlanner: public Planner
+class MockRangePlanner: public RangePlanner
{
public:
- MockPlanner() =default;
- virtual ~MockPlanner() =default;
+ MockRangePlanner():
+ m_range(std::pair<DateTime,DateTime>(DateTime::NowLocal(), DateTime::NowLocal()))
+ {
+ }
+
+ ~MockRangePlanner() =default;
+
+ core::Property<std::vector<Appointment>>& appointments() { return m_appointments; }
+ core::Property<std::pair<DateTime,DateTime>>& range() { return m_range; }
+
+private:
+ core::Property<std::vector<Appointment>> m_appointments;
+ core::Property<std::pair<DateTime,DateTime>> m_range;
};
+
} // namespace datetime
} // namespace indicator
diff --git a/tests/state-mock.h b/tests/state-mock.h
index 721b82f..792c60d 100644
--- a/tests/state-mock.h
+++ b/tests/state-mock.h
@@ -28,15 +28,21 @@ class MockState: public State
{
public:
std::shared_ptr<MockClock> mock_clock;
+ std::shared_ptr<MockRangePlanner> mock_range_planner;
MockState()
{
const DateTime now = DateTime::NowLocal();
mock_clock.reset(new MockClock(now));
- settings.reset(new Settings);
clock = std::dynamic_pointer_cast<Clock>(mock_clock);
- planner.reset(new MockPlanner);
- planner->time = now;
+
+ settings.reset(new Settings);
+
+ mock_range_planner.reset(new MockRangePlanner);
+ auto range_planner = std::dynamic_pointer_cast<RangePlanner>(mock_range_planner);
+ calendar_month.reset(new MonthPlanner(range_planner, now));
+ calendar_upcoming.reset(new UpcomingPlanner(range_planner, now));
+
locations.reset(new Locations);
}
};
diff --git a/tests/test-actions.cpp b/tests/test-actions.cpp
index 1865cfd..5d1efd5 100644
--- a/tests/test-actions.cpp
+++ b/tests/test-actions.cpp
@@ -150,10 +150,10 @@ TEST_F(ActionsFixture, SetCalendarDate)
// confirm that Planner.time gets changed to that date when we
// activate the 'calendar' action with that date's time_t as the arg
- EXPECT_NE (now, m_state->planner->time.get());
+ EXPECT_NE (now, m_state->calendar_month->month().get());
auto v = g_variant_new_int64(now.to_unix());
g_action_group_activate_action (action_group, action_name, v);
- EXPECT_EQ (now, m_state->planner->time.get());
+ EXPECT_EQ (now, m_state->calendar_month->month().get());
}
TEST_F(ActionsFixture, ActivatingTheCalendarResetsItsDate)
@@ -171,11 +171,12 @@ TEST_F(ActionsFixture, ActivatingTheCalendarResetsItsDate)
const auto now = m_state->clock->localtime();
auto next_week = g_date_time_add_weeks(now.get(), 1);
const auto next_week_unix = g_date_time_to_unix(next_week);
+ g_date_time_unref(next_week);
g_action_group_activate_action (action_group, "calendar", g_variant_new_int64(next_week_unix));
// confirm the planner and calendar action state moved a week into the future
// but that m_state->clock is unchanged
- EXPECT_EQ(next_week_unix, m_state->planner->time.get().to_unix());
+ EXPECT_EQ(next_week_unix, m_state->calendar_month->month().get().to_unix());
EXPECT_EQ(now, m_state->clock->localtime());
auto calendar_state = g_action_group_get_action_state(action_group, "calendar");
EXPECT_TRUE(calendar_state != nullptr);
@@ -196,7 +197,7 @@ TEST_F(ActionsFixture, ActivatingTheCalendarResetsItsDate)
g_action_group_change_action_state(action_group, "calendar-active", g_variant_new_boolean(true));
// confirm the planner and calendar action state were reset back to m_state->clock's time
- EXPECT_EQ(now.to_unix(), m_state->planner->time.get().to_unix());
+ EXPECT_EQ(now.to_unix(), m_state->calendar_month->month().get().to_unix());
EXPECT_EQ(now, m_state->clock->localtime());
calendar_state = g_action_group_get_action_state(action_group, "calendar");
EXPECT_TRUE(calendar_state != nullptr);
@@ -215,7 +216,8 @@ TEST_F(ActionsFixture, OpenAppointment)
Appointment appt;
appt.uid = "some arbitrary uid";
appt.url = "http://www.canonical.com/";
- m_state->planner->upcoming.set(std::vector<Appointment>({appt}));
+ appt.begin = m_state->clock->localtime();
+ m_state->calendar_upcoming->appointments().set(std::vector<Appointment>({appt}));
const auto action_name = "activate-appointment";
auto action_group = m_actions->action_group();
diff --git a/tests/test-clock-watcher.cpp b/tests/test-clock-watcher.cpp
new file mode 100644
index 0000000..2425fe8
--- /dev/null
+++ b/tests/test-clock-watcher.cpp
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2014 Canonical Ltd.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
+
+#include <datetime/clock-watcher.h>
+
+#include <gtest/gtest.h>
+
+#include "state-fixture.h"
+
+using namespace unity::indicator::datetime;
+
+class ClockWatcherFixture: public StateFixture
+{
+private:
+
+ typedef StateFixture super;
+
+protected:
+
+ std::vector<std::string> m_triggered;
+ std::unique_ptr<ClockWatcher> m_watcher;
+ std::shared_ptr<RangePlanner> m_range_planner;
+ std::shared_ptr<UpcomingPlanner> m_upcoming;
+
+ void SetUp()
+ {
+ super::SetUp();
+
+ m_range_planner.reset(new MockRangePlanner);
+ m_upcoming.reset(new UpcomingPlanner(m_range_planner, m_state->clock->localtime()));
+ m_watcher.reset(new ClockWatcherImpl(m_state->clock, m_upcoming));
+ m_watcher->alarm_reached().connect([this](const Appointment& appt){
+ m_triggered.push_back(appt.uid);
+ });
+
+ EXPECT_TRUE(m_triggered.empty());
+ }
+
+ void TearDown()
+ {
+ m_triggered.clear();
+ m_watcher.reset();
+ m_upcoming.reset();
+ m_range_planner.reset();
+
+ super::TearDown();
+ }
+
+ std::vector<Appointment> build_some_appointments()
+ {
+ const auto now = m_state->clock->localtime();
+ auto tomorrow = g_date_time_add_days (now.get(), 1);
+ auto tomorrow_begin = g_date_time_add_full (tomorrow, 0, 0, 0,
+ -g_date_time_get_hour(tomorrow),
+ -g_date_time_get_minute(tomorrow),
+ -g_date_time_get_seconds(tomorrow));
+ auto tomorrow_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1);
+
+ Appointment a1; // an alarm clock appointment
+ a1.color = "red";
+ a1.summary = "Alarm";
+ a1.summary = "http://www.example.com/";
+ a1.uid = "example";
+ a1.has_alarms = true;
+ a1.begin = tomorrow_begin;
+ a1.end = tomorrow_end;
+
+ auto ubermorgen_begin = g_date_time_add_days (tomorrow, 1);
+ auto ubermorgen_end = g_date_time_add_full (tomorrow_begin, 0, 0, 1, 0, 0, -1);
+
+ Appointment a2; // a non-alarm appointment
+ a2.color = "green";
+ a2.summary = "Other Text";
+ a2.summary = "http://www.monkey.com/";
+ a2.uid = "monkey";
+ a2.has_alarms = false;
+ a2.begin = ubermorgen_begin;
+ a2.end = ubermorgen_end;
+
+ // cleanup
+ g_date_time_unref(ubermorgen_end);
+ g_date_time_unref(ubermorgen_begin);
+ g_date_time_unref(tomorrow_end);
+ g_date_time_unref(tomorrow_begin);
+ g_date_time_unref(tomorrow);
+
+ return std::vector<Appointment>({a1, a2});
+ }
+};
+
+/***
+****
+***/
+
+TEST_F(ClockWatcherFixture, AppointmentsChanged)
+{
+ // Add some appointments to the planner.
+ // One of these matches our state's localtime, so that should get triggered.
+ std::vector<Appointment> a = build_some_appointments();
+ a[0].begin = m_state->clock->localtime();
+ m_range_planner->appointments().set(a);
+
+ // Confirm that it got fired
+ EXPECT_EQ(1, m_triggered.size());
+ EXPECT_EQ(a[0].uid, m_triggered[0]);
+}
+
+
+TEST_F(ClockWatcherFixture, TimeChanged)
+{
+ // Add some appointments to the planner.
+ // Neither of these match the state's localtime, so nothing should be triggered.
+ std::vector<Appointment> a = build_some_appointments();
+ m_range_planner->appointments().set(a);
+ EXPECT_TRUE(m_triggered.empty());
+
+ // Set the state's clock to a time that matches one of the appointments().
+ // That appointment should get triggered.
+ m_mock_state->mock_clock->set_localtime(a[1].begin);
+ EXPECT_EQ(1, m_triggered.size());
+ EXPECT_EQ(a[1].uid, m_triggered[0]);
+}
+
+
+TEST_F(ClockWatcherFixture, MoreThanOne)
+{
+ const auto now = m_state->clock->localtime();
+ std::vector<Appointment> a = build_some_appointments();
+ a[0].begin = a[1].begin = now;
+ m_range_planner->appointments().set(a);
+
+ EXPECT_EQ(2, m_triggered.size());
+ EXPECT_EQ(a[0].uid, m_triggered[0]);
+ EXPECT_EQ(a[1].uid, m_triggered[1]);
+}
+
+
+TEST_F(ClockWatcherFixture, NoDuplicates)
+{
+ // Setup: add an appointment that gets triggered.
+ const auto now = m_state->clock->localtime();
+ const std::vector<Appointment> appointments = build_some_appointments();
+ std::vector<Appointment> a;
+ a.push_back(appointments[0]);
+ a[0].begin = now;
+ m_range_planner->appointments().set(a);
+ EXPECT_EQ(1, m_triggered.size());
+ EXPECT_EQ(a[0].uid, m_triggered[0]);
+
+ // Now change the appointment vector by adding one to it.
+ // Confirm that the ClockWatcher doesn't re-trigger a[0]
+ a.push_back(appointments[1]);
+ m_range_planner->appointments().set(a);
+ EXPECT_EQ(1, m_triggered.size());
+ EXPECT_EQ(a[0].uid, m_triggered[0]);
+}
diff --git a/tests/test-clock.cpp b/tests/test-clock.cpp
index 4287e1c..a4924b3 100644
--- a/tests/test-clock.cpp
+++ b/tests/test-clock.cpp
@@ -37,12 +37,12 @@ class ClockFixture: public TestDBusFixture
void emitPrepareForSleep()
{
g_dbus_connection_emit_signal(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr),
- NULL,
+ nullptr,
"/org/freedesktop/login1", // object path
"org.freedesktop.login1.Manager", // interface
"PrepareForSleep", // signal name
g_variant_new("(b)", FALSE),
- NULL);
+ nullptr);
}
};
diff --git a/tests/test-live-actions.cpp b/tests/test-live-actions.cpp
index eab8596..d6ef424 100644
--- a/tests/test-live-actions.cpp
+++ b/tests/test-live-actions.cpp
@@ -284,7 +284,9 @@ TEST_F(LiveActionsFixture, OpenPlannerAt)
{
const auto now = DateTime::NowLocal();
m_actions->open_planner_at(now);
- const std::string expected = now.format("evolution \"calendar:///?startdate=%Y%m%d\"");
+ const auto today_begins = now.add_full(0, 0, 0, -now.hour(), -now.minute(), -now.seconds());
+ const auto gmt = today_begins.to_timezone("UTC");
+ const auto expected = gmt.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\"");
EXPECT_EQ(expected, m_live_actions->last_cmd);
}
@@ -295,7 +297,8 @@ TEST_F(LiveActionsFixture, CalendarState)
const DateTime now (tmp);
g_date_time_unref (tmp);
m_mock_state->mock_clock->set_localtime (now);
- m_state->planner->time.set(now);
+ m_state->calendar_month->month().set(now);
+ //m_state->planner->time.set(now);
///
/// Test the default calendar state.
@@ -315,7 +318,7 @@ TEST_F(LiveActionsFixture, CalendarState)
// calendar-day should be in sync with m_state->calendar_day
v = g_variant_lookup_value (calendar_state, "calendar-day", G_VARIANT_TYPE_INT64);
EXPECT_TRUE (v != nullptr);
- EXPECT_EQ (m_state->planner->time.get().to_unix(), g_variant_get_int64(v));
+ EXPECT_EQ (m_state->calendar_month->month().get().to_unix(), g_variant_get_int64(v));
g_clear_pointer (&v, g_variant_unref);
// show-week-numbers should be false because MockSettings defaults everything to 0
@@ -356,7 +359,7 @@ TEST_F(LiveActionsFixture, CalendarState)
a2.begin = next_begin;
a2.end = next_end;
- m_state->planner->this_month.set(std::vector<Appointment>({a1, a2}));
+ m_state->calendar_month->appointments().set(std::vector<Appointment>({a1, a2}));
///
/// Now test the calendar state again.
diff --git a/tests/test-menus.cpp b/tests/test-menus.cpp
index 73d6036..29d86b3 100644
--- a/tests/test-menus.cpp
+++ b/tests/test-menus.cpp
@@ -252,15 +252,17 @@ private:
void InspectAppointmentMenuItems(GMenuModel* section,
int first_appt_index,
- const std::vector<Appointment>& appointments)
+ const std::vector<Appointment>& appointments,
+ bool can_open_planner)
{
// try adding a few appointments and see if the menu updates itself
- m_state->planner->upcoming.set(appointments);
+ m_state->calendar_upcoming->appointments().set(appointments);
wait_msec(); // wait a moment for the menu to update
//auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU);
//auto section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION);
- EXPECT_EQ(appointments.size()+1, g_menu_model_get_n_items(section));
+ const int n_add_event_buttons = can_open_planner ? 1 : 0;
+ EXPECT_EQ(n_add_event_buttons + appointments.size(), g_menu_model_get_n_items(section));
for (int i=0, n=appointments.size(); i<n; i++)
InspectAppointmentMenuItem(section, first_appt_index+i, appointments[i]);
@@ -269,8 +271,10 @@ private:
//g_clear_object(&submenu);
}
- void InspectDesktopAppointments(GMenuModel* menu_model)
+ void InspectDesktopAppointments(GMenuModel* menu_model, bool can_open_planner)
{
+ const int n_add_event_buttons = can_open_planner ? 1 : 0;
+
// get the Appointments section
auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU);
@@ -281,42 +285,45 @@ private:
EXPECT_EQ(0, g_menu_model_get_n_items(section));
g_clear_object(&section);
- // when "show_events" is true,
- // there should be an "add event" button even if there aren't any appointments
std::vector<Appointment> appointments;
m_state->settings->show_events.set(true);
- m_state->planner->upcoming.set(appointments);
+ m_state->calendar_upcoming->appointments().set(appointments);
wait_msec();
section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION);
- EXPECT_EQ(1, g_menu_model_get_n_items(section));
- gchar* action = nullptr;
- EXPECT_TRUE(g_menu_model_get_item_attribute(section, 0, G_MENU_ATTRIBUTE_ACTION, "s", &action));
- const char* expected_action = "activate-planner";
- EXPECT_EQ(std::string("indicator.")+expected_action, action);
- EXPECT_TRUE(g_action_group_has_action(m_actions->action_group(), expected_action));
- g_free(action);
+ EXPECT_EQ(n_add_event_buttons, g_menu_model_get_n_items(section));
+ if (can_open_planner)
+ {
+ // when "show_events" is true,
+ // there should be an "add event" button even if there aren't any appointments
+ gchar* action = nullptr;
+ EXPECT_TRUE(g_menu_model_get_item_attribute(section, 0, G_MENU_ATTRIBUTE_ACTION, "s", &action));
+ const char* expected_action = "activate-planner";
+ EXPECT_EQ(std::string("indicator.")+expected_action, action);
+ EXPECT_TRUE(g_action_group_has_action(m_actions->action_group(), expected_action));
+ g_free(action);
+ }
g_clear_object(&section);
// try adding a few appointments and see if the menu updates itself
appointments = build_some_appointments();
- m_state->planner->upcoming.set(appointments);
+ m_state->calendar_upcoming->appointments().set(appointments);
wait_msec(); // wait a moment for the menu to update
section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION);
- EXPECT_EQ(3, g_menu_model_get_n_items(section));
- InspectAppointmentMenuItems(section, 0, appointments);
+ EXPECT_EQ(n_add_event_buttons + 2, g_menu_model_get_n_items(section));
+ InspectAppointmentMenuItems(section, 0, appointments, can_open_planner);
g_clear_object(&section);
// cleanup
g_clear_object(&submenu);
}
- void InspectPhoneAppointments(GMenuModel* menu_model)
+ void InspectPhoneAppointments(GMenuModel* menu_model, bool can_open_planner)
{
auto submenu = g_menu_model_get_item_link(menu_model, 0, G_MENU_LINK_SUBMENU);
// clear all the appointments
std::vector<Appointment> appointments;
- m_state->planner->upcoming.set(appointments);
+ m_state->calendar_upcoming->appointments().set(appointments);
wait_msec(); // wait a moment for the menu to update
// check that there's a "clock app" menuitem even when there are no appointments
@@ -332,11 +339,11 @@ private:
// add some appointments and test them
appointments = build_some_appointments();
- m_state->planner->upcoming.set(appointments);
+ m_state->calendar_upcoming->appointments().set(appointments);
wait_msec(); // wait a moment for the menu to update
section = g_menu_model_get_item_link(submenu, Menu::Appointments, G_MENU_LINK_SECTION);
EXPECT_EQ(3, g_menu_model_get_n_items(section));
- InspectAppointmentMenuItems(section, 1, appointments);
+ InspectAppointmentMenuItems(section, 1, appointments, can_open_planner);
g_clear_object(&section);
// cleanup
@@ -347,10 +354,12 @@ protected:
void InspectAppointments(GMenuModel* menu_model, Menu::Profile profile)
{
+ const auto can_open_planner = m_actions->can_open_planner();
+
switch (profile)
{
case Menu::Desktop:
- InspectDesktopAppointments(menu_model);
+ InspectDesktopAppointments(menu_model, can_open_planner);
break;
case Menu::DesktopGreeter:
@@ -358,7 +367,7 @@ protected:
break;
case Menu::Phone:
- InspectPhoneAppointments(menu_model);
+ InspectPhoneAppointments(menu_model, can_open_planner);
break;
case Menu::PhoneGreeter:
@@ -507,6 +516,13 @@ TEST_F(MenuFixture, Appointments)
{
for(auto& menu : m_menus)
InspectAppointments(menu->menu_model(), menu->profile());
+
+ // toggle can_open_planner() and test the desktop again
+ // to confirm that the "Add Event…" menuitem appears iff
+ // there's a calendar available user-agent
+ m_mock_actions->set_can_open_planner (!m_actions->can_open_planner());
+ std::shared_ptr<Menu> menu = m_menu_factory->buildMenu(Menu::Desktop);
+ InspectAppointments(menu->menu_model(), menu->profile());
}
TEST_F(MenuFixture, Locations)
diff --git a/tests/test-planner.cpp b/tests/test-planner.cpp
index b476ee8..8f1590c 100644
--- a/tests/test-planner.cpp
+++ b/tests/test-planner.cpp
@@ -18,19 +18,18 @@
*/
#include "glib-fixture.h"
+#include "timezone-mock.h"
#include <datetime/appointment.h>
#include <datetime/clock-mock.h>
#include <datetime/date-time.h>
#include <datetime/planner.h>
-#include <datetime/planner-eds.h>
+#include <datetime/planner-range.h>
#include <langinfo.h>
#include <locale.h>
-using unity::indicator::datetime::Appointment;
-using unity::indicator::datetime::DateTime;
-using unity::indicator::datetime::PlannerEds;
+using namespace unity::indicator::datetime;
/***
****
@@ -38,22 +37,6 @@ using unity::indicator::datetime::PlannerEds;
typedef GlibFixture PlannerFixture;
-TEST_F(PlannerFixture, EDS)
-{
- PlannerEds planner;
- wait_msec(100);
-
- auto now = g_date_time_new_now_local();
- planner.time.set(DateTime(now));
- wait_msec(2500);
-
- std::vector<Appointment> this_month = planner.this_month.get();
- std::cerr << this_month.size() << " appointments this month" << std::endl;
- for(const auto& a : this_month)
- std::cerr << a.summary << std::endl;
-}
-
-
TEST_F(PlannerFixture, HelloWorld)
{
auto halloween = g_date_time_new_local(2020, 10, 31, 18, 30, 59);
diff --git a/tests/test-settings.cpp b/tests/test-settings.cpp
index 980e7fa..707247d 100644
--- a/tests/test-settings.cpp
+++ b/tests/test-settings.cpp
@@ -167,8 +167,8 @@ TEST_F(SettingsFixture, Locations)
{
const auto key = SETTINGS_LOCATIONS_S;
- const gchar* astrv[] = {"America/Los_Angeles Oakland", "America/Chicago Oklahoma City", "Europe/London London", NULL};
- const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", NULL};
+ const gchar* astrv[] = {"America/Los_Angeles Oakland", "America/Chicago Oklahoma City", "Europe/London London", nullptr};
+ const gchar* bstrv[] = {"America/Denver", "Europe/London London", "Europe/Berlin Berlin", nullptr};
const std::vector<std::string> av = strv_to_vector(astrv);
const std::vector<std::string> bv = strv_to_vector(bstrv);
diff --git a/tests/test-utils.cpp b/tests/test-utils.cpp
index 036c13f..97f07ed 100644
--- a/tests/test-utils.cpp
+++ b/tests/test-utils.cpp
@@ -59,7 +59,7 @@ namespace
const char* location;
const char* expected_name;
} beautify_timezone_test_cases[] = {
- { "America/Chicago", NULL, "Chicago" },
+ { "America/Chicago", nullptr, "Chicago" },
{ "America/Chicago", "America/Chicago", "Chicago" },
{ "America/Chicago", "America/Chigago Chicago", "Chicago" },
{ "America/Chicago", "America/Chicago Oklahoma City", "Oklahoma City" },
diff --git a/include/datetime/planner-eds.h b/tests/timezone-mock.h
index f3abce0..67584cb 100644
--- a/include/datetime/planner-eds.h
+++ b/tests/timezone-mock.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2013 Canonical Ltd.
+ * Copyright 2014 Canonical Ltd.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 3, as published
@@ -17,33 +17,24 @@
* Charles Kerr <charles.kerr@canonical.com>
*/
-#ifndef INDICATOR_DATETIME_PLANNER_EDS_H
-#define INDICATOR_DATETIME_PLANNER_EDS_H
+#ifndef INDICATOR_DATETIME_TIMEZONE_MOCK_H
+#define INDICATOR_DATETIME_TIMEZONE_MOCK_H
-#include <datetime/planner.h>
-
-#include <memory> // unique_ptr
+#include <datetime/timezone.h>
namespace unity {
namespace indicator {
namespace datetime {
-/**
- * \brief Planner which uses EDS as its backend
- */
-class PlannerEds: public Planner
+class MockTimezone: public Timezone
{
public:
- PlannerEds();
- virtual ~PlannerEds();
-
-private:
- class Impl;
- std::unique_ptr<Impl> p;
+ MockTimezone() =default;
+ ~MockTimezone() =default;
};
} // namespace datetime
} // namespace indicator
} // namespace unity
-#endif // INDICATOR_DATETIME_PLANNER_EDS_H
+#endif // INDICATOR_DATETIME_TIMEZONE_MOCK_H