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',