diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/actions-live.cpp | 6 | ||||
-rw-r--r-- | src/actions.cpp | 13 | ||||
-rw-r--r-- | src/clock-watcher.cpp | 26 | ||||
-rw-r--r-- | src/date-time.cpp | 27 | ||||
-rw-r--r-- | src/engine-eds.cpp | 531 | ||||
-rw-r--r-- | src/main.cpp | 13 | ||||
-rw-r--r-- | src/menu.cpp | 14 | ||||
-rw-r--r-- | src/planner-eds.cpp | 555 | ||||
-rw-r--r-- | src/planner-month.cpp | 66 | ||||
-rw-r--r-- | src/planner-range.cpp | 72 | ||||
-rw-r--r-- | src/planner-upcoming.cpp | 61 | ||||
-rw-r--r-- | src/utils.c | 4 |
13 files changed, 841 insertions, 551 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7b1d7df..5660e6a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ add_library (${SERVICE_LIB} STATIC clock-live.cpp clock-watcher.cpp date-time.cpp + engine-eds.cpp exporter.cpp formatter.cpp formatter-desktop.cpp @@ -22,6 +23,9 @@ add_library (${SERVICE_LIB} STATIC locations-settings.cpp menu.cpp planner-eds.cpp + planner-month.cpp + planner-range.cpp + planner-upcoming.cpp settings-live.cpp snap.cpp timezone-file.cpp diff --git a/src/actions-live.cpp b/src/actions-live.cpp index ccc7fcf..c179274 100644 --- a/src/actions-live.cpp +++ b/src/actions-live.cpp @@ -91,13 +91,15 @@ void LiveActions::open_phone_clock_app() void LiveActions::open_planner_at(const DateTime& dt) { - auto cmd = dt.format("evolution \"calendar:///?startdate=%Y%m%d\""); + const auto day_begins = dt.add_full(0, 0, 0, -dt.hour(), -dt.minute(), -dt.seconds()); + const auto gmt = day_begins.to_timezone("UTC"); + auto cmd = gmt.format("evolution \"calendar:///?startdate=%Y%m%dT%H%M%SZ\""); execute_command(cmd.c_str()); } void LiveActions::open_appointment(const std::string& uid) { - for(const auto& appt : state()->planner->upcoming.get()) + for(const auto& appt : state()->calendar_upcoming->appointments().get()) { if(appt.uid != uid) continue; diff --git a/src/actions.cpp b/src/actions.cpp index d6fa698..c9c6286 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -65,7 +65,7 @@ void on_activate_appointment(GSimpleAction * /*action*/, g_return_if_fail(uid && *uid); // find url of the upcoming appointment with this uid - for (const auto& appt : self->state()->planner->upcoming.get()) + for (const auto& appt : self->state()->calendar_upcoming->appointments().get()) { if (appt.uid == uid) { @@ -146,7 +146,7 @@ GVariant* create_default_header_state() GVariant* create_calendar_state(const std::shared_ptr<State>& state) { gboolean days[32] = { 0 }; - for (const auto& appt : state->planner->this_month.get()) + for (const auto& appt : state->calendar_month->appointments().get()) days[appt.begin.day_of_month()] = true; GVariantBuilder day_builder; @@ -163,7 +163,7 @@ GVariant* create_calendar_state(const std::shared_ptr<State>& state) g_variant_builder_add(&dict_builder, "{sv}", key, v); key = "calendar-day"; - v = g_variant_new_int64(state->planner->time.get().to_unix()); + v = g_variant_new_int64(state->calendar_month->month().get().to_unix()); g_variant_builder_add(&dict_builder, "{sv}", key, v); key = "show-week-numbers"; @@ -219,10 +219,10 @@ Actions::Actions(const std::shared_ptr<State>& state): /// Keep our GActionGroup's action's states in sync with m_state /// - m_state->planner->time.changed().connect([this](const DateTime&){ + m_state->calendar_month->month().changed().connect([this](const DateTime&){ update_calendar_state(); }); - m_state->planner->this_month.changed().connect([this](const std::vector<Appointment>&){ + m_state->calendar_month->appointments().changed().connect([this](const std::vector<Appointment>&){ update_calendar_state(); }); m_state->settings->show_week_numbers.changed().connect([this](bool){ @@ -246,7 +246,8 @@ void Actions::update_calendar_state() void Actions::set_calendar_date(const DateTime& date) { - m_state->planner->time.set(date); + m_state->calendar_month->month().set(date); + m_state->calendar_upcoming->date().set(date); } GActionGroup* Actions::action_group() diff --git a/src/clock-watcher.cpp b/src/clock-watcher.cpp index a2e700d..5da66c8 100644 --- a/src/clock-watcher.cpp +++ b/src/clock-watcher.cpp @@ -27,17 +27,27 @@ namespace datetime { **** ***/ -ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<const State>& state): - m_state(state) +ClockWatcherImpl::ClockWatcherImpl(const std::shared_ptr<Clock>& clock, + const std::shared_ptr<UpcomingPlanner>& upcoming_planner): + m_clock(clock), + m_upcoming_planner(upcoming_planner) { - m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){ - g_debug("ClockWatcher pulse because upcoming appointments changed"); + m_clock->date_changed.connect([this](){ + const auto now = m_clock->localtime(); + g_debug("ClockWatcher %p refretching appointments due to date change: %s", this, now.format("%F %T").c_str()); + m_upcoming_planner->date().set(now); + }); + + m_clock->minute_changed.connect([this](){ + g_debug("ClockWatcher %p calling pulse() due to clock minute_changed", this); pulse(); }); - m_state->clock->minute_changed.connect([this](){ - g_debug("ClockWatcher pulse because clock minute_changed"); + + m_upcoming_planner->appointments().changed().connect([this](const std::vector<Appointment>&){ + g_debug("ClockWatcher %p calling pulse() due to appointments changed", this); pulse(); }); + pulse(); } @@ -48,9 +58,9 @@ core::Signal<const Appointment&>& ClockWatcherImpl::alarm_reached() void ClockWatcherImpl::pulse() { - const auto now = m_state->clock->localtime(); + const auto now = m_clock->localtime(); - for(const auto& appointment : m_state->planner->upcoming.get()) + for(const auto& appointment : m_upcoming_planner->appointments().get()) { if (m_triggered.count(appointment.uid)) continue; diff --git a/src/date-time.cpp b/src/date-time.cpp index 432d877..a1c1d1b 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -46,14 +46,22 @@ DateTime& DateTime::operator=(const DateTime& that) DateTime::DateTime(time_t t) { - GDateTime * gdt = g_date_time_new_from_unix_local(t); + auto gdt = g_date_time_new_from_unix_local(t); reset(gdt); g_date_time_unref(gdt); } DateTime DateTime::NowLocal() { - GDateTime * gdt = g_date_time_new_now_local(); + auto gdt = g_date_time_new_now_local(); + DateTime dt(gdt); + g_date_time_unref(gdt); + return dt; +} + +DateTime DateTime::Local(int year, int month, int day, int hour, int minute, int seconds) +{ + auto gdt = g_date_time_new_local (year, month, day, hour, minute, seconds); DateTime dt(gdt); g_date_time_unref(gdt); return dt; @@ -97,11 +105,26 @@ std::string DateTime::format(const std::string& fmt) const return ret; } +void DateTime::ymd(int& year, int& month, int& day) const +{ + g_date_time_get_ymd(get(), &year, &month, &day); +} + int DateTime::day_of_month() const { return g_date_time_get_day_of_month(get()); } +int DateTime::hour() const +{ + return g_date_time_get_hour(get()); +} + +int DateTime::minute() const +{ + return g_date_time_get_minute(get()); +} + double DateTime::seconds() const { return g_date_time_get_seconds(get()); diff --git a/src/engine-eds.cpp b/src/engine-eds.cpp new file mode 100644 index 0000000..c557857 --- /dev/null +++ b/src/engine-eds.cpp @@ -0,0 +1,531 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/engine-eds.h> + +#include <libical/ical.h> +#include <libical/icaltime.h> +#include <libecal/libecal.h> +#include <libedataserver/libedataserver.h> + +#include <algorithm> // std::sort() +#include <map> +#include <set> + +namespace unity { +namespace indicator { +namespace datetime { + +/**** +***** +****/ + +class EdsEngine::Impl +{ +public: + + Impl(EdsEngine& owner): + m_owner(owner), + m_cancellable(g_cancellable_new()) + { + e_source_registry_new(m_cancellable, on_source_registry_ready, this); + } + + ~Impl() + { + 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); + + if (m_source_registry) + g_signal_handlers_disconnect_by_data(m_source_registry, this); + g_clear_object(&m_source_registry); + } + + core::Signal<>& changed() + { + return m_changed; + } + + void get_appointments(const DateTime& begin, + const DateTime& end, + const Timezone& timezone, + std::function<void(const std::vector<Appointment>&)> func) + { + const auto begin_timet = begin.to_unix(); + const auto end_timet = end.to_unix(); + + const auto b_str = begin.format("%F %T"); + const auto e_str = end.format("%F %T"); + g_debug("getting all appointments from [%s ... %s]", b_str.c_str(), e_str.c_str()); + + /** + *** init the default timezone + **/ + + icaltimezone * default_timezone = nullptr; + const auto tz = timezone.timezone.get().c_str(); + if (tz && *tz) + { + default_timezone = icaltimezone_get_builtin_timezone(tz); + + if (default_timezone == nullptr) // maybe str is a tzid? + default_timezone = icaltimezone_get_builtin_timezone_from_tzid(tz); + + g_debug("default_timezone is %p", (void*)default_timezone); + } + + /** + *** walk through the sources to build the appointment list + **/ + + auto task_deleter = [](Task* task){ + // give the caller the (sorted) finished product + auto& a = task->appointments; + std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;}); + task->func(a); + // we're done; delete the task + g_debug("time to delete task %p", (void*)task); + delete task; + }; + + std::shared_ptr<Task> main_task(new Task(this, func), task_deleter); + + for (auto& kv : m_clients) + { + auto& client = kv.second; + if (default_timezone != nullptr) + e_cal_client_set_default_timezone(client, default_timezone); + + // start a new subtask to enumerate all the components in this client. + auto& source = kv.first; + auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR); + const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension)); + g_debug("calling e_cal_client_generate_instances for %p", (void*)client); + e_cal_client_generate_instances(client, + begin_timet, + end_timet, + m_cancellable, + my_get_appointments_foreach, + new AppointmentSubtask (main_task, client, color), + [](gpointer g){delete static_cast<AppointmentSubtask*>(g);}); + } + } + +private: + + void set_dirty_now() + { + m_changed(); + } + + static gboolean set_dirty_now_static (gpointer gself) + { + auto self = static_cast<Impl*>(gself); + self->m_rebuild_tag = 0; + self->set_dirty_now(); + return G_SOURCE_REMOVE; + } + + void set_dirty_soon() + { + static const int ARBITRARY_BATCH_MSEC = 200; + + if (m_rebuild_tag == 0) + m_rebuild_tag = g_timeout_add(ARBITRARY_BATCH_MSEC, set_dirty_now_static, this); + } + + static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself) + { + GError * error = nullptr; + auto r = e_source_registry_new_finish(res, &error); + if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("indicator-datetime cannot show EDS appointments: %s", error->message); + + g_error_free(error); + } + else + { + g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), gself); + g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), gself); + g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), gself); + g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself); + g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), gself); + + auto self = static_cast<Impl*>(gself); + self->m_source_registry = r; + 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<Impl*>(gself); + + self->m_sources.insert(E_SOURCE(g_object_ref(source))); + + if (e_source_get_enabled(source)) + on_source_enabled(registry, source, gself); + } + + static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + auto self = static_cast<Impl*>(gself); + ECalClientSourceType source_type; + bool client_wanted = false; + + if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR)) + { + source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; + client_wanted = true; + } + else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST)) + { + source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; + client_wanted = true; + } + + const auto source_uid = e_source_get_uid(source); + if (client_wanted) + { + g_debug("%s connecting a client to source %s", G_STRFUNC, source_uid); + e_cal_client_connect(source, + source_type, + self->m_cancellable, + on_client_connected, + gself); + } + else + { + g_debug("%s not using source %s -- no tasks/calendar", G_STRFUNC, source_uid); + } + } + + static void on_client_connected(GObject* /*source*/, GAsyncResult * res, gpointer gself) + { + GError * error = nullptr; + EClient * client = e_cal_client_connect_finish(res, &error); + if (error) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("indicator-datetime cannot connect to EDS source: %s", error->message); + + g_error_free(error); + } + else + { + // add the client to our collection + auto self = static_cast<Impl*>(gself); + g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client))); + self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client); + + // now create a view for it so that we can listen for changes + e_cal_client_get_view (E_CAL_CLIENT(client), + "#t", // match all + self->m_cancellable, + on_client_view_ready, + self); + + g_debug("client connected; calling set_dirty_soon()"); + self->set_dirty_soon(); + } + } + + static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself) + { + GError* error = nullptr; + ECalClientView* view = nullptr; + + if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error)) + { + // add the view to our collection + e_cal_client_view_start(view, &error); + g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client))); + auto self = static_cast<Impl*>(gself); + self->m_views[e_client_get_source(E_CLIENT(client))] = view; + + g_signal_connect(view, "objects-added", G_CALLBACK(on_view_objects_added), self); + g_signal_connect(view, "objects-modified", G_CALLBACK(on_view_objects_modified), self); + g_signal_connect(view, "objects-removed", G_CALLBACK(on_view_objects_removed), self); + g_debug("view connected; calling set_dirty_soon()"); + self->set_dirty_soon(); + } + else if(error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning("indicator-datetime cannot get View to EDS client: %s", error->message); + + g_error_free(error); + } + } + + static void on_view_objects_added(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) + { + g_debug("%s", G_STRFUNC); + static_cast<Impl*>(gself)->set_dirty_soon(); + } + static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) + { + g_debug("%s", G_STRFUNC); + static_cast<Impl*>(gself)->set_dirty_soon(); + } + static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) + { + g_debug("%s", G_STRFUNC); + static_cast<Impl*>(gself)->set_dirty_soon(); + } + + static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + static_cast<Impl*>(gself)->disable_source(source); + } + void disable_source(ESource* source) + { + // if an ECalClientView is associated with this source, remove it + auto vit = m_views.find(source); + if (vit != m_views.end()) + { + auto& view = vit->second; + e_cal_client_view_stop(view, nullptr); + const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this); + g_warn_if_fail(n_disconnected == 3); + g_object_unref(view); + m_views.erase(vit); + set_dirty_soon(); + } + + // if an ECalClient is associated with this source, remove it + auto cit = m_clients.find(source); + if (cit != m_clients.end()) + { + auto& client = cit->second; + g_object_unref(client); + m_clients.erase(cit); + set_dirty_soon(); + } + } + + static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) + { + static_cast<Impl*>(gself)->remove_source(source); + } + void remove_source(ESource* source) + { + disable_source(source); + + auto sit = m_sources.find(source); + if (sit != m_sources.end()) + { + g_object_unref(*sit); + m_sources.erase(sit); + set_dirty_soon(); + } + } + + static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself) + { + g_debug("source changed; calling set_dirty_soon()"); + static_cast<Impl*>(gself)->set_dirty_soon(); + } + +private: + + typedef std::function<void(const std::vector<Appointment>&)> appointment_func; + + struct Task + { + Impl* p; + appointment_func func; + std::vector<Appointment> appointments; + Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {} + }; + + struct AppointmentSubtask + { + std::shared_ptr<Task> task; + ECalClient* client; + std::string color; + AppointmentSubtask(const std::shared_ptr<Task>& task_in, ECalClient* client_in, const char* color_in): + task(task_in), client(client_in) + { + if (color_in) + color = color_in; + } + }; + + struct UrlSubtask + { + std::shared_ptr<Task> task; + Appointment appointment; + UrlSubtask(const std::shared_ptr<Task>& task_in, const Appointment& appointment_in): + task(task_in), appointment(appointment_in) {} + }; + + static gboolean + my_get_appointments_foreach(ECalComponent* component, + time_t begin, + time_t end, + gpointer gsubtask) + { + const auto vtype = e_cal_component_get_vtype(component); + auto subtask = static_cast<AppointmentSubtask*>(gsubtask); + + if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO)) + { + const gchar* uid = nullptr; + e_cal_component_get_uid(component, &uid); + + auto status = ICAL_STATUS_NONE; + e_cal_component_get_status(component, &status); + + if ((uid != nullptr) && + (status != ICAL_STATUS_COMPLETED) && + (status != ICAL_STATUS_CANCELLED)) + { + Appointment appointment; + + /* Determine whether this is a recurring event. + NB: icalrecurrencetype supports complex recurrence patterns; + however, since design only allows daily recurrence, + that's all we support here. */ + GSList * recur_list; + e_cal_component_get_rrule_list(component, &recur_list); + for (auto l=recur_list; l!=nullptr; l=l->next) + { + const auto recur = static_cast<struct icalrecurrencetype*>(l->data); + appointment.is_daily |= ((recur->freq == ICAL_DAILY_RECURRENCE) + && (recur->interval == 1)); + } + e_cal_component_free_recur_list(recur_list); + + ECalComponentText text; + text.value = nullptr; + e_cal_component_get_summary(component, &text); + if (text.value) + appointment.summary = text.value; + + appointment.begin = DateTime(begin); + appointment.end = DateTime(end); + appointment.color = subtask->color; + appointment.is_event = vtype == E_CAL_COMPONENT_EVENT; + appointment.uid = uid; + + GList * alarm_uids = e_cal_component_get_alarm_uids(component); + appointment.has_alarms = alarm_uids != nullptr; + cal_obj_uid_list_free(alarm_uids); + + e_cal_client_get_attachment_uris(subtask->client, + uid, + nullptr, + subtask->task->p->m_cancellable, + on_appointment_uris_ready, + new UrlSubtask(subtask->task, appointment)); + } + } + + return G_SOURCE_CONTINUE; + } + + static void on_appointment_uris_ready(GObject* client, GAsyncResult* res, gpointer gsubtask) + { + auto subtask = static_cast<UrlSubtask*>(gsubtask); + + GSList * uris = nullptr; + GError * error = nullptr; + e_cal_client_get_attachment_uris_finish(E_CAL_CLIENT(client), res, &uris, &error); + if (error != nullptr) + { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + !g_error_matches(error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED)) + { + g_warning("Error getting appointment uris: %s", error->message); + } + + g_error_free(error); + } + else if (uris != nullptr) + { + subtask->appointment.url = (const char*) uris->data; // copy the first URL + g_debug("found url '%s' for appointment '%s'", subtask->appointment.url.c_str(), subtask->appointment.summary.c_str()); + e_client_util_free_string_slist(uris); + } + + g_debug("adding appointment '%s' '%s'", subtask->appointment.summary.c_str(), subtask->appointment.url.c_str()); + subtask->task->appointments.push_back(subtask->appointment); + delete subtask; + } + + EdsEngine& m_owner; + core::Signal<> m_changed; + std::set<ESource*> m_sources; + std::map<ESource*,ECalClient*> m_clients; + std::map<ESource*,ECalClientView*> m_views; + GCancellable* m_cancellable = nullptr; + ESourceRegistry* m_source_registry = nullptr; + guint m_rebuild_tag = 0; +}; + +/*** +**** +***/ + +EdsEngine::EdsEngine(): + p(new Impl(*this)) +{ +} + +EdsEngine::~EdsEngine() =default; + +core::Signal<>& EdsEngine::changed() +{ + return p->changed(); +} + +void EdsEngine::get_appointments(const DateTime& begin, + const DateTime& end, + const Timezone& tz, + std::function<void(const std::vector<Appointment>&)> func) +{ + p->get_appointments(begin, end, tz, func); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/main.cpp b/src/main.cpp index 31d9db6..3e16555 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ #include <datetime/actions-live.h> #include <datetime/clock.h> #include <datetime/clock-watcher.h> +#include <datetime/engine-eds.h> #include <datetime/exporter.h> #include <datetime/locations-settings.h> #include <datetime/menu.h> @@ -27,6 +28,7 @@ #include <datetime/settings-live.h> #include <datetime/snap.h> #include <datetime/state.h> +#include <datetime/timezone-file.h> #include <datetime/timezones-live.h> #include <glib/gi18n.h> // bindtextdomain() @@ -56,16 +58,21 @@ main(int /*argc*/, char** /*argv*/) std::shared_ptr<Settings> live_settings(new LiveSettings); std::shared_ptr<Timezones> live_timezones(new LiveTimezones(live_settings, TIMEZONE_FILE)); std::shared_ptr<Clock> live_clock(new LiveClock(live_timezones)); + std::shared_ptr<Timezone> file_timezone(new FileTimezone(TIMEZONE_FILE)); + std::shared_ptr<EdsEngine> eds_engine (new EdsEngine); + const auto now = live_clock->localtime(); state->settings = live_settings; state->clock = live_clock; state->locations.reset(new SettingsLocations(live_settings, live_timezones)); - state->planner.reset(new PlannerEds(live_clock)); - state->planner->time = live_clock->localtime(); + auto calendar_month = new MonthPlanner(std::shared_ptr<RangePlanner>(new EdsPlanner(eds_engine, file_timezone)), now); + state->calendar_month.reset(calendar_month); + state->calendar_upcoming.reset(new UpcomingPlanner(std::shared_ptr<RangePlanner>(new EdsPlanner(eds_engine, file_timezone)), now)); std::shared_ptr<Actions> actions(new LiveActions(state)); MenuFactory factory(actions, state); // snap decisions - ClockWatcherImpl clock_watcher(state); + std::shared_ptr<UpcomingPlanner> upcoming_planner(new UpcomingPlanner(std::shared_ptr<RangePlanner>(new EdsPlanner (eds_engine, file_timezone)), now)); + ClockWatcherImpl clock_watcher(live_clock, upcoming_planner); Snap snap; clock_watcher.alarm_reached().connect([&snap](const Appointment& appt){ auto snap_show = [](const Appointment& a){ diff --git a/src/menu.cpp b/src/menu.cpp index 797757f..d6abd27 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -104,7 +104,7 @@ protected: m_state->settings->show_events.changed().connect([this](bool){ update_section(Appointments); // showing events got toggled }); - m_state->planner->upcoming.changed().connect([this](const std::vector<Appointment>&){ + m_state->calendar_upcoming->appointments().changed().connect([this](const std::vector<Appointment>&){ update_upcoming(); // our m_upcoming is planner->upcoming() filtered by time }); m_state->clock->date_changed.connect([this](){ @@ -138,12 +138,18 @@ protected: void update_upcoming() { + // show upcoming appointments that occur after "calendar_next_minute", + // where that is the wallclock time on the specified calendar day + const auto calendar_day = m_state->calendar_month->month().get(); const auto now = m_state->clock->localtime(); - const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds()); + int y, m, d; + calendar_day.ymd(y, m, d); + const auto calendar_now = DateTime::Local(y, m, d, now.hour(), now.minute(), now.seconds()); + const auto calendar_next_minute = calendar_now.add_full(0, 0, 0, 0, 1, -now.seconds()); std::vector<Appointment> upcoming; - for(const auto& a : m_state->planner->upcoming.get()) - if (next_minute <= a.begin) + for(const auto& a : m_state->calendar_upcoming->appointments().get()) + if (calendar_next_minute <= a.begin) upcoming.push_back(a); if (m_upcoming != upcoming) diff --git a/src/planner-eds.cpp b/src/planner-eds.cpp index a9eecf2..f3f28ee 100644 --- a/src/planner-eds.cpp +++ b/src/planner-eds.cpp @@ -18,546 +18,49 @@ */ #include <datetime/planner-eds.h> - -#include <datetime/appointment.h> - -#include <libical/ical.h> -#include <libical/icaltime.h> -#include <libecal/libecal.h> -#include <libedataserver/libedataserver.h> - -#include <algorithm> // std::sort() -#include <map> -#include <set> +#include <datetime/engine-eds.h> namespace unity { namespace indicator { namespace datetime { -/**** -***** -****/ +/*** +**** +***/ -class PlannerEds::Impl +EdsPlanner::EdsPlanner(const std::shared_ptr<EdsEngine>& engine, + const std::shared_ptr<Timezone>& timezone): + m_engine(engine), + m_timezone(timezone) { -public: - - Impl(PlannerEds& owner, const std::shared_ptr<Clock>& 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(MONTH); - }); - - rebuild_soon(ALL); - } - - ~Impl() - { - 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); - - if (m_source_registry) - g_signal_handlers_disconnect_by_data(m_source_registry, this); - g_clear_object(&m_source_registry); - } - -private: - - static void on_source_registry_ready(GObject* /*source*/, GAsyncResult* res, gpointer gself) - { - GError * error = nullptr; - auto r = e_source_registry_new_finish(res, &error); - if (error != nullptr) - { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning("indicator-datetime cannot show EDS appointments: %s", error->message); - - g_error_free(error); - } - else - { - g_signal_connect(r, "source-added", G_CALLBACK(on_source_added), gself); - g_signal_connect(r, "source-removed", G_CALLBACK(on_source_removed), gself); - g_signal_connect(r, "source-changed", G_CALLBACK(on_source_changed), gself); - g_signal_connect(r, "source-disabled", G_CALLBACK(on_source_disabled), gself); - g_signal_connect(r, "source-enabled", G_CALLBACK(on_source_enabled), gself); - - auto self = static_cast<Impl*>(gself); - self->m_source_registry = r; - 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<Impl*>(gself); - - self->m_sources.insert(E_SOURCE(g_object_ref(source))); - - if (e_source_get_enabled(source)) - on_source_enabled(registry, source, gself); - } - - static void on_source_enabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) - { - auto self = static_cast<Impl*>(gself); - ECalClientSourceType source_type; - bool client_wanted = false; + m_engine->changed().connect([this](){ + g_debug("EdsPlanner %p rebuilding soon because EdsEngine %p emitted 'changed' signal%p", this, m_engine.get()); + rebuild_soon(); + }); +} - if (e_source_has_extension(source, E_SOURCE_EXTENSION_CALENDAR)) - { - source_type = E_CAL_CLIENT_SOURCE_TYPE_EVENTS; - client_wanted = true; - } - else if (e_source_has_extension(source, E_SOURCE_EXTENSION_TASK_LIST)) - { - source_type = E_CAL_CLIENT_SOURCE_TYPE_TASKS; - client_wanted = true; - } +EdsPlanner::~EdsPlanner() =default; - const auto source_uid = e_source_get_uid(source); - if (client_wanted) - { - g_debug("%s connecting a client to source %s", G_STRFUNC, source_uid); - e_cal_client_connect(source, - source_type, - self->m_cancellable, - on_client_connected, - gself); - } - else - { - g_debug("%s not using source %s -- no tasks/calendar", G_STRFUNC, source_uid); - } - } - - static void on_client_connected(GObject* /*source*/, GAsyncResult * res, gpointer gself) - { - GError * error = nullptr; - EClient * client = e_cal_client_connect_finish(res, &error); - if (error) - { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) - g_warning("indicator-datetime cannot connect to EDS source: %s", error->message); - - g_error_free(error); - } - else - { - // add the client to our collection - auto self = static_cast<Impl*>(gself); - g_debug("got a client for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client))); - self->m_clients[e_client_get_source(client)] = E_CAL_CLIENT(client); - - // now create a view for it so that we can listen for changes - e_cal_client_get_view (E_CAL_CLIENT(client), - "#t", // match all - self->m_cancellable, - on_client_view_ready, - self); - - g_debug("client connected; calling rebuild_soon()"); - self->rebuild_soon(ALL); - } - } - - static void on_client_view_ready (GObject* client, GAsyncResult* res, gpointer gself) - { - GError* error = nullptr; - ECalClientView* view = nullptr; - - if (e_cal_client_get_view_finish (E_CAL_CLIENT(client), res, &view, &error)) - { - // add the view to our collection - e_cal_client_view_start(view, &error); - g_debug("got a view for %s", e_cal_client_get_local_attachment_store(E_CAL_CLIENT(client))); - auto self = static_cast<Impl*>(gself); - self->m_views[e_client_get_source(E_CLIENT(client))] = view; - - g_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(ALL); - } - 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<Impl*>(gself)->rebuild_soon(ALL); - } - static void on_view_objects_modified(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) - { - g_debug("%s", G_STRFUNC); - static_cast<Impl*>(gself)->rebuild_soon(ALL); - } - static void on_view_objects_removed(ECalClientView* /*view*/, gpointer /*objects*/, gpointer gself) - { - g_debug("%s", G_STRFUNC); - static_cast<Impl*>(gself)->rebuild_soon(ALL); - } - - static void on_source_disabled(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) - { - static_cast<Impl*>(gself)->disable_source(source); - } - void disable_source(ESource* source) - { - // if an ECalClientView is associated with this source, remove it - auto vit = m_views.find(source); - if (vit != m_views.end()) - { - auto& view = vit->second; - e_cal_client_view_stop(view, nullptr); - const auto n_disconnected = g_signal_handlers_disconnect_by_data(view, this); - g_warn_if_fail(n_disconnected == 3); - g_object_unref(view); - m_views.erase(vit); - rebuild_soon(ALL); - } - - // 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(ALL); - } - } - - static void on_source_removed(ESourceRegistry* /*registry*/, ESource* source, gpointer gself) - { - static_cast<Impl*>(gself)->remove_source(source); - } - void remove_source(ESource* source) - { - disable_source(source); - - auto sit = m_sources.find(source); - if (sit != m_sources.end()) - { - g_object_unref(*sit); - m_sources.erase(sit); - rebuild_soon(ALL); - } - } - - static void on_source_changed(ESourceRegistry* /*registry*/, ESource* /*source*/, gpointer gself) - { - g_debug("source changed; calling rebuild_soon()"); - static_cast<Impl*>(gself)->rebuild_soon(ALL); - } - -private: - - typedef std::function<void(const std::vector<Appointment>&)> appointment_func; - - struct Task - { - Impl* p; - appointment_func func; - std::vector<Appointment> appointments; - Task(Impl* p_in, const appointment_func& func_in): p(p_in), func(func_in) {} - }; - - struct AppointmentSubtask - { - std::shared_ptr<Task> task; - ECalClient* client; - std::string color; - AppointmentSubtask(const std::shared_ptr<Task>& task_in, ECalClient* client_in, const char* color_in): - task(task_in), client(client_in) - { - if (color_in) - color = color_in; - } - }; - - void rebuild_soon(int rebuild_flags) - { - 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); - } - - static gboolean rebuild_now_static(gpointer gself) - { - auto self = static_cast<Impl*>(gself); - const auto flags = self->m_rebuild_flags; - self->m_rebuild_tag = 0; - self->m_rebuild_flags = 0; - self->rebuild_now(flags); - return G_SOURCE_REMOVE; - } - - void rebuild_now(int rebuild_flags) - { - if (rebuild_flags & UPCOMING) - rebuild_upcoming(); - - 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<Appointment>& 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<Appointment>& 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) - { - const auto begin = g_date_time_to_unix(begin_dt); - const auto end = g_date_time_to_unix(end_dt); - - auto begin_str = g_date_time_format(begin_dt, "%F %T"); - auto end_str = g_date_time_format(end_dt, "%F %T"); - g_debug("getting all appointments from [%s ... %s]", begin_str, end_str); - g_free(begin_str); - g_free(end_str); - - /** - *** init the default timezone - **/ - - icaltimezone * default_timezone = nullptr; - - const auto tz = g_date_time_get_timezone_abbreviation(m_owner.time.get().get()); - g_debug("%s tz is %s", G_STRLOC, tz); - if (tz && *tz) - { - default_timezone = icaltimezone_get_builtin_timezone(tz); - - if (default_timezone == nullptr) // maybe str is a tzid? - default_timezone = icaltimezone_get_builtin_timezone_from_tzid(tz); - - g_debug("default_timezone is %p", (void*)default_timezone); - } - - /** - *** walk through the sources to build the appointment list - **/ - - auto task_deleter = [](Task* task){ - // give the caller the (sorted) finished product - auto& a = task->appointments; - std::sort(a.begin(), a.end(), [](const Appointment& a, const Appointment& b){return a.begin < b.begin;}); - task->func(a); - // we're done; delete the task - g_debug("time to delete task %p", (void*)task); - delete task; - }; - - std::shared_ptr<Task> main_task(new Task(this, func), task_deleter); - - for (auto& kv : m_clients) - { - auto& client = kv.second; - if (default_timezone != nullptr) - e_cal_client_set_default_timezone(client, default_timezone); - - // start a new subtask to enumerate all the components in this client. - auto& source = kv.first; - auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR); - const auto color = e_source_selectable_get_color(E_SOURCE_SELECTABLE(extension)); - g_debug("calling e_cal_client_generate_instances for %p", (void*)client); - e_cal_client_generate_instances(client, - begin, - end, - m_cancellable, - my_get_appointments_foreach, - new AppointmentSubtask (main_task, client, color), - [](gpointer g){delete static_cast<AppointmentSubtask*>(g);}); - } - } +void EdsPlanner::rebuild_now() +{ + const auto& r = range().get(); - struct UrlSubtask - { - std::shared_ptr<Task> task; - Appointment appointment; - UrlSubtask(const std::shared_ptr<Task>& task_in, const Appointment& appointment_in): - task(task_in), appointment(appointment_in) {} + auto on_appointments_fetched = [this](const std::vector<Appointment>& a){ + g_debug("EdsPlanner %p got %zu appointments", this, a.size()); + m_appointments.set(a); }; - static gboolean - my_get_appointments_foreach(ECalComponent* component, - time_t begin, - time_t end, - gpointer gsubtask) - { - const auto vtype = e_cal_component_get_vtype(component); - auto subtask = static_cast<AppointmentSubtask*>(gsubtask); - - if ((vtype == E_CAL_COMPONENT_EVENT) || (vtype == E_CAL_COMPONENT_TODO)) - { - const gchar* uid = nullptr; - e_cal_component_get_uid(component, &uid); - - auto status = ICAL_STATUS_NONE; - e_cal_component_get_status(component, &status); - - if ((uid != nullptr) && - (status != ICAL_STATUS_COMPLETED) && - (status != ICAL_STATUS_CANCELLED)) - { - Appointment appointment; - - /* Determine whether this is a recurring event. - NB: icalrecurrencetype supports complex recurrence patterns; - however, since design only allows daily recurrence, - that's all we support here. */ - GSList * recur_list; - e_cal_component_get_rrule_list(component, &recur_list); - for (auto l=recur_list; l!=nullptr; l=l->next) - { - const auto recur = static_cast<struct icalrecurrencetype*>(l->data); - appointment.is_daily |= ((recur->freq == ICAL_DAILY_RECURRENCE) - && (recur->interval == 1)); - } - e_cal_component_free_recur_list(recur_list); - - ECalComponentText text; - text.value = nullptr; - e_cal_component_get_summary(component, &text); - if (text.value) - appointment.summary = text.value; - - appointment.begin = DateTime(begin); - appointment.end = DateTime(end); - appointment.color = subtask->color; - appointment.is_event = vtype == E_CAL_COMPONENT_EVENT; - appointment.uid = uid; + m_engine->get_appointments(r.first, r.second, *m_timezone.get(), on_appointments_fetched); +} - GList * alarm_uids = e_cal_component_get_alarm_uids(component); - appointment.has_alarms = alarm_uids != nullptr; - cal_obj_uid_list_free(alarm_uids); - - e_cal_client_get_attachment_uris(subtask->client, - uid, - nullptr, - subtask->task->p->m_cancellable, - on_appointment_uris_ready, - new UrlSubtask(subtask->task, appointment)); - } - } - - return G_SOURCE_CONTINUE; - } - - static void on_appointment_uris_ready(GObject* client, GAsyncResult* res, gpointer gsubtask) - { - auto subtask = static_cast<UrlSubtask*>(gsubtask); - - GSList * uris = nullptr; - GError * error = nullptr; - e_cal_client_get_attachment_uris_finish(E_CAL_CLIENT(client), res, &uris, &error); - if (error != nullptr) - { - if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && - !g_error_matches(error, E_CLIENT_ERROR, E_CLIENT_ERROR_NOT_SUPPORTED)) - { - g_warning("Error getting appointment uris: %s", error->message); - } - - g_error_free(error); - } - else if (uris != nullptr) - { - subtask->appointment.url = (const char*) uris->data; // copy the first URL - g_debug("found url '%s' for appointment '%s'", subtask->appointment.url.c_str(), subtask->appointment.summary.c_str()); - e_client_util_free_string_slist(uris); - } - - g_debug("adding appointment '%s' '%s'", subtask->appointment.summary.c_str(), subtask->appointment.url.c_str()); - subtask->task->appointments.push_back(subtask->appointment); - delete subtask; - } - - PlannerEds& m_owner; - std::shared_ptr<Clock> m_clock; - std::set<ESource*> m_sources; - std::map<ESource*,ECalClient*> m_clients; - std::map<ESource*,ECalClientView*> m_views; - GCancellable* m_cancellable = nullptr; - ESourceRegistry* m_source_registry = nullptr; - guint m_rebuild_tag = 0; - guint m_rebuild_flags = 0; - enum { UPCOMING=(1<<0), MONTH=(1<<1), ALL=UPCOMING|MONTH }; -}; - -PlannerEds::PlannerEds(const std::shared_ptr<Clock>& clock): p(new Impl(*this, clock)) {} +core::Property<std::vector<Appointment>>& EdsPlanner::appointments() +{ + return m_appointments; +} -PlannerEds::~PlannerEds() =default; +/*** +**** +***/ } // namespace datetime } // namespace indicator diff --git a/src/planner-month.cpp b/src/planner-month.cpp new file mode 100644 index 0000000..5920daa --- /dev/null +++ b/src/planner-month.cpp @@ -0,0 +1,66 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/planner-month.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +MonthPlanner::MonthPlanner(const std::shared_ptr<RangePlanner>& range_planner, + const DateTime& month_in): + m_range_planner(range_planner) +{ + month().changed().connect([this](const DateTime& m){ + auto month_begin = m.add_full(0, // no years + 0, // no months + -(m.day_of_month()-1), + -m.hour(), + -m.minute(), + -m.seconds()); + auto month_end = month_begin.add_full(0, 1, 0, 0, 0, -0.1); + g_debug("PlannerMonth %p setting calendar month range: [%s..%s]", this, month_begin.format("%F %T").c_str(), month_end.format("%F %T").c_str()); + m_range_planner->range().set(std::pair<DateTime,DateTime>(month_begin,month_end)); + }); + + month().set(month_in); +} + +core::Property<DateTime>& MonthPlanner::month() +{ + return m_month; +} + +core::Property<std::vector<Appointment>>& MonthPlanner::appointments() +{ + return m_range_planner->appointments(); +} + + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/planner-range.cpp b/src/planner-range.cpp new file mode 100644 index 0000000..13a1492 --- /dev/null +++ b/src/planner-range.cpp @@ -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 <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/planner-range.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +RangePlanner::RangePlanner(): + m_range(std::pair<DateTime,DateTime>(DateTime::NowLocal(), DateTime::NowLocal())) +{ + range().changed().connect([this](const std::pair<DateTime,DateTime>&){ + g_debug("rebuilding because the date range changed"); + rebuild_soon(); + }); +} + +RangePlanner::~RangePlanner() +{ + if (m_rebuild_tag) + g_source_remove(m_rebuild_tag); +} + +void RangePlanner::rebuild_soon() +{ + static const int ARBITRARY_BATCH_MSEC = 200; + + if (m_rebuild_tag == 0) + m_rebuild_tag = g_timeout_add(ARBITRARY_BATCH_MSEC, rebuild_now_static, this); +} + +gboolean RangePlanner::rebuild_now_static(gpointer gself) +{ + auto self = static_cast<RangePlanner*>(gself); + self->m_rebuild_tag = 0; + self->rebuild_now(); + return G_SOURCE_REMOVE; +} + +core::Property<std::pair<DateTime,DateTime>>& RangePlanner::range() +{ + return m_range; +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/planner-upcoming.cpp b/src/planner-upcoming.cpp new file mode 100644 index 0000000..4e5af6f --- /dev/null +++ b/src/planner-upcoming.cpp @@ -0,0 +1,61 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <datetime/planner-upcoming.h> + +namespace unity { +namespace indicator { +namespace datetime { + +/*** +**** +***/ + +UpcomingPlanner::UpcomingPlanner(const std::shared_ptr<RangePlanner>& range_planner, + const DateTime& date_in): + m_range_planner(range_planner) +{ + date().changed().connect([this](const DateTime& dt){ + // set the range to the upcoming month + const auto b = dt.add_full(0, 0, -1, 0, 0, 0); + const auto e = dt.add_full(0, 1, 0, 0, 0, 0); + g_debug("%p setting date range to [%s..%s]", this, b.format("%F %T").c_str(), e.format("%F %T").c_str()); + m_range_planner->range().set(std::pair<DateTime,DateTime>(b,e)); + }); + + date().set(date_in); +} + +core::Property<DateTime>& UpcomingPlanner::date() +{ + return m_date; +} + +core::Property<std::vector<Appointment>>& UpcomingPlanner::appointments() +{ + return m_range_planner->appointments(); +} + +/*** +**** +***/ + +} // namespace datetime +} // namespace indicator +} // namespace unity diff --git a/src/utils.c b/src/utils.c index f4eb53f..c9107ce 100644 --- a/src/utils.c +++ b/src/utils.c @@ -159,6 +159,10 @@ getDateProximity(GDateTime* now, GDateTime* time) gint now_year, now_month, now_day; gint time_year, time_month, time_day; + // did it already happen? + if (g_date_time_difference(time, now) < -G_USEC_PER_SEC) + return DATE_PROXIMITY_FAR; + // does it happen today? g_date_time_get_ymd(now, &now_year, &now_month, &now_day); g_date_time_get_ymd(time, &time_year, &time_month, &time_day); |