From 835daa7778171256a02d8695776d0b8262b7b637 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 2 Feb 2014 15:28:52 -0600 Subject: copyediting: don't use camelCaseFunctionNames() in timezones-file --- include/datetime/timezone-file.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') 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(); -- cgit v1.2.3 From fcc1ab27cbc36983be51589800d269b055356b2b Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 2 Feb 2014 22:37:22 -0600 Subject: from alarm dev branch: add the alarm watcher and its unit tests --- include/datetime/clock-watcher.h | 72 +++++++++++++++++ src/CMakeLists.txt | 1 + src/clock-watcher.cpp | 71 +++++++++++++++++ tests/CMakeLists.txt | 1 + tests/test-clock-watcher.cpp | 166 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 311 insertions(+) create mode 100644 include/datetime/clock-watcher.h create mode 100644 src/clock-watcher.cpp create mode 100644 tests/test-clock-watcher.cpp (limited to 'include') diff --git a/include/datetime/clock-watcher.h b/include/datetime/clock-watcher.h new file mode 100644 index 0000000..e93b468 --- /dev/null +++ b/include/datetime/clock-watcher.h @@ -0,0 +1,72 @@ +/* + * 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 . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_CLOCK_WATCHER_H +#define INDICATOR_DATETIME_CLOCK_WATCHER_H + +#include +#include + +#include + +#include +#include +#include + +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& alarm_reached() = 0; +}; + + +/** + * \brief A #ClockWatcher implementation + */ +class ClockWatcherImpl: public ClockWatcher +{ +public: + ClockWatcherImpl(const std::shared_ptr& state); + ~ClockWatcherImpl() =default; + core::Signal& alarm_reached(); + +private: + void pulse(); + std::set m_triggered; + std::shared_ptr m_state; + core::Signal m_alarm_reached; +}; + + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_CLOCK_WATCHER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 810e299..12a5b74 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,6 +18,7 @@ add_library (${SERVICE_LIB} STATIC appointment.cpp clock.cpp clock-live.cpp + clock-watcher.cpp date-time.cpp exporter.cpp formatter.cpp diff --git a/src/clock-watcher.cpp b/src/clock-watcher.cpp new file mode 100644 index 0000000..a2e700d --- /dev/null +++ b/src/clock-watcher.cpp @@ -0,0 +1,71 @@ +/* + * 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 . + * + * Authors: + * Charles Kerr + */ + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr& state): + m_state(state) +{ + m_state->planner->upcoming.changed().connect([this](const std::vector&){ + g_debug("ClockWatcher pulse because upcoming appointments changed"); + pulse(); + }); + m_state->clock->minute_changed.connect([this](){ + g_debug("ClockWatcher pulse because clock minute_changed"); + pulse(); + }); + pulse(); +} + +core::Signal& ClockWatcherImpl::alarm_reached() +{ + return m_alarm_reached; +} + +void ClockWatcherImpl::pulse() +{ + const auto now = m_state->clock->localtime(); + + for(const auto& appointment : m_state->planner->upcoming.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/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3dcd151..06e40a7 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) diff --git a/tests/test-clock-watcher.cpp b/tests/test-clock-watcher.cpp new file mode 100644 index 0000000..79b8485 --- /dev/null +++ b/tests/test-clock-watcher.cpp @@ -0,0 +1,166 @@ +/* + * 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 . + * + * Authors: + * Charles Kerr + */ + +#include + +#include + +#include "state-fixture.h" + +using namespace unity::indicator::datetime; + +class ClockWatcherFixture: public StateFixture +{ +private: + + typedef StateFixture super; + +protected: + + std::vector m_triggered; + std::unique_ptr m_watcher; + + void SetUp() + { + super::SetUp(); + + m_watcher.reset(new ClockWatcherImpl(m_state)); + 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(); + + super::TearDown(); + } + + std::vector 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({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 a = build_some_appointments(); + a[0].begin = m_state->clock->localtime(); + m_state->planner->upcoming.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 a = build_some_appointments(); + m_state->planner->upcoming.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 a = build_some_appointments(); + a[0].begin = a[1].begin = now; + m_state->planner->upcoming.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 appointments = build_some_appointments(); + std::vector a; + a.push_back(appointments[0]); + a[0].begin = now; + m_state->planner->upcoming.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_state->planner->upcoming.set(a); + EXPECT_EQ(1, m_triggered.size()); + EXPECT_EQ(a[0].uid, m_triggered[0]); +} -- cgit v1.2.3 From 8365f5e3a35254b7234ac6f6edc43278cea11279 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 3 Feb 2014 00:42:57 -0600 Subject: add header file which should have been bzr add'ed last commit --- include/datetime/snap.h | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 include/datetime/snap.h (limited to 'include') diff --git a/include/datetime/snap.h b/include/datetime/snap.h new file mode 100644 index 0000000..d6f5aad --- /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 . + * + * Authors: + * Charles Kerr + */ + +#ifndef INDICATOR_DATETIME_SNAP_H +#define INDICATOR_DATETIME_SNAP_H + +#include +#include +#include + +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/** + * \brief Pops up Snap Decisions for appointments + */ +class Snap +{ +public: + Snap(const std::shared_ptr&); + virtual ~Snap() =default; + void operator()(const Appointment&); + +private: + PhoneFormatter m_formatter; +}; + +} // namespace datetime +} // namespace indicator +} // namespace unity + +#endif // INDICATOR_DATETIME_SNAP_H -- cgit v1.2.3 From 894c0c625ff1e2f2d031f48f157a3008302cb5a7 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 4 Feb 2014 00:10:17 -0600 Subject: pin the planner's upcoming appointments to the live clock time, rather than the calendar's time, so that they always update correctly in real-time --- include/datetime/planner-eds.h | 5 +- src/main.cpp | 2 +- src/planner-eds.cpp | 117 ++++++++++++++++++++++++----------------- tests/test-planner.cpp | 14 ++--- 4 files changed, 82 insertions(+), 56 deletions(-) (limited to 'include') diff --git a/include/datetime/planner-eds.h b/include/datetime/planner-eds.h index f3abce0..a99f611 100644 --- a/include/datetime/planner-eds.h +++ b/include/datetime/planner-eds.h @@ -20,9 +20,10 @@ #ifndef INDICATOR_DATETIME_PLANNER_EDS_H #define INDICATOR_DATETIME_PLANNER_EDS_H +#include #include -#include // unique_ptr +#include // shared_ptr, unique_ptr namespace unity { namespace indicator { @@ -34,7 +35,7 @@ namespace datetime { class PlannerEds: public Planner { public: - PlannerEds(); + PlannerEds(const std::shared_ptr& clock); virtual ~PlannerEds(); private: diff --git a/src/main.cpp b/src/main.cpp index 3c17923..87bfed1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -64,7 +64,7 @@ main(int /*argc*/, char** /*argv*/) state->settings = live_settings; state->clock = live_clock; state->locations.reset(new SettingsLocations(live_settings, live_timezones)); - state->planner.reset(new PlannerEds); + state->planner.reset(new PlannerEds(live_clock)); state->planner->time = live_clock->localtime(); std::shared_ptr actions(new LiveActions(state)); MenuFactory factory(actions, state); diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp index 9048f52..7d9416c 100644 --- a/src/planner-eds.cpp +++ b/src/planner-eds.cpp @@ -41,18 +41,24 @@ class PlannerEds::Impl { public: - Impl(PlannerEds& owner): + Impl(PlannerEds& owner, const std::shared_ptr& clock): m_owner(owner), + m_clock(clock), m_cancellable(g_cancellable_new()) { e_source_registry_new(m_cancellable, on_source_registry_ready, this); + m_clock->minute_changed.connect([this](){ + g_debug("rebuilding upcoming because the clock's minute_changed"); + rebuild_soon(UPCOMING); + }); + m_owner.time.changed().connect([this](const DateTime& dt) { g_debug("planner's datetime property changed to %s; calling rebuild_soon()", dt.format("%F %T").c_str()); - rebuild_soon(); + rebuild_soon(MONTH); }); - rebuild_soon(); + rebuild_soon(ALL); } ~Impl() @@ -164,7 +170,7 @@ private: self); g_debug("client connected; calling rebuild_soon()"); - self->rebuild_soon(); + self->rebuild_soon(ALL); } } @@ -185,7 +191,7 @@ private: 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 rebuild_soon()"); - self->rebuild_soon(); + self->rebuild_soon(ALL); } else if(error != nullptr) { @@ -199,17 +205,17 @@ private: static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) { g_debug("%s", G_STRFUNC); - static_cast(gself)->rebuild_soon(); + static_cast(gself)->rebuild_soon(ALL); } static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) { g_debug("%s", G_STRFUNC); - static_cast(gself)->rebuild_soon(); + static_cast(gself)->rebuild_soon(ALL); } static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) { g_debug("%s", G_STRFUNC); - static_cast(gself)->rebuild_soon(); + static_cast(gself)->rebuild_soon(ALL); } static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) @@ -228,7 +234,7 @@ private: g_warn_if_fail(n_disconnected == 3); g_object_unref(view); m_views.erase(vit); - rebuild_soon(); + rebuild_soon(ALL); } // if an ECalClient is associated with this source, remove it @@ -238,7 +244,7 @@ private: auto& client = cit->second; g_object_unref(client); m_clients.erase(cit); - rebuild_soon(); + rebuild_soon(ALL); } } @@ -255,14 +261,14 @@ private: { g_object_unref(*sit); m_sources.erase(sit); - rebuild_soon(); + rebuild_soon(ALL); } } static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself) { g_debug("source changed; calling rebuild_soon()"); - static_cast(gself)->rebuild_soon(); + static_cast(gself)->rebuild_soon(ALL); } private: @@ -286,9 +292,11 @@ private: task(task_in), client(client_in), color(color_in) {} }; - void rebuild_soon() + void rebuild_soon(int rebuild_flags) { - const static guint ARBITRARY_INTERVAL_SECS = 2; + static const guint ARBITRARY_INTERVAL_SECS = 2; + + m_rebuild_flags |= rebuild_flags; if (m_rebuild_tag == 0) m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuild_now_static, this); @@ -297,44 +305,56 @@ private: static gboolean rebuild_now_static(gpointer gself) { auto self = static_cast(gself); + const auto flags = self->m_rebuild_flags; self->m_rebuild_tag = 0; - self->rebuild_now(); + self->m_rebuild_flags = 0; + self->rebuild_now(flags); return G_SOURCE_REMOVE; } - void rebuild_now() + void rebuild_now(int rebuild_flags) { - 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) - { - get_appointments(begin, end, [this](const std::vector& appointments) { - g_debug("got %d appointments in this calendar month", (int)appointments.size()); - m_owner.this_month.set(appointments); - }); - } - g_clear_pointer(&begin, g_date_time_unref); - g_clear_pointer(&end, g_date_time_unref); + if (rebuild_flags & UPCOMING) + rebuild_upcoming(); - // get the upcoming appointments - begin = g_date_time_ref(calendar_date); - end = g_date_time_add_months(begin, 1); - if (begin && end) - { - get_appointments(begin, end, [this](const std::vector& 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); + if (rebuild_flags & MONTH) + rebuild_month(); + } + + void rebuild_month() + { + const auto ref = m_owner.time.get().get(); + auto month_begin = g_date_time_add_full(ref, + 0, // subtract no years + 0, // subtract no months + -(g_date_time_get_day_of_month(ref)-1), + -g_date_time_get_hour(ref), + -g_date_time_get_minute(ref), + -g_date_time_get_seconds(ref)); + auto month_end = g_date_time_add_full(month_begin, 0, 1, 0, 0, 0, -0.1); + + get_appointments(month_begin, month_end, [this](const std::vector& appointments) { + g_debug("got %d appointments in this calendar month", (int)appointments.size()); + m_owner.this_month.set(appointments); + }); + + g_date_time_unref(month_end); + g_date_time_unref(month_begin); + } + + void rebuild_upcoming() + { + const auto ref = m_clock->localtime(); + const auto begin = g_date_time_add_minutes(ref.get(),-10); + const auto end = g_date_time_add_months(begin,1); + + get_appointments(begin, end, [this](const std::vector& appointments) { + g_debug("got %d upcoming appointments", (int)appointments.size()); + m_owner.upcoming.set(appointments); + }); + + g_date_time_unref(end); + g_date_time_unref(begin); } void get_appointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func) @@ -496,15 +516,18 @@ private: } PlannerEds& m_owner; + std::shared_ptr m_clock; std::set m_sources; std::map m_clients; std::map m_views; GCancellable* m_cancellable = nullptr; ESourceRegistry* m_source_registry = nullptr; guint m_rebuild_tag = 0; + guint m_rebuild_flags = 0; + enum { UPCOMING=(1<<0), MONTH=(1<<1), ALL=UPCOMING|MONTH }; }; -PlannerEds::PlannerEds(): p(new Impl(*this)) {} +PlannerEds::PlannerEds(const std::shared_ptr& clock): p(new Impl(*this, clock)) {} PlannerEds::~PlannerEds() =default; diff --git a/tests/test-planner.cpp b/tests/test-planner.cpp index b476ee8..1923ba1 100644 --- a/tests/test-planner.cpp +++ b/tests/test-planner.cpp @@ -28,9 +28,7 @@ #include #include -using unity::indicator::datetime::Appointment; -using unity::indicator::datetime::DateTime; -using unity::indicator::datetime::PlannerEds; +using namespace unity::indicator::datetime; /*** **** @@ -40,11 +38,15 @@ typedef GlibFixture PlannerFixture; TEST_F(PlannerFixture, EDS) { - PlannerEds planner; + auto tmp = g_date_time_new_now_local(); + const auto now = DateTime(tmp); + g_date_time_unref(tmp); + + std::shared_ptr clock(new MockClock(now)); + PlannerEds planner(clock); wait_msec(100); - auto now = g_date_time_new_now_local(); - planner.time.set(DateTime(now)); + planner.time.set(now); wait_msec(2500); std::vector this_month = planner.this_month.get(); -- cgit v1.2.3 From 61accb9ce497e1f1cbe8038ac495d66d6a4505ff Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 4 Feb 2014 09:57:51 -0600 Subject: use the appointment's beginning time in the title of the alarm Snap Decision --- include/datetime/snap.h | 9 ++------- src/main.cpp | 2 +- src/snap.cpp | 15 ++++++++++----- 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'include') diff --git a/include/datetime/snap.h b/include/datetime/snap.h index d6f5aad..584d895 100644 --- a/include/datetime/snap.h +++ b/include/datetime/snap.h @@ -21,8 +21,6 @@ #define INDICATOR_DATETIME_SNAP_H #include -#include -#include #include @@ -36,12 +34,9 @@ namespace datetime { class Snap { public: - Snap(const std::shared_ptr&); - virtual ~Snap() =default; + Snap(); + virtual ~Snap(); void operator()(const Appointment&); - -private: - PhoneFormatter m_formatter; }; } // namespace datetime diff --git a/src/main.cpp b/src/main.cpp index 87bfed1..71a1ce5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -71,7 +71,7 @@ main(int /*argc*/, char** /*argv*/) // snap decisions ClockWatcherImpl clock_watcher(state); - Snap snap(state->clock); + Snap snap; clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){ snap(appt); }); diff --git a/src/snap.cpp b/src/snap.cpp index 7acac9c..a290f99 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -20,6 +20,7 @@ #include #include #include +#include // generate_full_format_string_at_time() #include @@ -72,8 +73,11 @@ void on_snap_decided(NotifyNotification * /*notification*/, **** ***/ -Snap::Snap(const std::shared_ptr& clock): - m_formatter(clock) +Snap::Snap() +{ +} + +Snap::~Snap() { } @@ -82,10 +86,10 @@ void Snap::operator()(const Appointment& appointment) if (!appointment.has_alarms) return; - const auto timestr = m_formatter.header.get(); + auto timestr = generate_full_format_string_at_time (appointment.begin.get(), nullptr, nullptr); + auto title = g_strdup_printf(_("Alarm %s"), timestr); const auto body = appointment.summary; - gchar* title = g_strdup_printf(_("Alarm %s"), timestr.c_str()); - const gchar* icon_name = "clock"; + const gchar* icon_name = "alarm-clock"; g_debug("creating a snap decision with title '%s', body '%s', icon '%s'", title, body.c_str(), icon_name); auto nn = notify_notification_new(title, body.c_str(), icon_name); @@ -104,6 +108,7 @@ void Snap::operator()(const Appointment& appointment) } g_free(title); + g_free(timestr); } /*** -- cgit v1.2.3 From 61581201f13509fbce9eb05fc90a5da17307c6a3 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 4 Feb 2014 13:00:22 -0600 Subject: Add audio notitication when the alarm is triggered. Add a manual test to tests/ to trigger a snap decision. --- CMakeLists.txt | 1 + debian/control | 1 + include/datetime/snap.h | 7 ++- src/main.cpp | 15 ++--- src/snap.cpp | 146 +++++++++++++++++++++++++++++++++++---------- tests/CMakeLists.txt | 4 ++ tests/manual-test-snap.cpp | 63 +++++++++++++++++++ 7 files changed, 194 insertions(+), 43 deletions(-) create mode 100644 tests/manual-test-snap.cpp (limited to 'include') diff --git a/CMakeLists.txt b/CMakeLists.txt index ab8cca4..3e6a810 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ 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) diff --git a/debian/control b/debian/control index 54d265d..76fc5a1 100644 --- a/debian/control +++ b/debian/control @@ -16,6 +16,7 @@ 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), diff --git a/include/datetime/snap.h b/include/datetime/snap.h index 584d895..a493772 100644 --- a/include/datetime/snap.h +++ b/include/datetime/snap.h @@ -23,6 +23,7 @@ #include #include +#include namespace unity { namespace indicator { @@ -36,7 +37,11 @@ class Snap public: Snap(); virtual ~Snap(); - void operator()(const Appointment&); + + typedef std::function appointment_func; + void operator()(const Appointment& appointment, + appointment_func show, + appointment_func dismiss); }; } // namespace datetime diff --git a/src/main.cpp b/src/main.cpp index 71a1ce5..7e09fda 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,8 +17,6 @@ * with this program. If not, see . */ - - #include #include #include @@ -33,10 +31,11 @@ #include // bindtextdomain() #include -#include + +#include #include -#include // exit() +#include // exit() using namespace unity::indicator::datetime; @@ -52,10 +51,6 @@ 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"); - // build the state, actions, and menufactory std::shared_ptr state(new State); std::shared_ptr live_settings(new LiveSettings); @@ -73,7 +68,9 @@ main(int /*argc*/, char** /*argv*/) ClockWatcherImpl clock_watcher(state); Snap snap; clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){ - snap(appt); + snap(appt, + [](const Appointment& a){url_dispatch_send(a.url.c_str(), nullptr, nullptr);}, + [](const Appointment&){}); }); // create the menus diff --git a/src/snap.cpp b/src/snap.cpp index a290f99..5f46dc7 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -22,13 +22,14 @@ #include #include // generate_full_format_string_at_time() -#include - +#include #include #include #include +#define ALARM_SOUND_FILENAME "/usr/share/sounds/ubuntu/stereo/phone-incoming-call.ogg" + namespace unity { namespace indicator { namespace datetime { @@ -40,77 +41,156 @@ namespace datetime { namespace { -void dispatch_alarm_url(const Appointment& appointment) +/** +*** libcanberra -- play sounds +**/ + +ca_context *c_context = nullptr; + +ca_context* get_ca_context() { - g_return_if_fail(!appointment.has_alarms); + 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; + } + } - const auto fmt = appointment.begin.format("%F %T"); - g_debug("dispatching url \"%s\" for appointment \"%s\", which begins at %s", - appointment.url.c_str(), - appointment.summary.c_str(), - fmt.c_str()); + return c_context; +} + +void play_soundfile(const char* filename) +{ + auto context = get_ca_context(); + g_return_if_fail(context != nullptr); - url_dispatch_send(appointment.url.c_str(), nullptr, nullptr); + const auto rv = ca_context_play(context, 0, CA_PROP_MEDIA_FILENAME, filename, NULL); + if (rv != CA_SUCCESS) + g_warning("Failed to play file '%s': %s\n", filename, ca_strerror(rv)); } -void on_snap_decided(NotifyNotification * /*notification*/, - char * action, - gpointer gurl) +void play_alarm_sound() { - g_debug("%s: %s", G_STRFUNC, action); + play_soundfile(ALARM_SOUND_FILENAME); +} + +/** +*** libnotify -- snap decisions +**/ - if (!g_strcmp0(action, "show")) +void first_time_init() +{ + static bool inited = false; + + if (G_UNLIKELY(!inited)) { - const auto url = static_cast(gurl); - g_debug("dispatching url '%s'", url); - url_dispatch_send(url, nullptr, nullptr); + inited = true; + + if(!notify_init("indicator-datetime-service")) + g_critical("libnotify initialization failed"); } } -} // unnamed namespace +struct SnapData +{ + Snap::appointment_func show; + Snap::appointment_func dismiss; + Appointment appointment; +}; -/*** -**** -***/ +void on_snap_show(NotifyNotification*, gchar* /*action*/, gpointer gdata) +{ + auto data = static_cast(gdata); + data->show(data->appointment); +} -Snap::Snap() +void on_snap_dismiss(NotifyNotification*, gchar* /*action*/, gpointer gdata) { + auto data = static_cast(gdata); + data->dismiss(data->appointment); } -Snap::~Snap() +void snap_data_destroy_notify(gpointer gdata) { + delete static_cast(gdata); } -void Snap::operator()(const Appointment& appointment) +void show_snap_decision(SnapData* data) { - if (!appointment.has_alarms) - return; + const Appointment& appointment = data->appointment; - auto timestr = generate_full_format_string_at_time (appointment.begin.get(), nullptr, nullptr); + auto timestr = generate_full_format_string_at_time(appointment.begin.get(), nullptr, nullptr); auto title = g_strdup_printf(_("Alarm %s"), timestr); const auto body = appointment.summary; const gchar* icon_name = "alarm-clock"; - g_debug("creating a snap decision with title '%s', body '%s', icon '%s'", title, body.c_str(), icon_name); auto nn = notify_notification_new(title, body.c_str(), icon_name); 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_decided, g_strdup(appointment.url.c_str()), g_free); - notify_notification_add_action(nn, "dismiss", _("Dismiss"), on_snap_decided, nullptr, nullptr); + 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_object_set_data_full(G_OBJECT(nn), "snap-data", data, snap_data_destroy_notify); GError * error = nullptr; notify_notification_show(nn, &error); if (error != NULL) { - g_warning("Unable to show alarm '%s' popup: %s", body.c_str(), error->message); + g_warning("Unable to show snap decision for '%s': %s", body.c_str(), error->message); g_error_free(error); - dispatch_alarm_url(appointment); + data->show(data->appointment); } g_free(title); g_free(timestr); } +/** +*** +**/ + +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; + + play_alarm_sound(); + show_snap_decision(data); +} + +} // unnamed namespace + + +/*** +**** +***/ + +Snap::Snap() +{ + first_time_init(); +} + +Snap::~Snap() +{ + 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); +} + /*** **** ***/ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 06e40a7..7d590c9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -53,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/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 + * + * 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 . + */ + +#include +#include + +#include + +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; +} -- cgit v1.2.3 From 613cbb1d468fe99767a5541956266191511ec9ef Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 5 Feb 2014 16:26:34 -0600 Subject: remove alarms from the menu once they've been shown in a snap decision. --- include/datetime/planner-eds.h | 2 ++ src/main.cpp | 8 +++++--- src/planner-eds.cpp | 22 +++++++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/datetime/planner-eds.h b/include/datetime/planner-eds.h index a99f611..4110ba5 100644 --- a/include/datetime/planner-eds.h +++ b/include/datetime/planner-eds.h @@ -20,6 +20,7 @@ #ifndef INDICATOR_DATETIME_PLANNER_EDS_H #define INDICATOR_DATETIME_PLANNER_EDS_H +#include #include #include @@ -37,6 +38,7 @@ class PlannerEds: public Planner public: PlannerEds(const std::shared_ptr& clock); virtual ~PlannerEds(); + void block_appointment(const Appointment& appointment); private: class Impl; diff --git a/src/main.cpp b/src/main.cpp index daf2a22..5f5ee3c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,15 +59,17 @@ main(int /*argc*/, char** /*argv*/) state->settings = live_settings; state->clock = live_clock; state->locations.reset(new SettingsLocations(live_settings, live_timezones)); - state->planner.reset(new PlannerEds(live_clock)); - state->planner->time = live_clock->localtime(); + std::shared_ptr eds_planner(new PlannerEds(live_clock)); + eds_planner->time = live_clock->localtime(); + state->planner = eds_planner; std::shared_ptr actions(new LiveActions(state)); MenuFactory factory(actions, state); // snap decisions ClockWatcherImpl clock_watcher(state); Snap snap; - clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){ + clock_watcher.alarm_reached().connect([&snap,&eds_planner](const Appointment& appt){ + eds_planner->block_appointment(appt); // when we show a snap decision, take it out of the menu auto snap_show = [](const Appointment& a){url_dispatch_send(a.url.c_str(), nullptr, nullptr);}; auto snap_dismiss = [](const Appointment&){}; snap(appt, snap_show, snap_dismiss); diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp index 7d9416c..e9452f0 100644 --- a/src/planner-eds.cpp +++ b/src/planner-eds.cpp @@ -77,6 +77,12 @@ public: g_clear_object(&m_source_registry); } + void block_appointment(const Appointment& appointment) + { + m_blocked.insert(appointment.uid); + rebuild_soon(UPCOMING); + } + private: static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself) @@ -348,9 +354,13 @@ private: const auto begin = g_date_time_add_minutes(ref.get(),-10); const auto end = g_date_time_add_months(begin,1); - get_appointments(begin, end, [this](const std::vector& appointments) { - g_debug("got %d upcoming appointments", (int)appointments.size()); - m_owner.upcoming.set(appointments); + get_appointments(begin, end, [this](const std::vector& all) { + std::vector unblocked; + for(const auto& a : all) + if (m_blocked.count(a.uid) == 0) + unblocked.push_back(a); + g_debug("got %d upcoming appointments, %d of which are unblocked", (int)all.size(), (int)unblocked.size()); + m_owner.upcoming.set(unblocked); }); g_date_time_unref(end); @@ -520,6 +530,7 @@ private: std::set m_sources; std::map m_clients; std::map m_views; + std::set m_blocked; GCancellable* m_cancellable = nullptr; ESourceRegistry* m_source_registry = nullptr; guint m_rebuild_tag = 0; @@ -531,6 +542,11 @@ PlannerEds::PlannerEds(const std::shared_ptr& clock): p(new Impl(*this, c PlannerEds::~PlannerEds() =default; +void PlannerEds::block_appointment(const Appointment& appointment) +{ + p->block_appointment(appointment); +} + } // namespace datetime } // namespace indicator } // namespace unity -- cgit v1.2.3 From 4f27e42b0a517fac386042c67efc721463115bb9 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 5 Feb 2014 16:52:57 -0600 Subject: revert r400; we can't block alarms by UID because that would hide recurring alarms --- include/datetime/planner-eds.h | 2 -- src/main.cpp | 8 +++----- src/planner-eds.cpp | 22 +++------------------- 3 files changed, 6 insertions(+), 26 deletions(-) (limited to 'include') diff --git a/include/datetime/planner-eds.h b/include/datetime/planner-eds.h index 4110ba5..a99f611 100644 --- a/include/datetime/planner-eds.h +++ b/include/datetime/planner-eds.h @@ -20,7 +20,6 @@ #ifndef INDICATOR_DATETIME_PLANNER_EDS_H #define INDICATOR_DATETIME_PLANNER_EDS_H -#include #include #include @@ -38,7 +37,6 @@ class PlannerEds: public Planner public: PlannerEds(const std::shared_ptr& clock); virtual ~PlannerEds(); - void block_appointment(const Appointment& appointment); private: class Impl; diff --git a/src/main.cpp b/src/main.cpp index 2be727f..31d9db6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,17 +59,15 @@ main(int /*argc*/, char** /*argv*/) state->settings = live_settings; state->clock = live_clock; state->locations.reset(new SettingsLocations(live_settings, live_timezones)); - std::shared_ptr eds_planner(new PlannerEds(live_clock)); - eds_planner->time = live_clock->localtime(); - state->planner = eds_planner; + state->planner.reset(new PlannerEds(live_clock)); + state->planner->time = live_clock->localtime(); std::shared_ptr actions(new LiveActions(state)); MenuFactory factory(actions, state); // snap decisions ClockWatcherImpl clock_watcher(state); Snap snap; - clock_watcher.alarm_reached().connect([&snap,&eds_planner](const Appointment& appt){ - eds_planner->block_appointment(appt); // when we show a snap decision, take it out of the menu + clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){ auto snap_show = [](const Appointment& a){ const char* url; if(!a.url.empty()) diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp index e9452f0..7d9416c 100644 --- a/src/planner-eds.cpp +++ b/src/planner-eds.cpp @@ -77,12 +77,6 @@ public: g_clear_object(&m_source_registry); } - void block_appointment(const Appointment& appointment) - { - m_blocked.insert(appointment.uid); - rebuild_soon(UPCOMING); - } - private: static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself) @@ -354,13 +348,9 @@ private: const auto begin = g_date_time_add_minutes(ref.get(),-10); const auto end = g_date_time_add_months(begin,1); - get_appointments(begin, end, [this](const std::vector& all) { - std::vector unblocked; - for(const auto& a : all) - if (m_blocked.count(a.uid) == 0) - unblocked.push_back(a); - g_debug("got %d upcoming appointments, %d of which are unblocked", (int)all.size(), (int)unblocked.size()); - m_owner.upcoming.set(unblocked); + get_appointments(begin, end, [this](const std::vector& appointments) { + g_debug("got %d upcoming appointments", (int)appointments.size()); + m_owner.upcoming.set(appointments); }); g_date_time_unref(end); @@ -530,7 +520,6 @@ private: std::set m_sources; std::map m_clients; std::map m_views; - std::set m_blocked; GCancellable* m_cancellable = nullptr; ESourceRegistry* m_source_registry = nullptr; guint m_rebuild_tag = 0; @@ -542,11 +531,6 @@ PlannerEds::PlannerEds(const std::shared_ptr& clock): p(new Impl(*this, c PlannerEds::~PlannerEds() =default; -void PlannerEds::block_appointment(const Appointment& appointment) -{ - p->block_appointment(appointment); -} - } // namespace datetime } // namespace indicator } // namespace unity -- cgit v1.2.3 From bf68bbe34cf8a8769345d1f36cc09cdc5d0ae07c Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 5 Feb 2014 18:09:49 -0600 Subject: another pass at removing alarms from the menu once they're no longer upcoming. This version fixes the header's icon as well. --- include/datetime/date-time.h | 3 +++ src/date-time.cpp | 18 ++++++++++++++++++ src/menu.cpp | 37 ++++++++++++++++++++++++++++--------- 3 files changed, 49 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h index 2ad7856..b054a1f 100644 --- a/include/datetime/date-time.h +++ b/include/datetime/date-time.h @@ -41,6 +41,7 @@ public: 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; @@ -48,9 +49,11 @@ public: std::string format(const std::string& fmt) const; int day_of_month() 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/src/date-time.cpp b/src/date-time.cpp index a634c5e..e6d99cd 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -69,6 +69,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); @@ -88,6 +96,11 @@ int DateTime::day_of_month() const return g_date_time_get_day_of_month(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 +125,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/menu.cpp b/src/menu.cpp index 50a0087..b2562db 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -25,6 +25,8 @@ #include #include +#include + namespace unity { namespace indicator { namespace datetime { @@ -103,13 +105,15 @@ protected: update_section(Appointments); // showing events got toggled }); m_state->planner->upcoming.changed().connect([this](const std::vector&){ - update_header(); // show an 'alarm' icon if there are upcoming alarms - update_section(Appointments); // "upcoming" is the list of Appointments we show + 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&) { update_section(Locations); // "locations" is the list of Locations we show }); @@ -132,6 +136,24 @@ protected: g_action_group_change_action_state(action_group, action_name.c_str(), state); } + void update_upcoming() + { + const auto now = m_state->clock->localtime(); + const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds()); + + std::vector upcoming; + for(const auto& a : m_state->planner->upcoming.get()) + if (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 m_state; std::shared_ptr m_actions; std::shared_ptr m_formatter; @@ -149,6 +171,8 @@ protected: return m_serialized_alarm_icon; } + std::vector m_upcoming; + private: GVariant* get_serialized_calendar_icon() @@ -239,10 +263,9 @@ private: { int n = 0; const int MAX_APPTS = 5; - const auto now = m_state->clock->localtime(); std::set added; - for (const auto& appt : m_state->planner->upcoming.get()) + for (const auto& appt : m_upcoming) { // don't show too many if (n++ >= MAX_APPTS) @@ -252,10 +275,6 @@ private: if (added.count(appt.uid)) continue; - // don't show appointments that have already started - if ((appt.beginplanner->upcoming.get()) + for(const auto& appointment : m_upcoming) if((has_alarms = appointment.has_alarms)) break; -- cgit v1.2.3