From 5828562c08f8bd01826da4db12f7c4be3dc574d0 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Fri, 31 Jan 2014 12:04:47 -0600 Subject: use realpath() to dereference symbolic links when watching /etc/timezone. h/t pitti --- src/timezone-file.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/timezone-file.cpp b/src/timezone-file.cpp index 76737b4..df4ee24 100644 --- a/src/timezone-file.cpp +++ b/src/timezone-file.cpp @@ -19,6 +19,9 @@ #include +#include +#include + namespace unity { namespace indicator { namespace datetime { @@ -53,9 +56,19 @@ FileTimezone::setFilename(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); @@ -67,7 +80,7 @@ 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()); + g_debug("%s Monitoring timezone file '%s'", G_STRLOC, m_filename.c_str()); } reload(); -- cgit v1.2.3 From 3e119374a27c333e2e85f2887668eb309edbe183 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 2 Feb 2014 15:28:10 -0600 Subject: add an ECalClientView for each ECalClient to listen for changes to its components. --- src/planner-eds.cpp | 169 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 122 insertions(+), 47 deletions(-) (limited to 'src') diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp index cb42d6e..22e834f 100644 --- a/src/planner-eds.cpp +++ b/src/planner-eds.cpp @@ -26,6 +26,9 @@ #include #include +#include +#include + namespace unity { namespace indicator { namespace datetime { @@ -34,9 +37,6 @@ namespace datetime { ***** ****/ -G_DEFINE_QUARK("source-client", source_client) - - class PlannerEds::Impl { public: @@ -48,11 +48,11 @@ public: 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(); + g_debug("planner's datetime property changed to %s; calling rebuild_soon()", dt.format("%F %T").c_str()); + rebuild_soon(); }); - rebuildSoon(); + rebuild_soon(); } ~Impl() @@ -60,6 +60,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); @@ -102,7 +105,7 @@ private: static void on_source_added(ESourceRegistry* registry, ESource* source, gpointer gself) { - auto self = static_cast(gself); + auto self = static_cast(gself); self->m_sources.insert(E_SOURCE(g_object_ref(source))); @@ -112,8 +115,9 @@ private: static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) { - auto self = static_cast(gself); + auto self = static_cast(gself); + g_debug("connecting a client to source %s", e_source_get_uid(source)); e_cal_client_connect(source, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, self->m_cancellable, @@ -134,45 +138,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(gself)->rebuildSoon(); + // add the client to our collection + auto self = static_cast(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 rebuild_soon()"); + self->rebuild_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(gself); + self->m_views[e_client_get_source(E_CLIENT(client))] = view;//G_CAL_CLIENT(client)] = view;//.insert(view); - g_debug("source disabled; calling rebuildSoon()"); - static_cast(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 rebuild_soon()"); + self->rebuild_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_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) + { + g_debug("%s", G_STRFUNC); + static_cast(gself)->rebuild_soon(); + } + static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) + { + g_debug("%s", G_STRFUNC); + static_cast(gself)->rebuild_soon(); + } + static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) + { + g_debug("%s", G_STRFUNC); + static_cast(gself)->rebuild_soon(); } - static void on_source_removed(ESourceRegistry* registry, ESource* source, gpointer gself) + static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + static_cast(gself)->disable_source(source); + } + void disable_source(ESource* source) { - auto self = static_cast(gself); + // 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); + rebuild_soon(); + } - on_source_disabled(registry, source, gself); + // 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); + rebuild_soon(); + } + } - self->m_sources.erase(source); - g_object_unref(source); + static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + static_cast(gself)->remove_source(source); + } + void remove_source(ESource* source) + { + disable_source(source); + + auto sit = m_sources.find(source); + if (sit != m_sources.end()) + { + g_object_unref(*sit); + m_sources.erase(sit); + rebuild_soon(); + } } static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself) { - g_debug("source changed; calling rebuildSoon()"); - static_cast(gself)->rebuildSoon(); + g_debug("source changed; calling rebuild_soon()"); + static_cast(gself)->rebuild_soon(); } private: @@ -196,23 +273,23 @@ private: task(task_in), client(client_in), color(color_in) {} }; - void rebuildSoon() + void rebuild_soon() { const static guint ARBITRARY_INTERVAL_SECS = 2; if (m_rebuild_tag == 0) - m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuildNowStatic, this); + m_rebuild_tag = g_timeout_add_seconds(ARBITRARY_INTERVAL_SECS, rebuild_now_static, this); } - static gboolean rebuildNowStatic(gpointer gself) + static gboolean rebuild_now_static(gpointer gself) { auto self = static_cast(gself); self->m_rebuild_tag = 0; - self->rebuildNow(); + self->rebuild_now(); return G_SOURCE_REMOVE; } - void rebuildNow() + void rebuild_now() { const auto calendar_date = m_owner.time.get().get(); GDateTime* begin; @@ -225,7 +302,7 @@ private: 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) { - getAppointments(begin, end, [this](const std::vector& appointments) { + 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); }); @@ -238,7 +315,7 @@ private: end = g_date_time_add_months(begin, 1); if (begin && end) { - getAppointments(begin, end, [this](const std::vector& appointments) { + get_appointments(begin, end, [this](const std::vector& appointments) { g_debug("got %d upcoming appointments", (int)appointments.size()); m_owner.upcoming.set(appointments); }); @@ -247,7 +324,7 @@ private: g_clear_pointer(&end, g_date_time_unref); } - void getAppointments(GDateTime* begin_dt, GDateTime* end_dt, appointment_func func) + void get_appointments(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); @@ -286,16 +363,14 @@ private: delete task; }); - for (auto& source : m_sources) + for (auto& kv : m_clients) { - auto client = E_CAL_CLIENT(g_object_get_qdata(G_OBJECT(source), source_client_quark())); - if (client == nullptr) - continue; - + 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); @@ -407,12 +482,12 @@ private: delete subtask; } -private: - PlannerEds& m_owner; std::set m_sources; - GCancellable * m_cancellable = nullptr; - ESourceRegistry * m_source_registry = nullptr; + std::map m_clients; + std::map m_views; + GCancellable* m_cancellable = nullptr; + ESourceRegistry* m_source_registry = nullptr; guint m_rebuild_tag = 0; }; -- cgit v1.2.3 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 --- src/timezone-file.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/timezone-file.cpp b/src/timezone-file.cpp index df4ee24..c99897a 100644 --- a/src/timezone-file.cpp +++ b/src/timezone-file.cpp @@ -32,7 +32,7 @@ FileTimezone::FileTimezone() FileTimezone::FileTimezone(const std::string& filename) { - setFilename(filename); + set_filename(filename); } FileTimezone::~FileTimezone() @@ -52,7 +52,7 @@ FileTimezone::clear() } void -FileTimezone::setFilename(const std::string& filename) +FileTimezone::set_filename(const std::string& filename) { clear(); @@ -79,7 +79,7 @@ FileTimezone::setFilename(const std::string& filename) } else { - m_monitor_handler_id = g_signal_connect_swapped(m_monitor, "changed", G_CALLBACK(onFileChanged), this); + 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()); } @@ -87,7 +87,7 @@ FileTimezone::setFilename(const std::string& filename) } void -FileTimezone::onFileChanged(gpointer gself) +FileTimezone::on_file_changed(gpointer gself) { static_cast(gself)->reload(); } -- cgit v1.2.3 From 33425752728cef43c566a2ace5a54a3a31f6b36f Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 2 Feb 2014 15:29:29 -0600 Subject: copyediting: use 'nullptr' instead of 'NULL' in c++ source --- src/actions-live.cpp | 2 +- src/menu.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/actions-live.cpp b/src/actions-live.cpp index c0fd8ff..32d41ad 100644 --- a/src/actions-live.cpp +++ b/src/actions-live.cpp @@ -160,7 +160,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/menu.cpp b/src/menu.cpp index 91f7dd2..b3dcc53 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -270,7 +270,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", -- cgit v1.2.3 From a03811363619c178fd5156fa94eb5d5a4897afa5 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 2 Feb 2014 19:23:08 -0600 Subject: aha! ubntu-ui-toolkit stores its alarms in E_SOURCE_EXTENSION_TASK_LIST instead of E_SOURCE_EXTENSION_CALENDAR. Let's handle both in indicator-datetime. --- src/planner-eds.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp index 22e834f..79b340a 100644 --- a/src/planner-eds.cpp +++ b/src/planner-eds.cpp @@ -86,23 +86,28 @@ private: } else { - auto self = static_cast(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(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(gself); -- 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 --- src/CMakeLists.txt | 1 + src/clock-watcher.cpp | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/clock-watcher.cpp (limited to 'src') 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 -- cgit v1.2.3 From 2934ddcc6b2c56d12eda6fcf12d8264f43bc0a12 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Sun, 2 Feb 2014 23:01:20 -0600 Subject: when connecting to an ECalClient, use the proper source types for events/tasks --- src/planner-eds.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp index 79b340a..06b29d3 100644 --- a/src/planner-eds.cpp +++ b/src/planner-eds.cpp @@ -121,10 +121,18 @@ private: static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) { auto self = static_cast(gself); + ECalClientSourceType source_type; + + if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR)) + source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; + else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST)) + source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; + else + g_assert_not_reached(); g_debug("connecting a client to source %s", e_source_get_uid(source)); e_cal_client_connect(source, - E_CAL_CLIENT_SOURCE_TYPE_EVENTS, + source_type, self->m_cancellable, on_client_connected, gself); -- cgit v1.2.3 From c4e010f1cd307a5919cace98f82941a57bc91573 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 3 Feb 2014 00:05:42 -0600 Subject: update the header state when the planner's appointments change --- src/menu.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/menu.cpp b/src/menu.cpp index b3dcc53..5d97859 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -108,6 +108,7 @@ 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 }); m_state->clock->date_changed.connect([this](){ -- cgit v1.2.3 From 35850dcd7c95e08b86052d5ac72ca23d00b5180f Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 3 Feb 2014 00:32:03 -0600 Subject: from alarm dev branch: snap decision handler --- src/CMakeLists.txt | 1 + src/main.cpp | 9 +++++ src/snap.cpp | 112 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/snap.cpp (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 12a5b74..fdf3e5e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -28,6 +28,7 @@ add_library (${SERVICE_LIB} STATIC menu.cpp planner-eds.cpp settings-live.cpp + snap.cpp timezone-file.cpp timezone-geoclue.cpp timezones-live.cpp diff --git a/src/main.cpp b/src/main.cpp index 1534777..3c17923 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,11 +21,13 @@ #include #include +#include #include #include #include #include #include +#include #include #include @@ -67,6 +69,13 @@ main(int /*argc*/, char** /*argv*/) std::shared_ptr actions(new LiveActions(state)); MenuFactory factory(actions, state); + // snap decisions + ClockWatcherImpl clock_watcher(state); + Snap snap(state->clock); + clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){ + snap(appt); + }); + // create the menus std::vector> menus; for(int i=0, n=Menu::NUM_PROFILES; i. + * + * Authors: + * Charles Kerr + */ + +#include +#include +#include + +#include + +#include + +#include +#include + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +namespace +{ + +void dispatch_alarm_url(const Appointment& appointment) +{ + g_return_if_fail(!appointment.has_alarms); + + 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()); + + url_dispatch_send(appointment.url.c_str(), nullptr, nullptr); +} + +void on_snap_decided(NotifyNotification * /*notification*/, + char * action, + gpointer gurl) +{ + g_debug("%s: %s", G_STRFUNC, action); + + if (!g_strcmp0(action, "show")) + { + const auto url = static_cast(gurl); + g_debug("dispatching url '%s'", url); + url_dispatch_send(url, nullptr, nullptr); + } +} + +} // unnamed namespace + +/*** +**** +***/ + +Snap::Snap(const std::shared_ptr& clock): + m_formatter(clock) +{ +} + +void Snap::operator()(const Appointment& appointment) +{ + const auto timestr = m_formatter.header.get(); + const auto body = appointment.summary; + gchar* title = g_strdup_printf(_("Alarm %s"), timestr.c_str()); + const gchar* icon_name = "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); + + 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_error_free(error); + dispatch_alarm_url(appointment); + } + + g_free(title); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity -- cgit v1.2.3 From 86fb7d453df01b80f73a82b690626ff35477ad4b Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 3 Feb 2014 01:02:24 -0600 Subject: copyediting: remove a piece of dead code --- src/planner-eds.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp index 06b29d3..9048f52 100644 --- a/src/planner-eds.cpp +++ b/src/planner-eds.cpp @@ -179,7 +179,7 @@ private: 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(gself); - self->m_views[e_client_get_source(E_CLIENT(client))] = view;//G_CAL_CLIENT(client)] = view;//.insert(view); + self->m_views[e_client_get_source(E_CLIENT(client))] = view; 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); -- cgit v1.2.3 From 2893b128d4aa61f9c55f2916350c898b1fa4a477 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 3 Feb 2014 21:34:40 -0600 Subject: when displaying alarms, use the 'alarm-clock' key matching the icon in ubuntu-mobile-icons --- src/menu.cpp | 69 +++++++----------------------------------------------------- 1 file changed, 8 insertions(+), 61 deletions(-) (limited to 'src') diff --git a/src/menu.cpp b/src/menu.cpp index 5d97859..7f41b22 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -22,8 +22,6 @@ #include #include -#include - #include #include @@ -62,7 +60,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 @@ -78,9 +76,6 @@ protected: m_actions(actions), m_formatter(formatter) { - // preload the alarm icon from click - m_serialized_alarm_icon = create_alarm_icon(); - // initialize the menu create_gmenu(); for (int i=0; i m_formatter; GMenu* m_submenu = nullptr; - GVariant* get_serialized_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* get_serialized_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) + if (G_UNLIKELY(m_serialized_alarm_icon == 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; } +private: + GVariant* get_serialized_calendar_icon() { if (G_UNLIKELY(m_serialized_calendar_icon == nullptr)) -- cgit v1.2.3 From 7d1ec01369d1c107cd42b0a7501f8cdb3c7e08ac Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 3 Feb 2014 21:35:10 -0600 Subject: don't show snap decisions for appointments that don't have alarms. --- src/snap.cpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src') diff --git a/src/snap.cpp b/src/snap.cpp index bac8958..7acac9c 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -79,6 +79,9 @@ Snap::Snap(const std::shared_ptr& clock): void Snap::operator()(const Appointment& appointment) { + if (!appointment.has_alarms) + return; + const auto timestr = m_formatter.header.get(); const auto body = appointment.summary; gchar* title = g_strdup_printf(_("Alarm %s"), timestr.c_str()); -- 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 --- src/main.cpp | 2 +- src/planner-eds.cpp | 117 +++++++++++++++++++++++++++++++--------------------- 2 files changed, 71 insertions(+), 48 deletions(-) (limited to 'src') 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; -- 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 --- src/main.cpp | 2 +- src/snap.cpp | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'src') 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. --- src/main.cpp | 15 +++--- src/snap.cpp | 146 +++++++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 119 insertions(+), 42 deletions(-) (limited to 'src') 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); +} + /*** **** ***/ -- cgit v1.2.3 From 4a563742f7d1232af2fe23c2b89165943328e134 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 4 Feb 2014 14:10:41 -0600 Subject: use %X for showing the appointment time in the title --- src/snap.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/snap.cpp b/src/snap.cpp index 5f46dc7..60811d2 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -20,7 +20,6 @@ #include #include #include -#include // generate_full_format_string_at_time() #include #include @@ -122,8 +121,8 @@ void show_snap_decision(SnapData* data) { const Appointment& appointment = data->appointment; - auto timestr = generate_full_format_string_at_time(appointment.begin.get(), nullptr, nullptr); - auto title = g_strdup_printf(_("Alarm %s"), timestr); + 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"; @@ -144,7 +143,6 @@ void show_snap_decision(SnapData* data) } g_free(title); - g_free(timestr); } /** -- cgit v1.2.3 From 7e2ad62fd44917d73092e85a9eafdb958a6cf21c Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 4 Feb 2014 14:38:44 -0600 Subject: loop the ringtone until user acks the snap decision --- src/snap.cpp | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/snap.cpp b/src/snap.cpp index 60811d2..ed8442a 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -44,6 +44,9 @@ namespace *** libcanberra -- play sounds **/ +// arbitrary number, but we need a consistent id for play/cancel +const int32_t alarm_ca_id = 1; + ca_context *c_context = nullptr; ca_context* get_ca_context() @@ -61,14 +64,34 @@ ca_context* get_ca_context() return c_context; } +void play_alarm_sound(); + +gboolean play_alarm_sound_idle (gpointer) +{ + 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 + g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr); +} + void play_soundfile(const char* filename) { auto context = get_ca_context(); g_return_if_fail(context != nullptr); - const auto rv = ca_context_play(context, 0, CA_PROP_MEDIA_FILENAME, filename, NULL); + ca_proplist* props = nullptr; + ca_proplist_create(&props); + 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\n", filename, ca_strerror(rv)); + g_warning("Failed to play file '%s': %s", filename, ca_strerror(rv)); + + g_clear_pointer(&props, ca_proplist_destroy); } void play_alarm_sound() @@ -76,6 +99,17 @@ void play_alarm_sound() play_soundfile(ALARM_SOUND_FILENAME); } +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)); + } +} + /** *** libnotify -- snap decisions **/ @@ -102,12 +136,14 @@ struct SnapData void on_snap_show(NotifyNotification*, gchar* /*action*/, gpointer gdata) { + stop_alarm_sound(); auto data = static_cast(gdata); data->show(data->appointment); } void on_snap_dismiss(NotifyNotification*, gchar* /*action*/, gpointer gdata) { + stop_alarm_sound(); auto data = static_cast(gdata); data->dismiss(data->appointment); } -- cgit v1.2.3 From 98eb9efebeadf69fc73613c6764a97f6f8163ec4 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 4 Feb 2014 17:10:48 -0600 Subject: add the alarm bell to the canberra cache before it gets played in a loop. --- src/snap.cpp | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/snap.cpp b/src/snap.cpp index ed8442a..a64f358 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -54,11 +54,23 @@ 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); + if (rv != CA_SUCCESS) + g_warning("Couldn't add '%s' to canberra cache: %s", filename, ca_strerror(rv)); + } } return c_context; @@ -72,19 +84,22 @@ gboolean play_alarm_sound_idle (gpointer) return G_SOURCE_REMOVE; } -void on_alarm_play_done (ca_context* /*context*/, uint32_t /*id*/, int /*rv*/, void* /*user_data*/) +void on_alarm_play_done (ca_context* /*context*/, uint32_t /*id*/, int rv, void* /*user_data*/) { // wait one second, then play it again - g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr); + if (rv == CA_SUCCESS) + g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr); } -void play_soundfile(const char* filename) +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); + 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); @@ -94,11 +109,6 @@ void play_soundfile(const char* filename) g_clear_pointer(&props, ca_proplist_destroy); } -void play_alarm_sound() -{ - play_soundfile(ALARM_SOUND_FILENAME); -} - void stop_alarm_sound() { auto context = get_ca_context(); -- cgit v1.2.3 From b96ba1beb969e7116881137d326967b48044845b Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 5 Feb 2014 08:36:31 -0600 Subject: bugfix: when closing the snap decision, ensure there's not a timeout waiting to loop the ringtone --- src/snap.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/snap.cpp b/src/snap.cpp index a64f358..0d548b8 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -48,6 +48,7 @@ namespace const int32_t alarm_ca_id = 1; ca_context *c_context = nullptr; +guint timeout_tag = 0; ca_context* get_ca_context() { @@ -80,6 +81,7 @@ void play_alarm_sound(); gboolean play_alarm_sound_idle (gpointer) { + timeout_tag = 0; play_alarm_sound(); return G_SOURCE_REMOVE; } @@ -87,8 +89,8 @@ gboolean play_alarm_sound_idle (gpointer) 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) - g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr); + if ((rv == CA_SUCCESS) && (timeout_tag == 0)) + timeout_tag = g_timeout_add_seconds (1, play_alarm_sound_idle, nullptr); } void play_alarm_sound() @@ -118,6 +120,12 @@ void stop_alarm_sound() 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; + } } /** -- cgit v1.2.3 From 7137a652b136d375637390b1659007104e96b61f Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 5 Feb 2014 15:31:03 -0600 Subject: copyediting: make the Snap lambdas a little easier to read. --- src/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 7e09fda..daf2a22 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,9 +68,9 @@ main(int /*argc*/, char** /*argv*/) ClockWatcherImpl clock_watcher(state); Snap snap; clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){ - snap(appt, - [](const Appointment& a){url_dispatch_send(a.url.c_str(), nullptr, nullptr);}, - [](const Appointment&){}); + 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); }); // create the menus -- 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. --- src/main.cpp | 8 +++++--- src/planner-eds.cpp | 22 +++++++++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) (limited to 'src') 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 5c91c53c1d56a356e091d6910b8c71e7c139f46a Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 5 Feb 2014 16:30:11 -0600 Subject: if an alarm doesn't have a URL associated with it, use 'appid://com.ubuntu.clock/clock/current-user-version' as a fallback url. --- src/main.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/main.cpp b/src/main.cpp index 5f5ee3c..2be727f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -70,7 +70,14 @@ main(int /*argc*/, char** /*argv*/) 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 - auto snap_show = [](const Appointment& a){url_dispatch_send(a.url.c_str(), nullptr, nullptr);}; + 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); }); -- 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 --- src/main.cpp | 8 +++----- src/planner-eds.cpp | 22 +++------------------- 2 files changed, 6 insertions(+), 24 deletions(-) (limited to 'src') 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 a75d4006a59c6e58e14d21fa6820a86f52d113cd Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Wed, 5 Feb 2014 16:57:10 -0600 Subject: remove upcoming events from the menu once they're no longer upcoming. --- src/menu.cpp | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src') diff --git a/src/menu.cpp b/src/menu.cpp index 7f41b22..50a0087 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -239,16 +239,23 @@ 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()) { + // don't show too many if (n++ >= MAX_APPTS) break; + // don't show duplicates if (added.count(appt.uid)) continue; + // don't show appointments that have already started + if ((appt.begin Date: Wed, 5 Feb 2014 17:04:00 -0600 Subject: when playing a sound in canberra, don't use CA_PROP_EVENT_ID if caching failed --- src/snap.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/snap.cpp b/src/snap.cpp index 0d548b8..f2d075a 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -47,6 +47,7 @@ namespace // 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; @@ -69,6 +70,7 @@ ca_context* get_ca_context() 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)); } @@ -101,7 +103,8 @@ void play_alarm_sound() ca_proplist* props = nullptr; ca_proplist_create(&props); - ca_proplist_sets(props, CA_PROP_EVENT_ID, "alarm"); + 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); @@ -230,6 +233,7 @@ Snap::Snap() Snap::~Snap() { + media_cached = false; g_clear_pointer(&c_context, ca_context_destroy); } -- 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. --- src/date-time.cpp | 18 ++++++++++++++++++ src/menu.cpp | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 46 insertions(+), 9 deletions(-) (limited to 'src') 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