From e47d49aca4da2d97c06acccc967abdf5698b044d Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Fri, 18 Mar 2016 10:17:55 -0500 Subject: get event selection up-to-date with the spec, including showing in-progress events. add unit tests to cover event priority and display order. --- include/datetime/date-time.h | 2 + include/datetime/menu.h | 6 +++ src/date-time.cpp | 10 ++++ src/menu.cpp | 111 ++++++++++++++++++++++++++++++++++++------- tests/CMakeLists.txt | 1 + tests/print-to.h | 10 ++++ 6 files changed, 122 insertions(+), 18 deletions(-) diff --git a/include/datetime/date-time.h b/include/datetime/date-time.h index fc83388..845716d 100644 --- a/include/datetime/date-time.h +++ b/include/datetime/date-time.h @@ -68,7 +68,9 @@ public: int64_t to_unix() const; bool operator<(const DateTime& that) const; + bool operator>(const DateTime& that) const; bool operator<=(const DateTime& that) const; + bool operator>=(const DateTime& that) const; bool operator!=(const DateTime& that) const; bool operator==(const DateTime& that) const; int64_t operator- (const DateTime& that) const; diff --git a/include/datetime/menu.h b/include/datetime/menu.h index acd9ed8..0074ea5 100644 --- a/include/datetime/menu.h +++ b/include/datetime/menu.h @@ -21,6 +21,7 @@ #define INDICATOR_DATETIME_MENU_H #include +#include #include #include // std::shared_ptr @@ -49,6 +50,11 @@ public: Profile profile() const; GMenuModel* menu_model(); + static std::vector get_display_appointments( + const std::vector&, + const DateTime& start, + unsigned int max_items=5); + protected: Menu (Profile profile_in, const std::string& name_in); virtual ~Menu() =default; diff --git a/src/date-time.cpp b/src/date-time.cpp index 169426c..911fb7a 100644 --- a/src/date-time.cpp +++ b/src/date-time.cpp @@ -245,11 +245,21 @@ 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 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 d19ad73..0cd3f9b 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include extern "C" @@ -58,11 +60,90 @@ GMenuModel* Menu::menu_model() return G_MENU_MODEL(m_menu); } +/** + * To avoid a giant menu on the PC, and to avoid pushing lower menu items + * off-screen on the phone, the menu should show the + * next five calendar events, if any. + * + * The list might include multiple occurrences of the same event (bug 1515821). + */ +std::vector +Menu::get_display_appointments(const std::vector& appointments_in, + const DateTime& now, + unsigned int max_items) +{ + std::vector appointments; + std::copy_if(appointments_in.begin(), + appointments_in.end(), + std::back_inserter(appointments), + [now](const Appointment& a){return a.end >= now;}); + + if (appointments.size() > max_items) + { + const auto next_minute = now.add_full(0,0,0,0,1,-now.seconds()); + const auto start_of_day = now.start_of_day(); + const auto end_of_day = now.end_of_day(); + + /* + * If there are more than five, the events shown should be, in order of priority: + * 1. any events that start or end (bug 1329048) after the current minute today; + * 2. any full-day events that span all of today (bug 1302004); + * 3. any events that start or end tomorrow; + * 4. any events that start or end the day after tomorrow; and so on. + */ + auto compare = [next_minute, start_of_day, end_of_day]( + const Appointment& a, + const Appointment& b) + { + const bool a_later_today = (a.begin >= next_minute) || (a.end <= end_of_day); + const bool b_later_today = (b.begin >= next_minute) || (b.end <= end_of_day); + if (a_later_today != b_later_today) + return a_later_today; + + const bool a_full_day_today = (a.begin <= start_of_day) && (end_of_day <= a.end); + const bool b_full_day_today = (b.begin <= start_of_day) && (end_of_day <= b.end); + if (a_full_day_today != b_full_day_today) + return a_full_day_today; + + const bool a_after_today = (a.begin > end_of_day) || (a.end > end_of_day); + const bool b_after_today = (a.begin > end_of_day) || (a.end > end_of_day); + if (a_after_today != b_after_today) + return a_after_today; + if (a.begin != b.begin) + return a.begin < b.begin; + if (b.end != b.end) + return a.end < b.end; + + return false; + }; + std::sort(appointments.begin(), appointments.end(), compare); + appointments.resize(max_items); + } + + /* + * However, the display order should be the reverse: full-day events + * first (since they start first), part-day events afterward in + * chronological order. If multiple events have exactly the same start+end + * time, they should be sorted alphabetically. + */ + auto compare = [](const Appointment& a, const Appointment& b) + { + if (a.begin != b.begin) + return a.begin < b.begin; + + if (a.end != b.end) + return a.end < b.end; + + return a.summary < b.summary; + }; + std::sort(appointments.begin(), appointments.end(), compare); + return appointments; +} + /**** ***** ****/ - #define ALARM_ICON_NAME "alarm-clock" #define CALENDAR_ICON_NAME "calendar" @@ -150,25 +231,19 @@ protected: void update_upcoming() { - // The usual case is on desktop (and /only/ case on phone) - // is that we're looking at the current date and want to see - // "the next five calendar events, if any." - // - // However on the Desktop when the user clicks onto a different - // calendar date, show the next five calendar events starting - // from the beginning of that clicked day. - DateTime begin; + // The usual case is to show events germane to the current time. + // However when the user clicks onto a different calendar date, + // we pick events starting from the beginning of that clicked day. const auto now = m_state->clock->localtime(); const auto calendar_day = m_state->calendar_month->month().get(); - if ((profile() == Desktop) && !DateTime::is_same_day(now, calendar_day)) - begin = calendar_day.start_of_day(); - else - begin = now.start_of_minute(); - - std::vector upcoming; - for(const auto& a : m_state->calendar_upcoming->appointments().get()) - if (begin <= a.begin) - upcoming.push_back(a); + const auto begin = DateTime::is_same_day(now, calendar_day) + ? now.start_of_minute() + : calendar_day.start_of_day(); + + auto upcoming = get_display_appointments( + m_state->calendar_upcoming->appointments().get(), + begin + ); if (m_upcoming != upcoming) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bec0010..aa69ca1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -59,6 +59,7 @@ add_test_by_name(test-exporter) add_test_by_name(test-formatter) add_test_by_name(test-live-actions) add_test_by_name(test-locations) +add_test_by_name(test-menu-appointments) add_test_by_name(test-menus) add_test_by_name(test-planner) add_test_by_name(test-settings) diff --git a/tests/print-to.h b/tests/print-to.h index 19367ac..652da52 100644 --- a/tests/print-to.h +++ b/tests/print-to.h @@ -21,6 +21,7 @@ #define INDICATOR_DATETIME_TESTS_PRINT_TO #include +#include #include @@ -71,6 +72,15 @@ PrintTo(const Appointment& appointment, std::ostream* os) *os << '}'; } +void +PrintTo(const std::vector& appointments, std::ostream* os) +{ + *os << '{'; + for (const auto& appointment : appointments) + PrintTo(appointment, os); + *os << '}'; +} + } // namespace datetime } // namespace indicator } // namespace ayatana -- cgit v1.2.3