diff --git a/core/DisplayMode.vala b/core/DisplayMode.vala new file mode 100644 index 000000000..ee3c2427d --- /dev/null +++ b/core/DisplayMode.vala @@ -0,0 +1,26 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya { + + public enum DisplayMode { + MONTH, + WEEK + } +} diff --git a/core/Model/CalendarModel.vala b/core/Model/CalendarModel.vala index f0981a5d2..f1f7aec7d 100644 --- a/core/Model/CalendarModel.vala +++ b/core/Model/CalendarModel.vala @@ -15,21 +15,21 @@ public class Maya.Model.CalendarModel : Object { /* The data_range is the range of dates for which this model is storing - * data. The month_range is a subset of this range corresponding to the - * calendar month that is being focused on. In summary: + * data. The display_range is a subset of this range corresponding to the + * calendar view that is being focused on (either month or week). In summary: * - * data_range.first_dt <= month_range.first_dt < month_range.last_dt <= data_range.last_dt + * data_range.first_dt <= display_range.first_dt < display_range.last_dt <= data_range.last_dt * * There is no way to set the ranges publicly. They can only be modified by - * changing one of the following properties: month_start, num_weeks, and + * changing one of the following properties: display_start, num_weeks, and * week_starts_on. */ public Util.DateRange data_range { get; private set; } - public Util.DateRange month_range { get; private set; } + public Util.DateRange display_range { get; private set; } public E.SourceRegistry registry { get; private set; } - /* The first day of the month */ - public DateTime month_start { get; set; } + /* The first day to be displayed */ + public DateTime display_start { get; set; } /* The number of weeks to show in this model */ public int num_weeks { get; private set; default = 6; } @@ -49,7 +49,7 @@ public class Maya.Model.CalendarModel : Object { public signal void connected (E.Source source); public signal void error_received (string error); - /* The month_start, num_weeks, or week_starts_on have been changed */ + /* The display_start, num_weeks, or week_starts_on have been changed */ public signal void parameters_changed (); HashTable source_client; @@ -68,12 +68,22 @@ public class Maya.Model.CalendarModel : Object { } private CalendarModel () { - int week_start = Posix.NLTime.FIRST_WEEKDAY.to_string ().data[0]; + int week_start = Util.get_first_weekday (); if (week_start >= 1 && week_start <= 7) { week_starts_on = (GLib.DateWeekday) (week_start - 1); } - this.month_start = Util.get_start_of_month (Settings.SavedState.get_default ().get_page ()); + var display_page = Settings.SavedState.get_default ().get_page (); + switch (Settings.SavedState.get_default ().get_mode ()) { + case Maya.DisplayMode.WEEK: + this.display_start = Util.get_start_of_week (display_page); + this.num_weeks = 1; + break; + + case Maya.DisplayMode.MONTH: + this.display_start = Util.get_start_of_month (display_page); + break; + } compute_ranges (); source_client = new HashTable (str_hash, str_equal); @@ -81,7 +91,7 @@ public class Maya.Model.CalendarModel : Object { source_view = new HashTable (str_hash, str_equal); calendar_trash = new GLib.Queue (); - notify["month-start"].connect (on_parameter_changed); + notify["display-start"].connect (on_parameter_changed); open.begin (); } @@ -221,12 +231,16 @@ public class Maya.Model.CalendarModel : Object { } } + public void change_week (int relative) { + display_start = display_start.add_weeks (relative); + } + public void change_month (int relative) { - month_start = month_start.add_months (relative); + display_start = display_start.add_months (relative); } public void change_year (int relative) { - month_start = month_start.add_years (relative); + display_start = display_start.add_years (relative); } public void load_all_sources () { @@ -284,11 +298,17 @@ public class Maya.Model.CalendarModel : Object { //--- Helper Methods ---// private void compute_ranges () { - Settings.SavedState.get_default ().month_page = month_start.format ("%Y-%m"); - var month_end = month_start.add_full (0, 1, -1); - month_range = new Util.DateRange (month_start, month_end); + Settings.SavedState.get_default ().display_page = display_start.format ("%Y-%m-%d"); + + DateTime display_end; + if (num_weeks > 1) { + display_end = display_start.add_full (0, 1, -1); + } else { + display_end = display_start.add_full (0, 0, 6); + } + display_range = new Util.DateRange (display_start, display_end); - int dow = month_start.get_day_of_week (); + int dow = display_start.get_day_of_week (); int wso = (int) week_starts_on; int offset = 0; @@ -298,9 +318,9 @@ public class Maya.Model.CalendarModel : Object { offset = 7 + dow - wso; } - var data_range_first = month_start.add_days (-offset); + var data_range_first = display_start.add_days (-offset); - dow = month_end.get_day_of_week (); + dow = display_end.get_day_of_week (); wso = (int) (week_starts_on + 6); // WSO must be between 1 and 7 @@ -314,12 +334,12 @@ public class Maya.Model.CalendarModel : Object { else if (wso > dow) offset = wso - dow; - var data_range_last = month_end.add_days (offset); + var data_range_last = display_end.add_days (offset); data_range = new Util.DateRange (data_range_first, data_range_last); num_weeks = data_range.to_list ().size / 7; - debug (@"Date ranges: ($data_range_first <= $month_start < $month_end <= $data_range_last)"); + debug (@"Date ranges: ($data_range_first <= $display_start < $display_end <= $data_range_last)"); } private void load_source (E.Source source) { diff --git a/core/Settings/SavedState.vala b/core/Settings/SavedState.vala index 2682f9ff5..5b0c33ba1 100644 --- a/core/Settings/SavedState.vala +++ b/core/Settings/SavedState.vala @@ -16,6 +16,7 @@ // namespace Maya.Settings { + public class SavedState : Granite.Services.Settings { private static Settings.SavedState? saved_state = null; @@ -25,21 +26,34 @@ namespace Maya.Settings { return saved_state; } - public string month_page { get; set; } + public string display_mode { get; set; } + public string display_page { get; set; } public string selected_day { get; set; } private SavedState () { base ("io.elementary.calendar.savedstate"); } + public Maya.DisplayMode get_mode () { + switch (display_mode) { + case "week": + return Maya.DisplayMode.WEEK; + default: + return Maya.DisplayMode.MONTH; + } + } + public DateTime get_page () { - if (month_page == null) + if (display_page == null) return new DateTime.now_local (); - if (month_page == "") + if (display_page == "") return new DateTime.now_local (); - var numbers = month_page.split ("-", 2); + var numbers = display_page.split ("-", 3); var dt = new DateTime.local (int.parse (numbers[0]), 1, 1, 0, 0, 0); dt = dt.add_months (int.parse (numbers[1]) - 1); + if (numbers.length > 2) { + dt = dt.add_days (int.parse (numbers[2]) - 1); + } return dt; } diff --git a/core/Utils.vala b/core/Utils.vala index eed908d9d..969e131e5 100644 --- a/core/Utils.vala +++ b/core/Utils.vala @@ -162,6 +162,12 @@ namespace Maya.Util { return false; } + public bool is_all_day_event (ICal.Component comp) { + DateTime start, end; + get_local_datetimes_from_icalcomponent (comp, out start, out end); + return is_all_day (start, end); + } + /** * Say if an event lasts all day. */ @@ -182,6 +188,103 @@ namespace Maya.Util { return new DateTime.local (date.get_year (), date.get_month (), 1, 0, 0, 0); } + /** + * TODO: This implementation needs some more work + */ + public int get_days_in_month (DateTime datetime) { + DateMonth month; + + switch (datetime.get_month ()) { + case 1: + month = DateMonth.JANUARY; + break; + case 2: + month = DateMonth.FEBRUARY; + break; + case 3: + month = DateMonth.MARCH; + break; + case 4: + month = DateMonth.APRIL; + break; + case 5: + month = DateMonth.MAY; + break; + case 6: + month = DateMonth.JUNE; + break; + case 7: + month = DateMonth.JULY; + break; + case 8: + month = DateMonth.AUGUST; + break; + case 9: + month = DateMonth.SEPTEMBER; + break; + case 10: + month = DateMonth.OCTOBER; + break; + case 11: + month = DateMonth.NOVEMBER; + break; + case 12: + month = DateMonth.DECEMBER; + break; + default: + month = DateMonth.BAD_MONTH; + break; + } + + // TODO: Make DateYear dynamic: datetime.get_year () + // how do I convert int into ushort ...?! + DateYear year = 2020; + + return month.get_days_in_month (year); + } + + /** + * The implementation might not work on every platform. See GNOME Calendar's + * implementation for a more comprehensive approach if needed: + * https://gitlab.gnome.org/GNOME/gnome-calendar/-/blob/master/src/utils/gcal-utils.c#L301 + */ + public int get_first_weekday () { + return Posix.NLTime.FIRST_WEEKDAY.to_string ().data[0]; + } + + /** + * Retrieves the first day of the week @date is in, at 00:00 + * of the local timezone. + * + * This date is inclusive. + */ + public DateTime get_start_of_week (DateTime date) { + var first_weekday = get_first_weekday (); + var weekday = date.get_day_of_week () % 7; + var n_days_after_week_start = (weekday - first_weekday) % 7; + + var start_of_week = date.add_days (-n_days_after_week_start); + + return new DateTime.local ( + start_of_week.get_year (), + start_of_week.get_month (), + start_of_week.get_day_of_month (), + 0, 0, 0); + } + + + /** + * Retrieves the last day of the week @date is in, at 23:59:59 + * of the local timezone. + * + * Because this date is exclusive, it actually is start of the + * next week. + */ + public DateTime get_end_of_week (DateTime date) { + var week_start = get_start_of_week (date); + return week_start.add_weeks (1); + } + public DateTime strip_time (DateTime datetime) { return datetime.add_full (0, 0, 0, -datetime.get_hour (), -datetime.get_minute (), -datetime.get_second ()); } diff --git a/core/meson.build b/core/meson.build index 87280c33d..4c9362e7d 100644 --- a/core/meson.build +++ b/core/meson.build @@ -17,6 +17,7 @@ core_files = files( 'Model/CalendarModel.vala', 'Settings/SavedState.vala', 'DateRange.vala', + 'DisplayMode.vala', 'Utils.vala', 'GesturesUtils.vala' ) diff --git a/daemon/Daemon.vala b/daemon/Daemon.vala index 961760788..905c83a60 100644 --- a/daemon/Daemon.vala +++ b/daemon/Daemon.vala @@ -47,7 +47,7 @@ namespace Maya { model.events_added.connect (on_events_added); model.events_updated.connect (on_events_updated); model.events_removed.connect (on_events_removed); - model.month_start = Maya.Util.get_start_of_month (new DateTime.now_local ()); + model.display_start = Maya.Util.get_start_of_month (new DateTime.now_local ()); } private void on_events_added (E.Source source, Gee.Collection events) { diff --git a/data/io.elementary.calendar.gschema.xml b/data/io.elementary.calendar.gschema.xml index 58b57cb8d..357f8c5c7 100644 --- a/data/io.elementary.calendar.gschema.xml +++ b/data/io.elementary.calendar.gschema.xml @@ -27,10 +27,15 @@ Whether or not to show weeknumbers. Whether or not to show weeknumbers. - + "" - The current year-month that is shown. - The current year-month that is shown. + The current year-month-day that is shown. + The current year-month-day that is shown. + + + "month" + The calendars display mode. + In which display mode the calendar should be shown ("month", "week"). "" diff --git a/data/maya.gresource.xml b/data/maya.gresource.xml index de5b1cf43..9f0e53606 100644 --- a/data/maya.gresource.xml +++ b/data/maya.gresource.xml @@ -19,5 +19,6 @@ style/Grid.css style/Header.css style/WeekLabels.css + style/WeekView.css diff --git a/data/style/WeekView.css b/data/style/WeekView.css new file mode 100644 index 000000000..67df25080 --- /dev/null +++ b/data/style/WeekView.css @@ -0,0 +1,63 @@ +/* +* Copyright 2020 elementary, Inc. (https://elementary.io) +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY 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 . +*/ + +/* +.week-head { + background-color: green; +} + +.week-body { + background-color: yellow; +} + +.week-sidebar { + background-color: purple; +} + +.weekday-head { + background-color: blue; +} + +.weekday-body { + background-color: red; +}*/ + +.week-header .week-names { + font-size: 10pt; + font-weight: bold; + color: alpha(@theme_fg_color, 0.55); +} + +.week-header .week-dates { + font-size: 16pt; + font-weight: bold; + color: alpha(@theme_fg_color, 0.70); +} + +.week-header .today { + color: @colorAccent; +} + +.week-view .lines { + color: alpha(@theme_fg_color, 0.30); +} + +.week-view .hours { + font-size: 10pt; + color: alpha(@theme_fg_color, 0.8); + padding: 8px 12px; +} diff --git a/src/Application.vala b/src/Application.vala index 31268863a..f11e74b1f 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -68,7 +68,8 @@ namespace Maya { datetime = datetime.add_years ((int)date.get_year () - datetime.get_year ()); datetime = datetime.add_days ((int)date.get_day_of_year () - datetime.get_day_of_year ()); Settings.SavedState.get_default ().selected_day = datetime.format ("%Y-%j"); - Settings.SavedState.get_default ().month_page = datetime.format ("%Y-%m"); + Settings.SavedState.get_default ().display_page = datetime.format ("%Y-%m-%d"); + Settings.SavedState.get_default ().display_mode = "month"; } else { warning ("Invalid date '%s' - Ignoring", Option.show_day); } diff --git a/src/Grid/CalendarView.vala b/src/Grid/CalendarView.vala index 6da6c019c..95d34a118 100644 --- a/src/Grid/CalendarView.vala +++ b/src/Grid/CalendarView.vala @@ -133,8 +133,8 @@ public class Maya.View.CalendarView : Gtk.Grid { var today = Util.strip_time (new DateTime.now_local ()); var calmodel = Model.CalendarModel.get_default (); var start = Util.get_start_of_month (today); - if (!start.equal (calmodel.month_start)) - calmodel.month_start = start; + if (!start.equal (calmodel.display_start)) + calmodel.display_start = start; sync_with_model (); grid.focus_date (today); } @@ -212,11 +212,11 @@ public class Maya.View.CalendarView : Gtk.Grid { header.update_columns (model.week_starts_on); weeks.update (model.data_range.first_dt, model.num_weeks); - grid.set_range (model.data_range, model.month_start); + grid.set_range (model.data_range, model.display_start); // keep focus date on the same day of the month if (selected_date != null) { - var bumpdate = model.month_start.add_days (selected_date.get_day_of_month () - 1); + var bumpdate = model.display_start.add_days (selected_date.get_day_of_month () - 1); grid.focus_date (bumpdate); } diff --git a/src/Grid/Grid.vala b/src/Grid/Grid.vala index 100dff1b1..dae128046 100644 --- a/src/Grid/Grid.vala +++ b/src/Grid/Grid.vala @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY 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 . * @@ -68,8 +68,8 @@ public class Grid : Gtk.Grid { selection_changed (selected_date); Settings.SavedState.get_default ().selected_day = selected_date.format ("%Y-%j"); var calmodel = Maya.Model.CalendarModel.get_default (); - var date_month = selected_date.get_month () - calmodel.month_start.get_month (); - var date_year = selected_date.get_year () - calmodel.month_start.get_year (); + var date_month = selected_date.get_month () - calmodel.display_start.get_month (); + var date_year = selected_date.get_year () - calmodel.display_start.get_year (); if (date_month != 0 || date_year != 0) { calmodel.change_month (date_month); calmodel.change_year (date_year); diff --git a/src/MainWindow.vala b/src/MainWindow.vala index e0e7c9211..87b100fb9 100644 --- a/src/MainWindow.vala +++ b/src/MainWindow.vala @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY 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 . * @@ -20,15 +20,21 @@ */ public class Maya.MainWindow : Gtk.ApplicationWindow { + private Gtk.Stack calview_stack; + private View.WeekView week_view; + private Gtk.Paned hpaned; + public View.CalendarView calview; public const string ACTION_PREFIX = "win."; public const string ACTION_NEW_EVENT = "action_new_event"; public const string ACTION_SHOW_TODAY = "action_show_today"; + public const string ACTION_SHOW_WEEK = "action_show_week"; private const ActionEntry[] ACTION_ENTRIES = { { ACTION_NEW_EVENT, action_new_event }, - { ACTION_SHOW_TODAY, action_show_today } + { ACTION_SHOW_TODAY, action_show_today }, + { ACTION_SHOW_WEEK, action_show_week } }; private uint configure_id; @@ -77,14 +83,23 @@ public class Maya.MainWindow : Gtk.ApplicationWindow { calview = new View.CalendarView (); calview.vexpand = true; - var hpaned = new Gtk.Paned (Gtk.Orientation.HORIZONTAL); + week_view = new View.WeekView (); + + hpaned = new Gtk.Paned (Gtk.Orientation.HORIZONTAL); hpaned.pack1 (calview, true, false); hpaned.pack2 (sidebar, true, false); + calview_stack = new Gtk.Stack (); + calview_stack.transition_type = Gtk.StackTransitionType.CROSSFADE; + calview_stack.expand = true; + + calview_stack.add (hpaned); + calview_stack.add (week_view); + var grid = new Gtk.Grid (); grid.orientation = Gtk.Orientation.VERTICAL; grid.add (infobar); - grid.add (hpaned); + grid.add (calview_stack); add (grid); set_titlebar (headerbar); @@ -121,6 +136,14 @@ public class Maya.MainWindow : Gtk.ApplicationWindow { calview.today (); } + private void action_show_week () { + if (calview_stack.visible_child == hpaned) { + calview_stack.visible_child = week_view; + } else { + calview_stack.visible_child = hpaned; + } + } + private void on_remove (ECal.Component comp) { Model.CalendarModel.get_default ().remove_event (comp.get_data ("source"), comp, ECal.ObjModType.THIS); } diff --git a/src/SimpleWeek/WeekBody.vala b/src/SimpleWeek/WeekBody.vala new file mode 100644 index 000000000..298755648 --- /dev/null +++ b/src/SimpleWeek/WeekBody.vala @@ -0,0 +1,46 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + public class WeekBody : WeekGrid { + + private WeekdayBody[] weekdays; + + construct { + weekdays = new WeekdayBody[7]; + for (var i = 0; i < 7; i++) { + weekdays[i] = new WeekdayBody (); + add_to_weekday_column (weekdays[i], i); + } + + var style_context = get_style_context (); + style_context.add_class ("week-body"); + style_context.add_provider (WeekView.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + + var sidebar_style_context = sidebar.get_style_context (); + sidebar_style_context.add_class ("week-sidebar"); + sidebar_style_context.add_provider (WeekView.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + + public WeekdayBody get_weekday (int i) { + return weekdays[i]; + } + } +} diff --git a/src/SimpleWeek/WeekGrid.vala b/src/SimpleWeek/WeekGrid.vala new file mode 100644 index 000000000..8df8778c7 --- /dev/null +++ b/src/SimpleWeek/WeekGrid.vala @@ -0,0 +1,64 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + public class WeekGrid : Gtk.Box { + + public Gtk.Box sidebar { get; construct; } + private Gtk.Box[] weekday_columns; + + private bool is_ltr; + + construct { + orientation = Gtk.Orientation.HORIZONTAL; + expand = true; + + is_ltr = get_direction () != Gtk.TextDirection.RTL; + + sidebar = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); + sidebar.vexpand = true; + sidebar.hexpand = false; + + if (is_ltr) { + add (sidebar); + } + + weekday_columns = new Gtk.Box[7]; + for (var i = 0; i < 7; i++) { + weekday_columns[i] = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); + weekday_columns[i].expand = true; + + add (weekday_columns[i]); + } + + if (!is_ltr) { + add (sidebar); + } + } + + public void add_to_weekday_column (Gtk.Widget widget, int weekday_column) { + if (is_ltr) { + weekday_columns[weekday_column].add (widget); + } else { + weekday_columns[7 - weekday_column].add (widget); + } + } + } +} diff --git a/src/SimpleWeek/WeekHead.vala b/src/SimpleWeek/WeekHead.vala new file mode 100644 index 000000000..1e94b58eb --- /dev/null +++ b/src/SimpleWeek/WeekHead.vala @@ -0,0 +1,47 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + public class WeekHead : WeekGrid { + + private WeekdayHead[] weekdays; + + construct { + weekdays = new WeekdayHead[7]; + for (var i = 0; i < 7; i++) { + weekdays[i] = new WeekdayHead (); + weekdays[i].title = "Montag"; + add_to_weekday_column (weekdays[i], i); + } + + var style_context = get_style_context (); + style_context.add_class ("week-head"); + style_context.add_provider (WeekView.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + + var sidebar_style_context = sidebar.get_style_context (); + sidebar_style_context.add_class ("week-sidebar"); + sidebar_style_context.add_provider (WeekView.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + + public WeekdayHead get_weekday (int i) { + return weekdays[i]; + } + } +} diff --git a/src/SimpleWeek/WeekView.vala b/src/SimpleWeek/WeekView.vala new file mode 100644 index 000000000..c93e9fd77 --- /dev/null +++ b/src/SimpleWeek/WeekView.vala @@ -0,0 +1,52 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + public class WeekView : Gtk.Box { + + internal static Gtk.CssProvider style_provider; + + static construct { + style_provider = new Gtk.CssProvider (); + style_provider.load_from_resource ("/io/elementary/calendar/WeekView.css"); + } + + private WeekHead head; + private WeekBody body; + + construct { + orientation = Gtk.Orientation.VERTICAL; + homogeneous = false; + + head = new WeekHead (); + body = new WeekBody (); + + var scrolled_window = new Gtk.ScrolledWindow (null, null); + scrolled_window.add (body); + + add (head); + add (scrolled_window); + + var style_context = get_style_context (); + style_context.add_class ("week-view"); + style_context.add_provider (style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + } +} diff --git a/src/SimpleWeek/WeekdayBody.vala b/src/SimpleWeek/WeekdayBody.vala new file mode 100644 index 000000000..c461a03a7 --- /dev/null +++ b/src/SimpleWeek/WeekdayBody.vala @@ -0,0 +1,36 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + public class WeekdayBody : Gtk.Grid { + + construct { + insert_row (48); + + column_homogeneous = false; + row_homogeneous = true; + expand = true; + + var style_context = get_style_context (); + style_context.add_class ("weekday-body"); + style_context.add_provider (WeekView.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + } +} diff --git a/src/SimpleWeek/WeekdayHead.vala b/src/SimpleWeek/WeekdayHead.vala new file mode 100644 index 000000000..cf578fe2c --- /dev/null +++ b/src/SimpleWeek/WeekdayHead.vala @@ -0,0 +1,40 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + public class WeekdayHead : Gtk.Grid { + + public string title { get; set; } + private Gtk.Label title_label; + + construct { + expand = true; + + title_label = new Gtk.Label (null); + add (title_label); + + bind_property ("title", title_label, "label"); + + var style_context = get_style_context (); + style_context.add_class ("weekday-head"); + style_context.add_provider (WeekView.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + } +} diff --git a/src/Week/WeekGrid.vala b/src/Week/WeekGrid.vala new file mode 100644 index 000000000..22a84726b --- /dev/null +++ b/src/Week/WeekGrid.vala @@ -0,0 +1,342 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + /** + * TODO: Documentation + * - https://gitlab.gnome.org/GNOME/gnome-calendar/-/blob/master/src/views/gcal-week-grid.c + */ + public class WeekGrid : Gtk.Container { + + private Gdk.Window event_window; + + private DateTime active_date; + private Maya.Util.DateRange date_range; + + /* + * These fields are "cells" rather than minutes. Each cell + * correspond to 30 minutes. + */ + private int selection_start; + private int selection_end; + private int dnd_cell; + + private int today_column { + get { + DateTime today, week_start; + int days_diff; + + // TODO + + return 3; + } + } + + construct { + set_has_window (false); + + selection_start = -1; + selection_end = -1; + dnd_cell = -1; + + /* Setup the week view as a drag n' drop destination */ + Gtk.drag_dest_set (this, Gtk.DestDefaults.ALL, null, Gdk.DragAction.MOVE); + + var style_context = get_style_context (); + style_context.add_provider (WeekView.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + + public override void realize () { + var parent_window = get_parent_window (); + + set_realized (true); + set_window (parent_window); + + Gtk.Allocation allocation; + get_allocation (out allocation); + + var attributes = Gdk.WindowAttr(); + attributes.window_type = Gdk.WindowType.CHILD; + attributes.wclass = Gdk.WindowWindowClass.INPUT_ONLY; + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + attributes.event_mask = get_events (); + attributes.event_mask |= (Gdk.EventMask.BUTTON_PRESS_MASK | + Gdk.EventMask.BUTTON_RELEASE_MASK | + Gdk.EventMask.BUTTON1_MOTION_MASK | + Gdk.EventMask.POINTER_MOTION_HINT_MASK | + Gdk.EventMask.POINTER_MOTION_MASK | + Gdk.EventMask.ENTER_NOTIFY_MASK | + Gdk.EventMask.LEAVE_NOTIFY_MASK | + Gdk.EventMask.SCROLL_MASK | + Gdk.EventMask.SMOOTH_SCROLL_MASK); + var attributes_mask = (Gdk.WindowAttributesType.X | Gdk.WindowAttributesType.Y); + event_window = new Gdk.Window (parent_window, attributes, attributes_mask); + register_window (event_window); + } + + public override void unrealize () { + if (event_window != null) { + unregister_window (event_window); + event_window.destroy (); + event_window = null; + } + base.unrealize (); + } + + public override void map () { + if (event_window != null) { + event_window.show (); + } + base.map (); + } + + public override void unmap () { + if (event_window != null) { + event_window.hide (); + } + base.unmap (); + } + + public override void size_allocate (Gtk.Allocation allocation) { + DateTime week_start = null; + //RangeTree overlaps; + bool ltr; + double minutes_height; + double column_width; + int i, x, y; + + /* Allocate the widget */ + set_allocation (allocation); + + ltr = get_direction () != Gtk.TextDirection.RTL; + + if (get_realized ()) { + event_window.move_resize (allocation.x, allocation.y, allocation.width, allocation.height); + } + + /* Preliminary calculations */ + minutes_height = allocation.height / WeekUtil.MINUTES_PER_DAY; + column_width = allocation.width / 7.0; + + /* Temporary range tree to hold positioned events' indexes */ + //overlaps = gcal_range_tree_new (); + + //week_start = + + /* + * Iterate through weekdays; we don't have to worry about events that + * jump between days because they're already handled by GcalWeekHeader. + */ + for (i = 0; i < 7; i++) { + // ... + } + } + + public override void get_preferred_height (out int minimum_height, out int natural_height) { + int hours_12_height, hours_24_height, cell_height, height; + + var style_context = get_style_context (); + var state = style_context.get_state (); + + style_context.save (); + style_context.add_class ("hours"); + + Pango.FontDescription font_desc; + style_context.@get (state, "font", out font_desc, null); + var padding = style_context.get_padding (state); + + var pango_context = get_pango_context (); + var pango_layout = new Pango.Layout (pango_context); + pango_layout.set_font_description (font_desc); + + pango_layout.set_text (_("00 AM"), -1); + pango_layout.get_pixel_size (null, out hours_12_height); + + pango_layout.set_text (_("00:00"), -1); + pango_layout.get_pixel_size (null, out hours_24_height); + + cell_height = int.max (hours_12_height, hours_24_height) + padding.top + padding.bottom; + height = cell_height * 48; + + style_context.restore (); + + /* Report the height */ + minimum_height = height; + natural_height = height; + } + + + public override bool draw (Cairo.Context context) { + var style_context = get_style_context (); + var state = get_state_flags (); + var ltr = get_direction () != Gtk.TextDirection.RTL; + + style_context.save (); + style_context.add_class ("lines"); + + var color = style_context.get_color (state); + var padding = style_context.get_padding (state); + + context.set_source_rgba (color.red, color.green, color.blue, color.alpha); + + double x; + int i, width, height, today_column; + + width = get_allocated_width (); + height = get_allocated_height (); + + double column_width = width / 7.0; + double minutes_height = height / WeekUtil.MINUTES_PER_DAY; + + context.set_line_width (0.65); + + /* First, draw the selection */ + if (selection_start != -1 && selection_end != -1) { + int selection_height, column, start, end; + + start = selection_start; + end = selection_end; + + /* Swap cells if needed */ + if (start > end) { + start = start + end; + end = start -end; + start = start - end; + } + + column = start * 30 / WeekUtil.MINUTES_PER_DAY; + selection_height = (end - start + 1) * 30 * (int)minutes_height; + + x = column * column_width; + + style_context.save (); + style_context.set_state (state | Gtk.StateFlags.SELECTED); + + style_context.render_background (context, WeekUtil.aligned (x), Math.round ((start * 30 % WeekUtil.MINUTES_PER_DAY) * minutes_height), column_width, selection_height); + + style_context.restore (); + } + + /* Drag and Drop highlight */ + if (dnd_cell != -1) { + double cell_height; + int column, row; + + cell_height = minutes_height * 30; + column = dnd_cell / (WeekUtil.MINUTES_PER_DAY / 30); + row = dnd_cell - column * 48; + + style_context.render_background (context, column * column_width, row * cell_height, column_width, cell_height); + } + + /* Vertical lines */ + for (i = 0; i < 7; i++) { + if (ltr) { + x = column_width * i; + } else { + x = width - column_width * i; + } + + context.move_to (WeekUtil.aligned (x), 0); + context.rel_line_to (0, height); + } + + /* Horizontal lines */ + for (i = 1; i < 24; i++) { + context.move_to (0, WeekUtil.aligned ((height / 24.0) * i)); + context.rel_line_to (width, 0); + } + + context.stroke (); + + /* Dashed lines between the vertical lines */ + context.set_dash (WeekUtil.dashed, 2); + + for (i = 0; i < 24; i++) { + context.move_to (0, WeekUtil.aligned((height / 24.0) * i + (height / 48.0))); + context.rel_line_to (width, 0); + } + + context.stroke (); + style_context.restore (); + + base.draw (context); + + /* Today column */ + // today_column = get_today_column (GCAL_WEEK_GRID (widget)); + // TODO: if (today_column != -1) + + return false; + } + + public override void add (Gtk.Widget widget) { + if (widget.get_parent () == null) { + widget.set_parent (this); + } + } + + /** + * Puts the given event on the grid. + */ + public void add_event (E.Source source, ECal.Component event) { + critical ("grid.add_event..."); + + /*foreach (var grid_day in data.values) { + if (Util.calcomp_is_on_day (event, grid_day.date)) { + var button = new EventButton (event); + grid_day.add_event_button (button); + } + } */ + } + + /** + * Removes the given event from the grid. + */ + public void remove_event (E.Source source, ECal.Component event) { + critical ("grid.remove_event..."); + /*foreach (var grid_day in data.values) { + grid_day.remove_event (event); + }*/ + } + + /** + * Removes all events from the grid. + */ + public void remove_all_events () { + critical ("grid.remove_all_events..."); + /*foreach (var grid_day in data.values) { + grid_day.clear_events (); + }*/ + } + + public override void remove (Gtk.Widget widget) { + if (widget.get_parent () != null) { + widget.unparent (); + } + } + + /*public override void forall (Gtk.Callback callback) { + // TODO + }*/ + } +} diff --git a/src/Week/WeekHeader.vala b/src/Week/WeekHeader.vala new file mode 100644 index 000000000..d2d72bc14 --- /dev/null +++ b/src/Week/WeekHeader.vala @@ -0,0 +1,300 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + /** + * TODO: Documentation + * - https://gitlab.gnome.org/GNOME/gnome-calendar/-/blob/master/src/views/gcal-week-header.ui + */ + public class WeekHeader : Gtk.Box { + + public Gtk.SizeGroup sidebar_sizegroup { get; construct; } + + private Gtk.ScrolledWindow scrolled_window; + private Gtk.Box top_edge_box; + + private DateTime active_date; + + public WeekHeader (Gtk.SizeGroup sidebar_sizegroup) { + Object ( + sidebar_sizegroup: sidebar_sizegroup + ); + } + + construct { + active_date = new DateTime.now_local (); + + var style_context = get_style_context (); + style_context.add_class ("week-header"); + style_context.add_provider (WeekView.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + + top_edge_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); + sidebar_sizegroup.add_widget (top_edge_box); + add (top_edge_box); + + var grid = new Gtk.Grid (); + grid.column_homogeneous = true; + grid.hexpand = true; + grid.column_spacing = 6; + grid.row_spacing = 2; + grid.margin_start = 6; + + for (int i = 0; i < 7; i++) { + var box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); + grid.attach (box, i, 0); + } + + var viewport = new Gtk.Viewport (null, null); + viewport.shadow_type = Gtk.ShadowType.NONE; + viewport.add (grid); + + scrolled_window = new Gtk.ScrolledWindow (null, null); + scrolled_window.hscrollbar_policy = scrolled_window.vscrollbar_policy = Gtk.PolicyType.NEVER; + scrolled_window.propagate_natural_height = true; + scrolled_window.margin_bottom = 2; + scrolled_window.add (viewport); + + var event_box = new Gtk.EventBox (); + event_box.add (scrolled_window); + + add (event_box); + } + + private int get_weekday_names_height () { + Pango.FontDescription font_desc; + Gtk.Border padding; + int font_height; + int final_height; + + var style_context = get_style_context (); + var state = style_context.get_state (); + + var pango_layout = create_pango_layout ("A"); + + style_context.save (); + style_context.add_class ("week-dates"); + + padding = style_context.get_padding (state); + + style_context.@get (state, "font", out font_desc, null); + pango_layout.set_font_description (font_desc); + pango_layout.get_pixel_size (out font_height, null); + + style_context.restore (); + + final_height = padding.top + font_height + padding.bottom; + + style_context.save (); + style_context.add_class ("week-names"); + + padding = style_context.get_padding (state); + + style_context.@get (state, "font", out font_desc, null); + pango_layout.set_font_description (font_desc); + pango_layout.get_pixel_size (out font_height, null); + + final_height += padding.top + font_height + padding.bottom; + + // TODO: multiply by 2 should not be necessary here :( + return final_height * 2; + } + + + public override void size_allocate (Gtk.Allocation allocation) { + var min_header_height = get_weekday_names_height (); + scrolled_window.margin_top = min_header_height; + + base.size_allocate (allocation); + } + + public override bool draw (Cairo.Context context) { + Pango.FontDescription bold_font; + DateTime week_start, week_end; + Gdk.RGBA color; + + double cell_width; + int i, day_abv_font_height, current_cell, today_column; + int start_x, start_y; + + context.save (); + + /* Fonts and colour selection */ + var style_context = get_style_context (); + var state = style_context.get_state (); + var ltr = get_direction () != Gtk.TextDirection.RTL; + + start_x = ltr ? top_edge_box.get_allocated_width () : 0; + start_y = 0; + + var padding = style_context.get_padding (state); + + Gtk.Allocation alloc; + get_allocation (out alloc); + + if (!ltr) { + alloc.width -= top_edge_box.get_allocated_width (); + } + + color = style_context.get_color (state); + context.set_source_rgba (color.red, color.green, color.blue, color.alpha); + + var pango_layout = Pango.cairo_create_layout (context); + style_context.@get (state, "font", out bold_font, null); + bold_font.set_weight (Pango.Weight.MEDIUM); + pango_layout.set_font_description (bold_font); + + week_start = Maya.Util.get_start_of_week (active_date); + week_end = week_start.add_days (6); + + current_cell = active_date.get_day_of_week () - 1; + current_cell = (7 + current_cell - Util.get_first_weekday ()) % 7; + today_column = 2; // TODO: get_today_column (); + + cell_width = (alloc.width - start_x) / 7.0; + + /* Drag and Drop highlight */ + // TODO: if (self->dnd_cell != -1) + + /* Draw the selection background */ + // TODO: if (self->selection_start != -1 && self->selection_end != -1) + + pango_layout.get_pixel_size (null, out day_abv_font_height); + + for (i = 0; i < 7; i++) { + var day = week_start.add_days (i); + var n_day = day.get_day_of_month (); + var days_in_month = Maya.Util.get_days_in_month (week_start); + + string weekday_abv, weekday; + int font_width, day_num_font_height, day_num_font_baseline; + double x; + + if (n_day > days_in_month) { + n_day = n_day - days_in_month; + } + + /* Draws the date of days in the week */ + var weekday_date = "%d".printf (n_day); + + style_context.save (); + style_context.add_class ("week-dates"); + + style_context.@get (state, "font", out bold_font, null); + + if (i == today_column) { + style_context.add_class ("today"); + } + + pango_layout.set_font_description (bold_font); + pango_layout.set_text (weekday_date, -1); + + pango_layout.get_pixel_size (out font_width, out day_num_font_height); + day_num_font_baseline = pango_layout.get_baseline () / Pango.SCALE; + + if (ltr) { + x = padding.left + cell_width * i + WeekUtil.COLUMN_PADDING + start_x; + } else { + x = alloc.width - (cell_width * i + font_width + WeekUtil.COLUMN_PADDING + start_x); + } + + style_context.render_layout (context, x, day_abv_font_height + padding.bottom + start_y, pango_layout); + style_context.restore (); + + /* Draws the days name */ + weekday = day.format ("%a"); + weekday_abv = weekday.up (); + + style_context.save (); + style_context.add_class ("week-names"); + style_context.@get (state, "font", out bold_font, null); + + if (i == today_column) { + style_context.add_class ("today"); + } + + pango_layout.set_font_description (bold_font); + pango_layout.set_text (weekday_abv, -1); + + pango_layout.get_pixel_size (out font_width, null); + + if (ltr) { + x = padding.left + cell_width * i + WeekUtil.COLUMN_PADDING + start_x; + } else { + x = alloc.width - (cell_width * i + font_width + WeekUtil.COLUMN_PADDING + start_x); + } + + style_context.render_layout (context, x, start_y, pango_layout); + style_context.restore (); + + /* Draws the lines after each day of the week */ + style_context.save (); + style_context.add_class ("lines"); + + color = style_context.get_color (state); + context.set_source_rgba (color.red, color.green, color.blue, color.alpha); + context.set_line_width (0.25); + context.move_to (WeekUtil.aligned (ltr ? (cell_width * i + start_x) : (alloc.width - (cell_width * i + start_x))), day_abv_font_height + padding.bottom + start_y); + context.rel_line_to (0.0, get_allocated_height () - day_abv_font_height - start_y + padding.bottom); + context.stroke (); + + style_context.restore (); + } + + context.restore (); + base.draw (context); + + return false; + } + + // /** + // * Puts the given event on the header. + // */ + // public void add_event (E.Source source, ECal.Component event) { + // critical ("header.add_event..."); + + // /*foreach (var grid_day in data.values) { + // if (Util.calcomp_is_on_day (event, grid_day.date)) { + // var button = new EventButton (event); + // grid_day.add_event_button (button); + // } + // } */ + // } + + // /** + // * Removes the given event from the header. + // */ + // public void remove_event (E.Source source, ECal.Component event) { + // critical ("header.remove_event..."); + // /*foreach (var grid_day in data.values) { + // grid_day.remove_event (event); + // }*/ + // } + + // /** + // * Removes all events from the header. + // */ + // public void remove_all_events () { + // critical ("header.remove_all_events..."); + // /*foreach (var grid_day in data.values) { + // grid_day.clear_events (); + // }*/ + // } + } +} diff --git a/src/Week/WeekSidebar.vala b/src/Week/WeekSidebar.vala new file mode 100644 index 000000000..ac837f2e1 --- /dev/null +++ b/src/Week/WeekSidebar.vala @@ -0,0 +1,130 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + /** + * TODO: Documentation + * - https://gitlab.gnome.org/GNOME/gnome-calendar/-/blob/master/src/views/gcal-week-view.c + */ + public class WeekSidebar : Gtk.DrawingArea { + + public Gtk.SizeGroup sidebar_sizegroup { get; construct; } + + public WeekSidebar (Gtk.SizeGroup sidebar_sizegroup) { + Object ( + sidebar_sizegroup: sidebar_sizegroup + ); + } + + construct { + height_request = 2568; + sidebar_sizegroup.add_widget (this); + get_style_context ().add_provider (WeekView.style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + } + + public override bool draw (Cairo.Context context) { + Gdk.RGBA color; + int i; + //var time_format = ?? + + var style_context = get_style_context (); + var state = style_context.get_state (); + var ltr = get_direction () != Gtk.TextDirection.RTL; + + style_context.save (); + style_context.add_class ("hours"); + + color = style_context.get_color (state); + var padding = style_context.get_padding (state); + + Pango.FontDescription font_desc; + style_context.@get (state, "font", out font_desc, null); + + var pango_layout = Pango.cairo_create_layout (context); + pango_layout.set_font_description (font_desc); + + context.set_source_rgba (color.red, color.green, color.blue, color.alpha); + + /* Gets the size of the widget */ + var width = get_allocated_width (); + var height = get_allocated_height (); + + /* Draws the hours in the sidebar */ + for (i = 0; i < 24; i++) { + string hours; + + // TODO: Honor User Time Format (12/24h): + // if (time_format == GCAL_TIME_FORMAT_24H): + hours = "%02d:00".printf(i); + // else: + /*hours = "%d %s".printf ( + i % 12 == 0 ? 12 : i % 12, + i >= 12 ? _("PM") : _("AM") + );*/ + + pango_layout.set_text (hours, -1); + + int font_width; + pango_layout.get_pixel_size (out font_width, null); + + style_context.render_layout ( + context, + ltr ? padding.left : width - font_width - padding.right, + (height / 24) * i + padding.top, + pango_layout + ); + } + + style_context.restore (); + style_context.save (); + + style_context.add_class ("lines"); + color = style_context.get_color (state); + + context.set_source_rgba (color.red, color.green, color.blue, color.alpha); + context.set_line_width (0.65); + + if (!ltr) { + context.move_to (0.5, 0); + context.rel_line_to (0, height); + } + + /* Draws the horizontal complete lines */ + for (i = 1; i < 24; i++) { + context.move_to (0, (height / 24) * i + 0.4); + context.rel_line_to (width, 0); + } + + context.stroke (); + context.set_dash (WeekUtil.dashed, 2); + + /* Draws the horizontal dashed lines */ + for (i = 0; i < 24; i++) { + context.move_to (0, (height / 24) * i + (height / 48) + 0.4); + context.rel_line_to (width, 0); + } + + context.stroke (); + style_context.restore (); + + return false; + } + } +} diff --git a/src/Week/WeekUtil.vala b/src/Week/WeekUtil.vala new file mode 100644 index 000000000..1d0ed4f73 --- /dev/null +++ b/src/Week/WeekUtil.vala @@ -0,0 +1,30 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View.WeekUtil { + + internal const int MINUTES_PER_DAY = 1440; + internal const int MAX_MINUTES = (7 * MINUTES_PER_DAY); + internal const double dashed[] = { 5.0, 6.0 }; + internal const int COLUMN_PADDING = 6; + + internal double aligned (double x) { + return Math.round (x) + 0.5; + } +} diff --git a/src/Week/WeekView.vala b/src/Week/WeekView.vala new file mode 100644 index 000000000..50a33b4da --- /dev/null +++ b/src/Week/WeekView.vala @@ -0,0 +1,133 @@ +/*- + * Copyright (c) 2020 elementary, Inc. (https://elementary.io) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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 . + * + * Authored by: Marco Betschart + */ + +namespace Maya.View { + + /** + * TODO: Documentation + * - https://gitlab.gnome.org/GNOME/gnome-calendar/-/blob/master/src/views/gcal-week-view.ui + */ + public class WeekView : Gtk.Box { + + internal static Gtk.CssProvider style_provider; + + private WeekSidebar sidebar; + private Gtk.SizeGroup sidebar_sizegroup; + + private WeekGrid grid; + private WeekHeader header; + + static construct { + style_provider = new Gtk.CssProvider (); + style_provider.load_from_resource ("/io/elementary/calendar/WeekView.css"); + } + + construct { + orientation = Gtk.Orientation.VERTICAL; + + var style_context = get_style_context (); + style_context.add_class ("week-view"); + style_context.add_provider (style_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + + sidebar_sizegroup = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL); + sidebar = new WeekSidebar (sidebar_sizegroup); + + grid = new WeekGrid (); + grid.expand = true; + + var box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); + box.add (sidebar); + box.add (grid); + + var scrolled_window = new Gtk.ScrolledWindow (null, null); + scrolled_window.expand = true; + scrolled_window.add (box); + + header = new WeekHeader (sidebar_sizegroup); + + add (header); + add (scrolled_window); + + update_hours_sidebar_size (); + } + + private void update_hours_sidebar_size () { + int hours_12_width, hours_24_width, sidebar_width; + int hours_12_height, hours_24_height, cell_height; + + var style_context = get_style_context (); + var state = style_context.get_state (); + + style_context.save (); + style_context.add_class ("hours"); + + Pango.FontDescription font_desc; + style_context.@get (state, "font", out font_desc, null); + var padding = style_context.get_padding (state); + + var pango_context = get_pango_context (); + var pango_layout = new Pango.Layout (pango_context); + pango_layout.set_font_description (font_desc); + + pango_layout.set_text (_("00 AM"), -1); + pango_layout.get_pixel_size (out hours_12_width, out hours_12_height); + + pango_layout.set_text (_("00:00"), -1); + pango_layout.get_pixel_size (out hours_24_width, out hours_24_height); + + sidebar_width = int.max (hours_12_width, hours_24_width) + padding.left + padding.right; + cell_height = int.max (hours_12_height, hours_24_height) + padding.top + padding.bottom; + + style_context.restore (); + + /* Update the size requests */ + sidebar.set_size_request (sidebar_width, 48 * cell_height); + } + + // /* Render new event in the view */ + // private void add_event (E.Source source, ECal.Component event) { + // unowned ICal.Component comp = event.get_icalcomponent (); + + // if (Maya.Util.is_multiday_event (comp) || Maya.Util.is_all_day_event (comp)) { + // header.add_event (source, event); + // } else { + // grid.add_event (source, event); + // } + // } + + // /* Update the event in the view */ + // private void update_event (E.Source source, ECal.Component event) { + // remove_event (source, event); + // add_event (source, event); + // } + + // /* Remove event from the view */ + // private void remove_event (E.Source source, ECal.Component event) { + // header.remove_event (source, event); + // grid.remove_event (source, event); + // } + + // /* Remove all events from the view */ + // private void remove_all_events () { + // header.remove_all_events (); + // grid.remove_all_events (); + // } + } +} + diff --git a/src/Widgets/HeaderBar.vala b/src/Widgets/HeaderBar.vala index b11bf5296..1f58e1afa 100644 --- a/src/Widgets/HeaderBar.vala +++ b/src/Widgets/HeaderBar.vala @@ -6,12 +6,12 @@ * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY 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 . * @@ -41,6 +41,13 @@ namespace Maya.View { _("Create a new event") ); + var button_week_view = new Gtk.Button.from_icon_name ("office-calendar", Gtk.IconSize.LARGE_TOOLBAR); + button_week_view.action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_SHOW_WEEK; + button_week_view.tooltip_markup = Granite.markup_accel_tooltip ( + application_instance.get_accels_for_action (button_week_view.action_name), + _("Show week view") + ); + var button_today = new Gtk.Button.from_icon_name ("calendar-go-today", Gtk.IconSize.LARGE_TOOLBAR); button_today.action_name = MainWindow.ACTION_PREFIX + MainWindow.ACTION_SHOW_TODAY; button_today.tooltip_markup = Granite.markup_accel_tooltip ( @@ -58,7 +65,7 @@ namespace Maya.View { month_switcher = new Widgets.DateSwitcher (10); year_switcher = new Widgets.DateSwitcher (-1); var calmodel = Model.CalendarModel.get_default (); - set_switcher_date (calmodel.month_start); + set_switcher_date (calmodel.display_start); var contractor = new Widgets.ContractorButtonWithMenu (_("Export or Share the default Calendar")); @@ -67,6 +74,7 @@ namespace Maya.View { title_grid.add (button_today); title_grid.add (month_switcher); title_grid.add (year_switcher); + title_grid.add (button_week_view); var spinner = new Widgets.DynamicSpinner (); @@ -81,7 +89,7 @@ namespace Maya.View { year_switcher.left_clicked.connect (() => Model.CalendarModel.get_default ().change_year (-1)); year_switcher.right_clicked.connect (() => Model.CalendarModel.get_default ().change_year (1)); calmodel.parameters_changed.connect (() => { - set_switcher_date (calmodel.month_start); + set_switcher_date (calmodel.display_start); }); } diff --git a/src/meson.build b/src/meson.build index 7e529db2d..1a46ac675 100644 --- a/src/meson.build +++ b/src/meson.build @@ -27,6 +27,11 @@ calendar_files = files( 'SourceDialog/SourceDialog.vala', 'SourceDialog/SourceItem.vala', 'SourceDialog/SourceSelector.vala', + 'Week/WeekGrid.vala', + 'Week/WeekHeader.vala', + 'Week/WeekSidebar.vala', + 'Week/WeekUtil.vala', + 'Week/WeekView.vala', 'Widgets/AgendaEventRow.vala', 'Widgets/CalendarButton.vala', 'Widgets/ContractorButtonWithMenu.vala',