summary refs log tree commit diff
path: root/pkgs/desktops
diff options
context:
space:
mode:
authorFrederik Rietdijk <fridh@fridh.nl>2020-04-25 07:53:37 +0200
committerFrederik Rietdijk <fridh@fridh.nl>2020-04-25 07:53:37 +0200
commitbb10352242dceb863069797bb92131944b0ac62f (patch)
treed3f5ceea6723e3d6c901652146e41ffe45100c9b /pkgs/desktops
parent7fb1ff90031c59cbc9ef79224516a74bc138c7a6 (diff)
parent5c70040db2dd2123f7728f1bdcd40d5018de0ef9 (diff)
downloadnixpkgs-bb10352242dceb863069797bb92131944b0ac62f.tar
nixpkgs-bb10352242dceb863069797bb92131944b0ac62f.tar.gz
nixpkgs-bb10352242dceb863069797bb92131944b0ac62f.tar.bz2
nixpkgs-bb10352242dceb863069797bb92131944b0ac62f.tar.lz
nixpkgs-bb10352242dceb863069797bb92131944b0ac62f.tar.xz
nixpkgs-bb10352242dceb863069797bb92131944b0ac62f.tar.zst
nixpkgs-bb10352242dceb863069797bb92131944b0ac62f.zip
Merge staging-next into staging
Diffstat (limited to 'pkgs/desktops')
-rw-r--r--pkgs/desktops/gnome-3/apps/file-roller/default.nix4
-rw-r--r--pkgs/desktops/gnome-3/extensions/dash-to-dock/default.nix23
-rw-r--r--pkgs/desktops/gnome-3/find-latest-version.py50
-rw-r--r--pkgs/desktops/gnome-3/update.nix16
-rw-r--r--pkgs/desktops/pantheon/apps/switchboard-plugs/pantheon-shell/default.nix4
-rw-r--r--pkgs/desktops/pantheon/desktop/elementary-greeter/default.nix3
-rw-r--r--pkgs/desktops/pantheon/desktop/wingpanel-indicators/datetime/207.patch4726
-rw-r--r--pkgs/desktops/pantheon/desktop/wingpanel-indicators/datetime/default.nix11
-rw-r--r--pkgs/desktops/pantheon/desktop/wingpanel/default.nix7
9 files changed, 4810 insertions, 34 deletions
diff --git a/pkgs/desktops/gnome-3/apps/file-roller/default.nix b/pkgs/desktops/gnome-3/apps/file-roller/default.nix
index 1243d5fa5cb..a735d9f446d 100644
--- a/pkgs/desktops/gnome-3/apps/file-roller/default.nix
+++ b/pkgs/desktops/gnome-3/apps/file-roller/default.nix
@@ -3,11 +3,11 @@
 
 stdenv.mkDerivation rec {
   pname = "file-roller";
-  version = "3.36.1";
+  version = "3.36.2";
 
   src = fetchurl {
     url = "mirror://gnome/sources/file-roller/${stdenv.lib.versions.majorMinor version}/${pname}-${version}.tar.xz";
-    sha256 = "0p22jxcagamvp08xfglz4cz1sp8w4p101npw0ggrkhh7vm8yb9bh";
+    sha256 = "1lkb0m8ys13sy3b6c1kj3cqrqf5d1dqvhbp8spz8v9yjv3d7z3r6";
   };
 
   LANG = "en_US.UTF-8"; # postinstall.py
diff --git a/pkgs/desktops/gnome-3/extensions/dash-to-dock/default.nix b/pkgs/desktops/gnome-3/extensions/dash-to-dock/default.nix
index d508222188b..3784f109900 100644
--- a/pkgs/desktops/gnome-3/extensions/dash-to-dock/default.nix
+++ b/pkgs/desktops/gnome-3/extensions/dash-to-dock/default.nix
@@ -1,27 +1,34 @@
-{ stdenv, fetchFromGitHub, glib, gettext }:
+{ stdenv
+, fetchFromGitHub
+, glib
+, gettext
+}:
 
 stdenv.mkDerivation rec {
   pname = "gnome-shell-dash-to-dock-unstable";
-  version = "2020-03-19";
+  version = "2020-04-20";
 
   src = fetchFromGitHub {
     owner = "micheleg";
     repo = "dash-to-dock";
     # rev = "extensions.gnome.org-v" + version;
-    rev = "8c94a8d6db47ebc1273e690f4e0ba5e592f7f268";
-    sha256 = "7nNfxAINqOIJCgYXYaPck2EJ1IOmzt6AkfDFknZ8GaI=";
+    rev = "1788f31b049b622f78d0e65c56bef76169022ca9";
+    sha256 = "M3tlRbQ1PjKvNrKNtg0+CBEtzLSFQYauXJXQojdkHuk=";
   };
 
   nativeBuildInputs = [
-    glib gettext
+    glib
+    gettext
   ];
 
-  makeFlags = [ "INSTALLBASE=$(out)/share/gnome-shell/extensions" ];
+  makeFlags = [
+    "INSTALLBASE=${placeholder "out"}/share/gnome-shell/extensions"
+  ];
 
   meta = with stdenv.lib; {
     description = "A dock for the Gnome Shell";
-    license = licenses.gpl2;
-    maintainers = with maintainers; [ eperuffo ];
     homepage = "https://micheleg.github.io/dash-to-dock/";
+    license = licenses.gpl2;
+    maintainers = with maintainers; [ eperuffo jtojnar ];
   };
 }
diff --git a/pkgs/desktops/gnome-3/find-latest-version.py b/pkgs/desktops/gnome-3/find-latest-version.py
index b0359f79969..ad80af24bcb 100644
--- a/pkgs/desktops/gnome-3/find-latest-version.py
+++ b/pkgs/desktops/gnome-3/find-latest-version.py
@@ -1,10 +1,13 @@
 import argparse
+import math
 import json
 import requests
 import sys
 
+
 def version_to_list(version):
-    return list(map(int, version.split('.')))
+    return list(map(int, version.split(".")))
+
 
 def odd_unstable(version_str, selected):
     version = version_to_list(version_str)
@@ -14,47 +17,58 @@ def odd_unstable(version_str, selected):
     even = version[1] % 2 == 0
     prerelease = (version[1] >= 90 and version[1] < 100) or (version[1] >= 900 and version[1] < 1000)
     stable = even and not prerelease
-    if selected == 'stable':
+    if selected == "stable":
         return stable
     else:
         return True
 
+
 def no_policy(version, selected):
     return True
 
+
 version_policies = {
-    'odd-unstable': odd_unstable,
-    'none': no_policy,
+    "odd-unstable": odd_unstable,
+    "none": no_policy,
 }
 
-def make_version_policy(version_predicate, selected):
-    return lambda version: version_predicate(version, selected)
 
-parser = argparse.ArgumentParser(description='Find latest version for a GNOME package by crawling their release server.')
-parser.add_argument('package-name', help='Name of the directory in https://ftp.gnome.org/pub/GNOME/sources/ containing the package.')
-parser.add_argument('version-policy', help='Policy determining which versions are considered stable. For most GNOME packages, odd minor versions are unstable but there are exceptions.', choices=version_policies.keys(), nargs='?', default='odd-unstable')
-parser.add_argument('requested-release', help='Most of the time, we will want to update to stable version but sometimes it is useful to test.', choices=['stable', 'unstable'], nargs='?', default='stable')
+def make_version_policy(version_predicate, selected, upper_bound):
+    if not upper_bound:
+        upper_bound = [math.inf, math.inf]
+    else:
+        upper_bound = version_to_list(upper_bound)
+
+    return lambda version: version_predicate(version, selected) and version_to_list(version) < upper_bound
+
+
+parser = argparse.ArgumentParser(description="Find latest version for a GNOME package by crawling their release server.")
+parser.add_argument("package-name", help="Name of the directory in https://ftp.gnome.org/pub/GNOME/sources/ containing the package.")
+parser.add_argument("version-policy", help="Policy determining which versions are considered stable. For most GNOME packages, odd minor versions are unstable but there are exceptions.", choices=version_policies.keys(), nargs="?", default="odd-unstable")
+parser.add_argument("requested-release", help="Most of the time, we will want to update to stable version but sometimes it is useful to test.", choices=["stable", "unstable"], nargs="?", default="stable")
+parser.add_argument("--upper-bound", dest="upper-bound", help="Only look for versions older than this one (useful for pinning dependencies).")
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     args = parser.parse_args()
 
-    package_name = getattr(args, 'package-name')
-    requested_release = getattr(args, 'requested-release')
-    version_predicate = version_policies[getattr(args, 'version-policy')]
-    version_policy = make_version_policy(version_predicate, requested_release)
+    package_name = getattr(args, "package-name")
+    requested_release = getattr(args, "requested-release")
+    upper_bound = getattr(args, "upper-bound")
+    version_predicate = version_policies[getattr(args, "version-policy")]
+    version_policy = make_version_policy(version_predicate, requested_release, upper_bound)
 
     # The structure of cache.json: https://gitlab.gnome.org/Infrastructure/sysadmin-bin/blob/master/ftpadmin#L762
-    cache = json.loads(requests.get('https://ftp.gnome.org/pub/GNOME/sources/{}/cache.json'.format(package_name)).text)
+    cache = json.loads(requests.get(f"https://ftp.gnome.org/pub/GNOME/sources/{package_name}/cache.json").text)
     if type(cache) != list or cache[0] != 4:
-        print('Unknown format of cache.json file.', file=sys.stderr)
+        print("Unknown format of cache.json file.", file=sys.stderr)
         sys.exit(1)
 
     versions = cache[2][package_name]
     versions = sorted(filter(version_policy, versions), key=version_to_list)
 
     if len(versions) == 0:
-        print('No versions matched.', file=sys.stderr)
+        print("No versions matched.", file=sys.stderr)
         sys.exit(1)
 
     print(versions[-1])
diff --git a/pkgs/desktops/gnome-3/update.nix b/pkgs/desktops/gnome-3/update.nix
index f42b6723950..555504086a4 100644
--- a/pkgs/desktops/gnome-3/update.nix
+++ b/pkgs/desktops/gnome-3/update.nix
@@ -1,8 +1,18 @@
-{ stdenv, lib, writeScript, python3, common-updater-scripts }:
-{ packageName, attrPath ? packageName, versionPolicy ? "odd-unstable" }:
+{ stdenv, pkgs, lib, writeScript, python3, common-updater-scripts }:
+{ packageName, attrPath ? packageName, versionPolicy ? "odd-unstable", freeze ? false }:
 
 let
   python = python3.withPackages (p: [ p.requests ]);
+  upperBoundFlag =
+    let
+      package = lib.getAttrFromPath (lib.splitString "." attrPath) pkgs;
+      packageVersion = lib.getVersion package;
+      versionComponents = lib.versions.splitVersion packageVersion;
+      minorVersion = lib.versions.minor packageVersion;
+      minorAvailable = builtins.length versionComponents > 1 && builtins.match "[0-9]+" minorVersion != null;
+      nextMinor = builtins.fromJSON minorVersion + 1;
+      upperBound = "${lib.versions.major packageVersion}.${builtins.toString nextMinor}";
+    in lib.optionalString (minorAvailable && freeze) ''--upper-bound="${upperBound}"'';
   updateScript = writeScript "gnome-update-script" ''
     #!${stdenv.shell}
     set -o errexit
@@ -10,7 +20,7 @@ let
     attr_path="$2"
     version_policy="$3"
     PATH=${lib.makeBinPath [ common-updater-scripts python ]}
-    latest_tag=$(python "${./find-latest-version.py}" "$package_name" "$version_policy" "stable")
+    latest_tag=$(python "${./find-latest-version.py}" "$package_name" "$version_policy" "stable" ${upperBoundFlag})
     update-source-version "$attr_path" "$latest_tag"
   '';
 in [ updateScript packageName attrPath versionPolicy ]
diff --git a/pkgs/desktops/pantheon/apps/switchboard-plugs/pantheon-shell/default.nix b/pkgs/desktops/pantheon/apps/switchboard-plugs/pantheon-shell/default.nix
index c4487525954..a70fee63094 100644
--- a/pkgs/desktops/pantheon/apps/switchboard-plugs/pantheon-shell/default.nix
+++ b/pkgs/desktops/pantheon/apps/switchboard-plugs/pantheon-shell/default.nix
@@ -4,13 +4,13 @@
 
 stdenv.mkDerivation rec {
   pname = "switchboard-plug-pantheon-shell";
-  version = "2.8.3";
+  version = "2.8.4";
 
   src = fetchFromGitHub {
     owner = "elementary";
     repo = pname;
     rev = version;
-    sha256 = "0ypyppxx51l3r3fgxrvjdwnz33lpbfh1bf27fww9fx9520wixnx8";
+    sha256 = "1nnsv745inbdqk3xnbcaqmj87vr3kzh5hazbh8v3ib33cpi7wy88";
   };
 
   passthru = {
diff --git a/pkgs/desktops/pantheon/desktop/elementary-greeter/default.nix b/pkgs/desktops/pantheon/desktop/elementary-greeter/default.nix
index 432f7c3ac5f..64c4509280a 100644
--- a/pkgs/desktops/pantheon/desktop/elementary-greeter/default.nix
+++ b/pkgs/desktops/pantheon/desktop/elementary-greeter/default.nix
@@ -103,6 +103,9 @@ stdenv.mkDerivation rec {
 
       # for the compositor
       --prefix PATH : "$out/bin"
+
+      # the theme is hardcoded
+      --prefix XDG_DATA_DIRS : "${elementary-gtk-theme}/share"
     )
   '';
 
diff --git a/pkgs/desktops/pantheon/desktop/wingpanel-indicators/datetime/207.patch b/pkgs/desktops/pantheon/desktop/wingpanel-indicators/datetime/207.patch
new file mode 100644
index 00000000000..c4d6d8574a5
--- /dev/null
+++ b/pkgs/desktops/pantheon/desktop/wingpanel-indicators/datetime/207.patch
@@ -0,0 +1,4726 @@
+From 20228e34bf97f67b1dd542a22e92cd90f0db5c72 Mon Sep 17 00:00:00 2001
+From: Dirli <litandrej85@gmail.com>
+Date: Thu, 9 Apr 2020 16:30:16 +0300
+Subject: [PATCH 1/8] added a single namespace
+
+---
+ meson.build                                   |  14 +-
+ src/DateIterator.vala                         |  82 -----
+ src/DateRange.vala                            |  66 ----
+ src/Indicator.vala                            | 341 +++++++++---------
+ .../calendar => Models}/CalendarModel.vala    |   4 +-
+ src/Services/TimeManager.vala                 | 230 ++++++------
+ src/Util/DateIterator.vala                    |  84 +++++
+ src/Util/DateRange.vala                       |  68 ++++
+ src/{Widgets/calendar => Util}/Util.vala      |   8 +-
+ src/Widgets/CalendarView.vala                 | 185 ++++++++++
+ src/Widgets/EventRow.vala                     | 184 +++++-----
+ src/Widgets/{calendar => }/Grid.vala          |  12 +-
+ src/Widgets/GridDay.vala                      | 180 +++++++++
+ src/Widgets/PanelLabel.vala                   |  80 ++--
+ src/Widgets/calendar/CalendarView.vala        | 183 ----------
+ src/Widgets/calendar/GridDay.vala             | 178 ---------
+ 16 files changed, 957 insertions(+), 942 deletions(-)
+ delete mode 100644 src/DateIterator.vala
+ delete mode 100644 src/DateRange.vala
+ rename src/{Widgets/calendar => Models}/CalendarModel.vala (99%)
+ create mode 100644 src/Util/DateIterator.vala
+ create mode 100644 src/Util/DateRange.vala
+ rename src/{Widgets/calendar => Util}/Util.vala (96%)
+ create mode 100644 src/Widgets/CalendarView.vala
+ rename src/Widgets/{calendar => }/Grid.vala (95%)
+ create mode 100644 src/Widgets/GridDay.vala
+ delete mode 100644 src/Widgets/calendar/CalendarView.vala
+ delete mode 100644 src/Widgets/calendar/GridDay.vala
+
+diff --git a/meson.build b/meson.build
+index 2555723..b44c5bd 100644
+--- a/meson.build
++++ b/meson.build
+@@ -39,16 +39,16 @@ endif
+ shared_module(
+     meson.project_name(),
+     gresource,
+-    'src/DateIterator.vala',
+-    'src/DateRange.vala',
+     'src/Indicator.vala',
++    'src/Util/DateIterator.vala',
++    'src/Util/DateRange.vala',
++    'src/Util/Util.vala',
++    'src/Models/CalendarModel.vala',
++    'src/Widgets/CalendarView.vala',
+     'src/Widgets/EventRow.vala',
++    'src/Widgets/Grid.vala',
++    'src/Widgets/GridDay.vala',
+     'src/Widgets/PanelLabel.vala',
+-    'src/Widgets/calendar/CalendarModel.vala',
+-    'src/Widgets/calendar/CalendarView.vala',
+-    'src/Widgets/calendar/Grid.vala',
+-    'src/Widgets/calendar/GridDay.vala',
+-    'src/Widgets/calendar/Util.vala',
+     'src/Services/TimeManager.vala',
+     dependencies: [
+         dependency('glib-2.0'),
+diff --git a/src/DateIterator.vala b/src/DateIterator.vala
+deleted file mode 100644
+index 961895b..0000000
+--- a/src/DateIterator.vala
++++ /dev/null
+@@ -1,82 +0,0 @@
+-/*
+- * Copyright 2011-2018 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 2 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, write to the
+- * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+- * Boston, MA 02110-1301 USA.
+- *
+- * Authored by: Corentin Noël <corentin@elementaryos.org>
+- */
+-
+-public class Util.DateIterator : Object, Gee.Traversable<GLib.DateTime>, Gee.Iterator<GLib.DateTime> {
+-    public GLib.DateTime current { get; construct set; }
+-    public Util.DateRange range { get; construct; }
+-
+-    // Required by Gee.Iterator
+-    public bool valid {
+-        get {
+-            return true;
+-        }
+-    }
+-
+-    // Required by Gee.Iterator
+-    public bool read_only {
+-        get {
+-            return false;
+-        }
+-    }
+-
+-    public DateIterator (Util.DateRange range) {
+-        Object (
+-            range: range,
+-            current: range.first_dt.add_days (-1)
+-        );
+-    }
+-
+-    public bool @foreach (Gee.ForallFunc<GLib.DateTime> f) {
+-        var element = range.first_dt;
+-
+-        while (element.compare (range.last_dt) < 0) {
+-            if (f (element) == false) {
+-                return false;
+-            }
+-
+-            element = element.add_days (1);
+-        }
+-
+-        return true;
+-    }
+-
+-    public bool next () {
+-        if (!has_next ()) {
+-            return false;
+-        }
+-
+-        current = this.current.add_days (1);
+-
+-        return true;
+-    }
+-
+-    public bool has_next () {
+-        return current.compare (range.last_dt) < 0;
+-    }
+-
+-    public new GLib.DateTime get () {
+-        return current;
+-    }
+-
+-    public void remove () {
+-        assert_not_reached ();
+-    }
+-}
+diff --git a/src/DateRange.vala b/src/DateRange.vala
+deleted file mode 100644
+index 08e4c00..0000000
+--- a/src/DateRange.vala
++++ /dev/null
+@@ -1,66 +0,0 @@
+-/*
+- * Copyright 2011-2019 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 2 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, write to the
+- * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+- * Boston, MA 02110-1301 USA.
+- *
+- * Authored by: Corentin Noël <corentin@elementaryos.org>
+- */
+-
+-/* Represents date range from 'first' to 'last' inclusive */
+-public class Util.DateRange : Object, Gee.Traversable<GLib.DateTime>, Gee.Iterable<GLib.DateTime> {
+-    public GLib.DateTime first_dt { get; construct; }
+-    public GLib.DateTime last_dt { get; construct; }
+-
+-    public bool @foreach (Gee.ForallFunc<GLib.DateTime> f) {
+-        foreach (var date in this) {
+-            if (f (date) == false) {
+-                return false;
+-            }
+-        }
+-
+-        return true;
+-    }
+-
+-    public DateRange (GLib.DateTime first, GLib.DateTime last) {
+-        Object (
+-            first_dt: first,
+-            last_dt: last
+-        );
+-    }
+-
+-    public bool equals (DateRange other) {
+-        return (first_dt == other.first_dt && last_dt == other.last_dt);
+-    }
+-
+-    public Gee.Iterator<GLib.DateTime> iterator () {
+-        return new DateIterator (this);
+-    }
+-
+-    public Gee.List<GLib.DateTime> to_list () {
+-        var list = new Gee.ArrayList<GLib.DateTime> ((Gee.EqualDataFunc<GLib.DateTime>? )datetime_equal_func);
+-
+-        foreach (var date in this) {
+-            list.add (date);
+-        }
+-
+-        return list;
+-    }
+-
+-    /* Returns true if 'a' and 'b' are the same GLib.DateTime */
+-    private bool datetime_equal_func (GLib.DateTime a, GLib.DateTime b) {
+-        return a.equal (b);
+-    }
+-}
+diff --git a/src/Indicator.vala b/src/Indicator.vala
+index 77aa35c..b712c12 100644
+--- a/src/Indicator.vala
++++ b/src/Indicator.vala
+@@ -17,218 +17,219 @@
+  * Boston, MA 02110-1301 USA.
+  */
+ 
+-public class DateTime.Indicator : Wingpanel.Indicator {
+-    public static GLib.Settings settings;
+-
+-    private Widgets.PanelLabel panel_label;
+-    private Gtk.Grid main_grid;
+-    private Widgets.CalendarView calendar;
+-    private Gtk.ListBox event_listbox;
+-    private uint update_events_idle_source = 0;
+-
+-    public Indicator () {
+-        Object (
+-            code_name: Wingpanel.Indicator.DATETIME,
+-            display_name: _("Date & Time"),
+-            description: _("The date and time indicator")
+-        );
+-    }
+-
+-    static construct {
+-        settings = new GLib.Settings ("io.elementary.desktop.wingpanel.datetime");
+-    }
++namespace DateTimeIndicator {
++    public class Indicator : Wingpanel.Indicator {
++        public static GLib.Settings settings;
++
++        private Widgets.PanelLabel panel_label;
++        private Gtk.Grid main_grid;
++        private Widgets.CalendarView calendar;
++        private Gtk.ListBox event_listbox;
++        private uint update_events_idle_source = 0;
++
++        public Indicator () {
++            Object (
++                code_name: Wingpanel.Indicator.DATETIME,
++                display_name: _("Date & Time"),
++                description: _("The date and time indicator")
++            );
++        }
+ 
+-    construct {
+-        visible = true;
+-    }
++        static construct {
++            settings = new GLib.Settings ("io.elementary.desktop.wingpanel.datetime");
++        }
+ 
+-    public override Gtk.Widget get_display_widget () {
+-        if (panel_label == null) {
+-            panel_label = new Widgets.PanelLabel ();
++        construct {
++            visible = true;
+         }
+ 
+-        return panel_label;
+-    }
++        public override Gtk.Widget get_display_widget () {
++            if (panel_label == null) {
++                panel_label = new Widgets.PanelLabel ();
++            }
+ 
+-    public override Gtk.Widget? get_widget () {
+-        if (main_grid == null) {
+-            calendar = new Widgets.CalendarView ();
+-            calendar.margin_bottom = 6;
+-
+-            var placeholder_label = new Gtk.Label (_("No Events on This Day"));
+-            placeholder_label.wrap = true;
+-            placeholder_label.wrap_mode = Pango.WrapMode.WORD;
+-            placeholder_label.margin_start = 12;
+-            placeholder_label.margin_end = 12;
+-            placeholder_label.max_width_chars = 20;
+-            placeholder_label.justify = Gtk.Justification.CENTER;
+-            placeholder_label.show_all ();
+-
+-            var placeholder_style_context = placeholder_label.get_style_context ();
+-            placeholder_style_context.add_class (Gtk.STYLE_CLASS_DIM_LABEL);
+-            placeholder_style_context.add_class (Granite.STYLE_CLASS_H3_LABEL);
+-
+-            event_listbox = new Gtk.ListBox ();
+-            event_listbox.selection_mode = Gtk.SelectionMode.NONE;
+-            event_listbox.set_header_func (header_update_func);
+-            event_listbox.set_placeholder (placeholder_label);
+-            event_listbox.set_sort_func (sort_function);
+-
+-            var scrolled_window = new Gtk.ScrolledWindow (null, null);
+-            scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER;
+-            scrolled_window.add (event_listbox);
+-
+-            var settings_button = new Gtk.ModelButton ();
+-            settings_button.text = _("Date & Time Settings…");
+-
+-            main_grid = new Gtk.Grid ();
+-            main_grid.margin_top = 12;
+-            main_grid.attach (calendar, 0, 0);
+-            main_grid.attach (new Gtk.Separator (Gtk.Orientation.VERTICAL), 1, 0);
+-            main_grid.attach (scrolled_window, 2, 0);
+-            main_grid.attach (new Wingpanel.Widgets.Separator (), 0, 2, 3);
+-            main_grid.attach (settings_button, 0, 3, 3);
+-
+-            var size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
+-            size_group.add_widget (calendar);
+-            size_group.add_widget (event_listbox);
+-
+-            calendar.day_double_click.connect (() => {
+-                close ();
+-            });
++            return panel_label;
++        }
+ 
+-            calendar.selection_changed.connect ((date) => {
+-                idle_update_events ();
+-            });
++        public override Gtk.Widget? get_widget () {
++            if (main_grid == null) {
++                calendar = new Widgets.CalendarView ();
++                calendar.margin_bottom = 6;
++
++                var placeholder_label = new Gtk.Label (_("No Events on This Day"));
++                placeholder_label.wrap = true;
++                placeholder_label.wrap_mode = Pango.WrapMode.WORD;
++                placeholder_label.margin_start = 12;
++                placeholder_label.margin_end = 12;
++                placeholder_label.max_width_chars = 20;
++                placeholder_label.justify = Gtk.Justification.CENTER;
++                placeholder_label.show_all ();
++
++                var placeholder_style_context = placeholder_label.get_style_context ();
++                placeholder_style_context.add_class (Gtk.STYLE_CLASS_DIM_LABEL);
++                placeholder_style_context.add_class (Granite.STYLE_CLASS_H3_LABEL);
++
++                event_listbox = new Gtk.ListBox ();
++                event_listbox.selection_mode = Gtk.SelectionMode.NONE;
++                event_listbox.set_header_func (header_update_func);
++                event_listbox.set_placeholder (placeholder_label);
++                event_listbox.set_sort_func (sort_function);
++
++                var scrolled_window = new Gtk.ScrolledWindow (null, null);
++                scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER;
++                scrolled_window.add (event_listbox);
++
++                var settings_button = new Gtk.ModelButton ();
++                settings_button.text = _("Date & Time Settings…");
++
++                main_grid = new Gtk.Grid ();
++                main_grid.margin_top = 12;
++                main_grid.attach (calendar, 0, 0);
++                main_grid.attach (new Gtk.Separator (Gtk.Orientation.VERTICAL), 1, 0);
++                main_grid.attach (scrolled_window, 2, 0);
++                main_grid.attach (new Wingpanel.Widgets.Separator (), 0, 2, 3);
++                main_grid.attach (settings_button, 0, 3, 3);
++
++                var size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
++                size_group.add_widget (calendar);
++                size_group.add_widget (event_listbox);
++
++                calendar.day_double_click.connect (() => {
++                    close ();
++                });
++
++                calendar.selection_changed.connect ((date) => {
++                    idle_update_events ();
++                });
++
++                event_listbox.row_activated.connect ((row) => {
++                    calendar.show_date_in_maya (((EventRow) row).date);
++                    close ();
++                });
++
++                settings_button.clicked.connect (() => {
++                    try {
++                        AppInfo.launch_default_for_uri ("settings://time", null);
++                    } catch (Error e) {
++                        warning ("Failed to open time and date settings: %s", e.message);
++                    }
++                });
++            }
+ 
+-            event_listbox.row_activated.connect ((row) => {
+-                calendar.show_date_in_maya (((DateTime.EventRow) row).date);
+-                close ();
+-            });
++            return main_grid;
++        }
+ 
+-            settings_button.clicked.connect (() => {
+-                try {
+-                    AppInfo.launch_default_for_uri ("settings://time", null);
+-                } catch (Error e) {
+-                    warning ("Failed to open time and date settings: %s", e.message);
++        private void header_update_func (Gtk.ListBoxRow lbrow, Gtk.ListBoxRow? lbbefore) {
++            var row = (EventRow) lbrow;
++            if (lbbefore != null) {
++                var before = (EventRow) lbbefore;
++                if (row.is_allday == before.is_allday) {
++                    row.set_header (null);
++                    return;
+                 }
+-            });
+-        }
+ 
+-        return main_grid;
+-    }
++                if (row.is_allday != before.is_allday) {
++                    var header_label = new Granite.HeaderLabel (_("During the Day"));
++                    header_label.margin_start = header_label.margin_end = 6;
+ 
+-    private void header_update_func (Gtk.ListBoxRow lbrow, Gtk.ListBoxRow? lbbefore) {
+-        var row = (DateTime.EventRow) lbrow;
+-        if (lbbefore != null) {
+-            var before = (DateTime.EventRow) lbbefore;
+-            if (row.is_allday == before.is_allday) {
+-                row.set_header (null);
++                    row.set_header (header_label);
++                    return;
++                }
++            } else {
++                if (row.is_allday) {
++                    var allday_header = new Granite.HeaderLabel (_("All Day"));
++                    allday_header.margin_start = allday_header.margin_end = 6;
++
++                    row.set_header (allday_header);
++                }
+                 return;
+             }
++        }
+ 
+-            if (row.is_allday != before.is_allday) {
+-                var header_label = new Granite.HeaderLabel (_("During the Day"));
+-                header_label.margin_start = header_label.margin_end = 6;
++        [CCode (instance_pos = -1)]
++        private int sort_function (Gtk.ListBoxRow child1, Gtk.ListBoxRow child2) {
++            var e1 = (EventRow) child1;
++            var e2 = (EventRow) child2;
+ 
+-                row.set_header (header_label);
+-                return;
++            if (e1.start_time.compare (e2.start_time) != 0) {
++                return e1.start_time.compare (e2.start_time);
+             }
+-        } else {
+-            if (row.is_allday) {
+-                var allday_header = new Granite.HeaderLabel (_("All Day"));
+-                allday_header.margin_start = allday_header.margin_end = 6;
+ 
+-                row.set_header (allday_header);
++            // If they have the same date, sort them wholeday first
++            if (e1.is_allday) {
++                return -1;
++            } else if (e2.is_allday) {
++                return 1;
+             }
+-            return;
+-        }
+-    }
+-
+-    [CCode (instance_pos = -1)]
+-    private int sort_function (Gtk.ListBoxRow child1, Gtk.ListBoxRow child2) {
+-        var e1 = (EventRow) child1;
+-        var e2 = (EventRow) child2;
+ 
+-        if (e1.start_time.compare (e2.start_time) != 0) {
+-            return e1.start_time.compare (e2.start_time);
++            return 0;
+         }
+ 
+-        // If they have the same date, sort them wholeday first
+-        if (e1.is_allday) {
+-            return -1;
+-        } else if (e2.is_allday) {
+-            return 1;
++        private void update_events_model (E.Source source, Gee.Collection<ECal.Component> events) {
++            idle_update_events ();
+         }
+ 
+-        return 0;
+-    }
+-
+-    private void update_events_model (E.Source source, Gee.Collection<ECal.Component> events) {
+-        idle_update_events ();
+-    }
++        private void idle_update_events () {
++            if (update_events_idle_source > 0) {
++                GLib.Source.remove (update_events_idle_source);
++            }
+ 
+-    private void idle_update_events () {
+-        if (update_events_idle_source > 0) {
+-            GLib.Source.remove (update_events_idle_source);
++            update_events_idle_source = GLib.Idle.add (update_events);
+         }
+ 
+-        update_events_idle_source = GLib.Idle.add (update_events);
+-    }
+-
+-    private bool update_events () {
+-        foreach (unowned Gtk.Widget widget in event_listbox.get_children ()) {
+-            widget.destroy ();
+-        }
++        private bool update_events () {
++            foreach (unowned Gtk.Widget widget in event_listbox.get_children ()) {
++                widget.destroy ();
++            }
+ 
+-        if (calendar.selected_date == null) {
+-            update_events_idle_source = 0;
+-            return GLib.Source.REMOVE;
+-        }
++            if (calendar.selected_date == null) {
++                update_events_idle_source = 0;
++                return GLib.Source.REMOVE;
++            }
+ 
+-        var date = calendar.selected_date;
++            var date = calendar.selected_date;
+ 
+-        var model = Widgets.CalendarModel.get_default ();
++            var model = Models.CalendarModel.get_default ();
+ 
+-        var events_on_day = new Gee.TreeMap<string, DateTime.EventRow> ();
++            var events_on_day = new Gee.TreeMap<string, EventRow> ();
+ 
+-        model.source_events.@foreach ((source, component_map) => {
+-            foreach (var comp in component_map.get_values ()) {
+-                if (Util.calcomp_is_on_day (comp, date)) {
+-                    unowned ICal.Component ical = comp.get_icalcomponent ();
+-                    var event_uid = ical.get_uid ();
+-                    if (!events_on_day.has_key (event_uid)) {
+-                        events_on_day[event_uid] = new DateTime.EventRow (date, ical, source);
++            model.source_events.@foreach ((source, component_map) => {
++                foreach (var comp in component_map.get_values ()) {
++                    if (Util.calcomp_is_on_day (comp, date)) {
++                        unowned ICal.Component ical = comp.get_icalcomponent ();
++                        var event_uid = ical.get_uid ();
++                        if (!events_on_day.has_key (event_uid)) {
++                            events_on_day[event_uid] = new EventRow (date, ical, source);
+ 
+-                        event_listbox.add (events_on_day[event_uid]);
++                            event_listbox.add (events_on_day[event_uid]);
++                        }
+                     }
+                 }
+-            }
+-        });
++            });
+ 
+-        event_listbox.show_all ();
+-        update_events_idle_source = 0;
+-        return GLib.Source.REMOVE;
+-    }
++            event_listbox.show_all ();
++            update_events_idle_source = 0;
++            return GLib.Source.REMOVE;
++        }
+ 
+-    public override void opened () {
+-        calendar.show_today ();
++        public override void opened () {
++            calendar.show_today ();
+ 
+-        Widgets.CalendarModel.get_default ().events_added.connect (update_events_model);
+-        Widgets.CalendarModel.get_default ().events_updated.connect (update_events_model);
+-        Widgets.CalendarModel.get_default ().events_removed.connect (update_events_model);
+-    }
++            Models.CalendarModel.get_default ().events_added.connect (update_events_model);
++            Models.CalendarModel.get_default ().events_updated.connect (update_events_model);
++            Models.CalendarModel.get_default ().events_removed.connect (update_events_model);
++        }
+ 
+-    public override void closed () {
+-        Widgets.CalendarModel.get_default ().events_added.disconnect (update_events_model);
+-        Widgets.CalendarModel.get_default ().events_updated.disconnect (update_events_model);
+-        Widgets.CalendarModel.get_default ().events_removed.disconnect (update_events_model);
++        public override void closed () {
++            Models.CalendarModel.get_default ().events_added.disconnect (update_events_model);
++            Models.CalendarModel.get_default ().events_updated.disconnect (update_events_model);
++            Models.CalendarModel.get_default ().events_removed.disconnect (update_events_model);
++        }
+     }
+ }
+-
+ public Wingpanel.Indicator get_indicator (Module module) {
+     debug ("Activating DateTime Indicator");
+-    var indicator = new DateTime.Indicator ();
++    var indicator = new DateTimeIndicator.Indicator ();
+ 
+     return indicator;
+ }
+diff --git a/src/Widgets/calendar/CalendarModel.vala b/src/Models/CalendarModel.vala
+similarity index 99%
+rename from src/Widgets/calendar/CalendarModel.vala
+rename to src/Models/CalendarModel.vala
+index 7602303..965b93e 100644
+--- a/src/Widgets/calendar/CalendarModel.vala
++++ b/src/Models/CalendarModel.vala
+@@ -15,8 +15,8 @@
+  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+  */
+ 
+-namespace DateTime.Widgets {
+-    public class CalendarModel : Object {
++namespace DateTimeIndicator {
++    public class Models.CalendarModel : Object {
+         /* The data_range is the range of dates for which this model is storing
+          * data.
+          *
+diff --git a/src/Services/TimeManager.vala b/src/Services/TimeManager.vala
+index b68f158..5baa136 100644
+--- a/src/Services/TimeManager.vala
++++ b/src/Services/TimeManager.vala
+@@ -32,153 +32,155 @@ interface FDO.Accounts : Object {
+     public abstract string find_user_by_name (string username) throws GLib.Error;
+ }
+ 
+-public class DateTime.Services.TimeManager : Gtk.Calendar {
+-    private static TimeManager? instance = null;
++namespace DateTimeIndicator {
++    public class Services.TimeManager : Gtk.Calendar {
++        private static TimeManager? instance = null;
+ 
+-    public signal void minute_changed ();
++        public signal void minute_changed ();
+ 
+-    private GLib.DateTime? current_time = null;
+-    private uint timeout_id = 0;
+-    private Manager? manager = null;
++        private GLib.DateTime? current_time = null;
++        private uint timeout_id = 0;
++        private Manager? manager = null;
+ 
+-    public bool clock_show_seconds { get; set; }
+-    public bool is_12h { get; set; }
++        public bool clock_show_seconds { get; set; }
++        public bool is_12h { get; set; }
+ 
+-    public TimeManager () {
+-        update_current_time ();
+-
+-        if (current_time == null) {
+-            return;
+-        }
++        public TimeManager () {
++            update_current_time ();
+ 
+-        add_timeout ();
+-        try {
+-            var clock_settings = new GLib.Settings ("io.elementary.desktop.wingpanel.datetime");
+-            clock_settings.bind ("clock-show-seconds", this, "clock-show-seconds", SettingsBindFlags.DEFAULT);
++            if (current_time == null) {
++                return;
++            }
+ 
+-            notify["clock-show-seconds"].connect (() => {
+-                add_timeout ();
+-            });
++            add_timeout ();
++            try {
++                var clock_settings = new GLib.Settings ("io.elementary.desktop.wingpanel.datetime");
++                clock_settings.bind ("clock-show-seconds", this, "clock-show-seconds", SettingsBindFlags.DEFAULT);
+ 
+-            // Listen for the D-BUS server that controls time settings
+-            Bus.watch_name (BusType.SYSTEM, "org.freedesktop.timedate1", BusNameWatcherFlags.NONE, on_watch, on_unwatch);
+-            // Listen for the signal that is fired when waking up from sleep, then update time
+-            manager = Bus.get_proxy_sync (BusType.SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1");
+-            manager.prepare_for_sleep.connect ((sleeping) => {
+-                if (!sleeping) {
+-                    update_current_time ();
+-                    minute_changed ();
++                notify["clock-show-seconds"].connect (() => {
+                     add_timeout ();
+-                }
+-            });
+-        } catch (Error e) {
+-            warning (e.message);
++                });
++
++                // Listen for the D-BUS server that controls time settings
++                Bus.watch_name (BusType.SYSTEM, "org.freedesktop.timedate1", BusNameWatcherFlags.NONE, on_watch, on_unwatch);
++                // Listen for the signal that is fired when waking up from sleep, then update time
++                manager = Bus.get_proxy_sync (BusType.SYSTEM, "org.freedesktop.login1", "/org/freedesktop/login1");
++                manager.prepare_for_sleep.connect ((sleeping) => {
++                    if (!sleeping) {
++                        update_current_time ();
++                        minute_changed ();
++                        add_timeout ();
++                    }
++                });
++            } catch (Error e) {
++                warning (e.message);
++            }
+         }
+-    }
+-
+-    construct {
+-        setup_time_format.begin ();
+-    }
+ 
+-    private async void setup_time_format () {
+-        try {
+-            var accounts_service = yield GLib.Bus.get_proxy<FDO.Accounts> (GLib.BusType.SYSTEM,
+-                                                                           "org.freedesktop.Accounts",
+-                                                                           "/org/freedesktop/Accounts");
+-            var user_path = accounts_service.find_user_by_name (GLib.Environment.get_user_name ());
+-
+-            var greeter_act = yield GLib.Bus.get_proxy<Pantheon.AccountsService> (GLib.BusType.SYSTEM,
+-                                                    "org.freedesktop.Accounts",
+-                                                    user_path,
+-                                                    GLib.DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
+-            is_12h = ("12h" in greeter_act.time_format);
+-            ((GLib.DBusProxy) greeter_act).g_properties_changed.connect ((changed_properties, invalidated_properties) => {
+-                if (changed_properties.lookup_value ("TimeFormat", GLib.VariantType.STRING) != null) {
+-                    is_12h = ("12h" in greeter_act.time_format);
+-                }
+-            });
+-        } catch (Error e) {
+-            critical (e.message);
+-            // Connect to the GSettings instead
+-            var clock_settings = new GLib.Settings ("org.gnome.desktop.interface");
+-            clock_settings.changed["clock-format"].connect (() => {
+-                is_12h = ("12h" in clock_settings.get_string ("clock-format"));
+-            });
+-
+-            is_12h = ("12h" in clock_settings.get_string ("clock-format"));
++        construct {
++            setup_time_format.begin ();
+         }
+-    }
+ 
+-    private void on_watch (DBusConnection conn) {
+-        // Start updating the time display quicker because someone is changing settings
+-        add_timeout (true);
+-    }
++        private async void setup_time_format () {
++            try {
++                var accounts_service = yield GLib.Bus.get_proxy<FDO.Accounts> (GLib.BusType.SYSTEM,
++                                                                               "org.freedesktop.Accounts",
++                                                                               "/org/freedesktop/Accounts");
++                var user_path = accounts_service.find_user_by_name (GLib.Environment.get_user_name ());
++
++                var greeter_act = yield GLib.Bus.get_proxy<Pantheon.AccountsService> (GLib.BusType.SYSTEM,
++                                                        "org.freedesktop.Accounts",
++                                                        user_path,
++                                                        GLib.DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
++                is_12h = ("12h" in greeter_act.time_format);
++                ((GLib.DBusProxy) greeter_act).g_properties_changed.connect ((changed_properties, invalidated_properties) => {
++                    if (changed_properties.lookup_value ("TimeFormat", GLib.VariantType.STRING) != null) {
++                        is_12h = ("12h" in greeter_act.time_format);
++                    }
++                });
++            } catch (Error e) {
++                critical (e.message);
++                // Connect to the GSettings instead
++                var clock_settings = new GLib.Settings ("org.gnome.desktop.interface");
++                clock_settings.changed["clock-format"].connect (() => {
++                    is_12h = ("12h" in clock_settings.get_string ("clock-format"));
++                });
+ 
+-    private void on_unwatch (DBusConnection conn) {
+-        // Stop updating the time display quicker
+-        add_timeout (false);
+-    }
++                is_12h = ("12h" in clock_settings.get_string ("clock-format"));
++            }
++        }
+ 
+-    private void add_timeout (bool update_fast = false) {
+-        uint interval;
+-        if (update_fast || clock_show_seconds) {
+-            interval = 500;
+-        } else {
+-            interval = calculate_time_until_next_minute ();
++        private void on_watch (DBusConnection conn) {
++            // Start updating the time display quicker because someone is changing settings
++            add_timeout (true);
+         }
+ 
+-        if (timeout_id > 0) {
+-            Source.remove (timeout_id);
++        private void on_unwatch (DBusConnection conn) {
++            // Stop updating the time display quicker
++            add_timeout (false);
+         }
+ 
+-        timeout_id = Timeout.add (interval, () => {
+-            update_current_time ();
+-            minute_changed ();
+-            add_timeout (update_fast);
++        private void add_timeout (bool update_fast = false) {
++            uint interval;
++            if (update_fast || clock_show_seconds) {
++                interval = 500;
++            } else {
++                interval = calculate_time_until_next_minute ();
++            }
+ 
+-            return false;
+-        });
+-    }
++            if (timeout_id > 0) {
++                Source.remove (timeout_id);
++            }
+ 
+-    public string format (string format) {
+-        if (current_time == null) {
+-            return "undef";
++            timeout_id = Timeout.add (interval, () => {
++                update_current_time ();
++                minute_changed ();
++                add_timeout (update_fast);
++
++                return false;
++            });
+         }
+ 
+-        return current_time.format (format);
+-    }
++        public string format (string format) {
++            if (current_time == null) {
++                return "undef";
++            }
+ 
+-    public GLib.DateTime get_current_time () {
+-        return current_time;
+-    }
++            return current_time.format (format);
++        }
+ 
+-    private void update_current_time () {
+-        var local_time = new GLib.DateTime.now_local ();
++        public GLib.DateTime get_current_time () {
++            return current_time;
++        }
+ 
+-        if (local_time == null) {
+-            critical ("Can't get the local time.");
++        private void update_current_time () {
++            var local_time = new GLib.DateTime.now_local ();
+ 
+-            return;
+-        }
++            if (local_time == null) {
++                critical ("Can't get the local time.");
+ 
+-        current_time = local_time;
+-    }
++                return;
++            }
+ 
+-    private uint calculate_time_until_next_minute () {
+-        if (current_time == null) {
+-            return 60 * 1000;
++            current_time = local_time;
+         }
+ 
+-        var seconds_until_next_minute = 60 - (current_time.to_unix () % 60);
++        private uint calculate_time_until_next_minute () {
++            if (current_time == null) {
++                return 60 * 1000;
++            }
+ 
+-        return (uint)seconds_until_next_minute * 1000;
+-    }
++            var seconds_until_next_minute = 60 - (current_time.to_unix () % 60);
+ 
+-    public static TimeManager get_default () {
+-        if (instance == null) {
+-            instance = new TimeManager ();
++            return (uint)seconds_until_next_minute * 1000;
+         }
+ 
+-        return instance;
++        public static TimeManager get_default () {
++            if (instance == null) {
++                instance = new TimeManager ();
++            }
++
++            return instance;
++        }
+     }
+ }
+diff --git a/src/Util/DateIterator.vala b/src/Util/DateIterator.vala
+new file mode 100644
+index 0000000..c2c771e
+--- /dev/null
++++ b/src/Util/DateIterator.vala
+@@ -0,0 +1,84 @@
++/*
++ * Copyright 2011-2018 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 2 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, write to the
++ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
++ * Boston, MA 02110-1301 USA.
++ *
++ * Authored by: Corentin Noël <corentin@elementaryos.org>
++ */
++
++namespace DateTimeIndicator {
++    public class Util.DateIterator : Object, Gee.Traversable<GLib.DateTime>, Gee.Iterator<GLib.DateTime> {
++        public GLib.DateTime current { get; construct set; }
++        public Util.DateRange range { get; construct; }
++
++        // Required by Gee.Iterator
++        public bool valid {
++            get {
++                return true;
++            }
++        }
++
++        // Required by Gee.Iterator
++        public bool read_only {
++            get {
++                return false;
++            }
++        }
++
++        public DateIterator (Util.DateRange range) {
++            Object (
++                range: range,
++                current: range.first_dt.add_days (-1)
++            );
++        }
++
++        public bool @foreach (Gee.ForallFunc<GLib.DateTime> f) {
++            var element = range.first_dt;
++
++            while (element.compare (range.last_dt) < 0) {
++                if (f (element) == false) {
++                    return false;
++                }
++
++                element = element.add_days (1);
++            }
++
++            return true;
++        }
++
++        public bool next () {
++            if (!has_next ()) {
++                return false;
++            }
++
++            current = this.current.add_days (1);
++
++            return true;
++        }
++
++        public bool has_next () {
++            return current.compare (range.last_dt) < 0;
++        }
++
++        public new GLib.DateTime get () {
++            return current;
++        }
++
++        public void remove () {
++            assert_not_reached ();
++        }
++    }
++}
+diff --git a/src/Util/DateRange.vala b/src/Util/DateRange.vala
+new file mode 100644
+index 0000000..82da7c8
+--- /dev/null
++++ b/src/Util/DateRange.vala
+@@ -0,0 +1,68 @@
++/*
++ * Copyright 2011-2019 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 2 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, write to the
++ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
++ * Boston, MA 02110-1301 USA.
++ *
++ * Authored by: Corentin Noël <corentin@elementaryos.org>
++ */
++
++namespace DateTimeIndicator {
++/* Represents date range from 'first' to 'last' inclusive */
++    public class Util.DateRange : Object, Gee.Traversable<GLib.DateTime>, Gee.Iterable<GLib.DateTime> {
++        public GLib.DateTime first_dt { get; construct; }
++        public GLib.DateTime last_dt { get; construct; }
++
++        public bool @foreach (Gee.ForallFunc<GLib.DateTime> f) {
++            foreach (var date in this) {
++                if (f (date) == false) {
++                    return false;
++                }
++            }
++
++            return true;
++        }
++
++        public DateRange (GLib.DateTime first, GLib.DateTime last) {
++            Object (
++                first_dt: first,
++                last_dt: last
++            );
++        }
++
++        public bool equals (DateRange other) {
++            return (first_dt == other.first_dt && last_dt == other.last_dt);
++        }
++
++        public Gee.Iterator<GLib.DateTime> iterator () {
++            return new DateIterator (this);
++        }
++
++        public Gee.List<GLib.DateTime> to_list () {
++            var list = new Gee.ArrayList<GLib.DateTime> ((Gee.EqualDataFunc<GLib.DateTime>? )datetime_equal_func);
++
++            foreach (var date in this) {
++                list.add (date);
++            }
++
++            return list;
++        }
++
++        /* Returns true if 'a' and 'b' are the same GLib.DateTime */
++        private bool datetime_equal_func (GLib.DateTime a, GLib.DateTime b) {
++            return a.equal (b);
++        }
++    }
++}
+diff --git a/src/Widgets/calendar/Util.vala b/src/Util/Util.vala
+similarity index 96%
+rename from src/Widgets/calendar/Util.vala
+rename to src/Util/Util.vala
+index e51f784..c261f4b 100644
+--- a/src/Widgets/calendar/Util.vala
++++ b/src/Util/Util.vala
+@@ -19,7 +19,7 @@
+  * Authored by: Corentin Noël <corentin@elementaryos.org>
+  */
+ 
+-namespace Util {
++namespace DateTimeIndicator.Util {
+     static bool has_scrolled = false;
+ 
+     public bool on_scroll_event (Gdk.EventScroll event) {
+@@ -35,7 +35,7 @@ namespace Util {
+ 
+         /* It's mouse scroll ! */
+         if (choice == 1 || choice == -1) {
+-            DateTime.Widgets.CalendarModel.get_default ().change_month ((int)choice);
++            Models.CalendarModel.get_default ().change_month ((int)choice);
+ 
+             return true;
+         }
+@@ -46,14 +46,14 @@ namespace Util {
+ 
+         if (choice > 0.3) {
+             reset_timer.begin ();
+-            DateTime.Widgets.CalendarModel.get_default ().change_month (1);
++            Models.CalendarModel.get_default ().change_month (1);
+ 
+             return true;
+         }
+ 
+         if (choice < -0.3) {
+             reset_timer.begin ();
+-            DateTime.Widgets.CalendarModel.get_default ().change_month (-1);
++            Models.CalendarModel.get_default ().change_month (-1);
+ 
+             return true;
+         }
+diff --git a/src/Widgets/CalendarView.vala b/src/Widgets/CalendarView.vala
+new file mode 100644
+index 0000000..65cee28
+--- /dev/null
++++ b/src/Widgets/CalendarView.vala
+@@ -0,0 +1,185 @@
++/*-
++ * Copyright (c) 2011–2018 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 <http://www.gnu.org/licenses/>.
++ *
++ * Authored by: Maxwell Barvian
++ *              Corentin Noël <corentin@elementaryos.org>
++ */
++
++namespace DateTimeIndicator {
++    public class Widgets.CalendarView : Gtk.Grid {
++        public signal void day_double_click ();
++        public signal void event_updates ();
++        public signal void selection_changed (GLib.DateTime? new_date);
++
++        public GLib.DateTime? selected_date { get; private set; }
++
++        private Widgets.Grid grid;
++        private Gtk.Stack stack;
++        private Gtk.Grid big_grid;
++
++        construct {
++            var label = new Gtk.Label (new GLib.DateTime.now_local ().format (_("%OB, %Y")));
++            label.hexpand = true;
++            label.margin_start = 6;
++            label.xalign = 0;
++            label.width_chars = 13;
++
++            var provider = new Gtk.CssProvider ();
++            provider.load_from_resource ("/io/elementary/desktop/wingpanel/datetime/ControlHeader.css");
++
++            var label_style_context = label.get_style_context ();
++            label_style_context.add_class ("header-label");
++            label_style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++
++            var left_button = new Gtk.Button.from_icon_name ("pan-start-symbolic");
++            var center_button = new Gtk.Button.from_icon_name ("office-calendar-symbolic");
++            center_button.tooltip_text = _("Go to today's date");
++            var right_button = new Gtk.Button.from_icon_name ("pan-end-symbolic");
++
++            var box_buttons = new Gtk.Grid ();
++            box_buttons.margin_end = 6;
++            box_buttons.valign = Gtk.Align.CENTER;
++            box_buttons.get_style_context ().add_class (Gtk.STYLE_CLASS_LINKED);
++            box_buttons.add (left_button);
++            box_buttons.add (center_button);
++            box_buttons.add (right_button);
++
++            big_grid = create_big_grid ();
++
++            stack = new Gtk.Stack ();
++            stack.add (big_grid);
++            stack.show_all ();
++            stack.expand = true;
++
++            stack.notify["transition-running"].connect (() => {
++                if (stack.transition_running == false) {
++                    stack.get_children ().foreach ((child) => {
++                        if (child != stack.visible_child) {
++                            child.destroy ();
++                        }
++                    });
++                }
++            });
++
++            column_spacing = 6;
++            row_spacing = 6;
++            margin_start = margin_end = 10;
++            attach (label, 0, 0);
++            attach (box_buttons, 1, 0);
++            attach (stack, 0, 1, 2);
++
++            var model = Models.CalendarModel.get_default ();
++            model.notify["data-range"].connect (() => {
++                label.label = model.month_start.format (_("%OB, %Y"));
++
++                sync_with_model ();
++
++                selected_date = null;
++                selection_changed (selected_date);
++            });
++
++            left_button.clicked.connect (() => {
++                model.change_month (-1);
++            });
++
++            right_button.clicked.connect (() => {
++                model.change_month (1);
++            });
++
++            center_button.clicked.connect (() => {
++                show_today ();
++            });
++        }
++
++        private Gtk.Grid create_big_grid () {
++            grid = new Widgets.Grid ();
++            grid.show_all ();
++
++            grid.on_event_add.connect ((date) => {
++                show_date_in_maya (date);
++                day_double_click ();
++            });
++
++            grid.selection_changed.connect ((date) => {
++                selected_date = date;
++                selection_changed (date);
++            });
++
++            return grid;
++        }
++
++        public void show_today () {
++            var calmodel = Models.CalendarModel.get_default ();
++            var today = Util.strip_time (new GLib.DateTime.now_local ());
++            var start = Util.get_start_of_month (today);
++            selected_date = today;
++            if (!start.equal (calmodel.month_start)) {
++                calmodel.month_start = start;
++            }
++            sync_with_model ();
++
++            grid.set_focus_to_today ();
++        }
++
++        // TODO: As far as maya supports it use the Dbus Activation feature to run the calendar-app.
++        public void show_date_in_maya (GLib.DateTime date) {
++            var command = "io.elementary.calendar --show-day %s".printf (date.format ("%F"));
++
++            try {
++                var appinfo = AppInfo.create_from_commandline (command, null, AppInfoCreateFlags.NONE);
++                appinfo.launch_uris (null, null);
++            } catch (GLib.Error e) {
++                var dialog = new Granite.MessageDialog.with_image_from_icon_name (
++                    _("Unable To Launch Calendar"),
++                    _("The program \"io.elementary.calendar\" may not be installed"),
++                    "dialog-error"
++                );
++                dialog.show_error_details (e.message);
++                dialog.run ();
++                dialog.destroy ();
++            }
++        }
++
++        /* Sets the calendar widgets to the date range of the model */
++        private void sync_with_model () {
++            var model = Models.CalendarModel.get_default ();
++            if (grid.grid_range != null && (model.data_range.equals (grid.grid_range) || grid.grid_range.first_dt.compare (model.data_range.first_dt) == 0)) {
++                grid.update_today ();
++                return; // nothing else to do
++            }
++
++            GLib.DateTime previous_first = null;
++            if (grid.grid_range != null)
++                previous_first = grid.grid_range.first_dt;
++
++            big_grid = create_big_grid ();
++            stack.add (big_grid);
++
++            grid.set_range (model.data_range, model.month_start);
++            grid.update_weeks (model.data_range.first_dt, model.num_weeks);
++
++            if (previous_first != null) {
++                if (previous_first.compare (grid.grid_range.first_dt) == -1) {
++                    stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT;
++                } else {
++                    stack.transition_type = Gtk.StackTransitionType.SLIDE_RIGHT;
++                }
++            }
++
++            stack.set_visible_child (big_grid);
++        }
++    }
++}
+diff --git a/src/Widgets/EventRow.vala b/src/Widgets/EventRow.vala
+index 8e0513e..1268311 100644
+--- a/src/Widgets/EventRow.vala
++++ b/src/Widgets/EventRow.vala
+@@ -17,104 +17,106 @@
+  * Boston, MA 02110-1301 USA.
+  */
+ 
+-public class DateTime.EventRow : Gtk.ListBoxRow {
+-    public GLib.DateTime date { get; construct; }
+-    public unowned ICal.Component component { get; construct; }
+-    public unowned E.SourceCalendar cal { get; construct; }
+-
+-    public GLib.DateTime start_time { get; private set; }
+-    public GLib.DateTime? end_time { get; private set; }
+-    public bool is_allday { get; private set; default = false; }
+-
+-    private static Services.TimeManager time_manager;
+-    private static Gtk.CssProvider css_provider;
+-
+-    private Gtk.Grid grid;
+-    private Gtk.Image event_image;
+-    private Gtk.Label time_label;
+-
+-    public EventRow (GLib.DateTime date, ICal.Component component, E.Source source) {
+-        Object (
+-            component: component,
+-            date: date,
+-            cal: (E.SourceCalendar?) source.get_extension (E.SOURCE_EXTENSION_CALENDAR)
+-        );
+-    }
+-
+-    static construct {
+-        css_provider = new Gtk.CssProvider ();
+-        css_provider.load_from_resource ("/io/elementary/desktop/wingpanel/datetime/EventRow.css");
+-
+-        time_manager = Services.TimeManager.get_default ();
+-    }
++namespace DateTimeIndicator {
++    public class EventRow : Gtk.ListBoxRow {
++        public GLib.DateTime date { get; construct; }
++        public unowned ICal.Component component { get; construct; }
++        public unowned E.SourceCalendar cal { get; construct; }
++
++        public GLib.DateTime start_time { get; private set; }
++        public GLib.DateTime? end_time { get; private set; }
++        public bool is_allday { get; private set; default = false; }
++
++        private static Services.TimeManager time_manager;
++        private static Gtk.CssProvider css_provider;
++
++        private Gtk.Grid grid;
++        private Gtk.Image event_image;
++        private Gtk.Label time_label;
++
++        public EventRow (GLib.DateTime date, ICal.Component component, E.Source source) {
++            Object (
++                component: component,
++                date: date,
++                cal: (E.SourceCalendar?) source.get_extension (E.SOURCE_EXTENSION_CALENDAR)
++            );
++        }
+ 
+-    construct {
+-        start_time = Util.ical_to_date_time (component.get_dtstart ());
+-        end_time = Util.ical_to_date_time (component.get_dtend ());
++        static construct {
++            css_provider = new Gtk.CssProvider ();
++            css_provider.load_from_resource ("/io/elementary/desktop/wingpanel/datetime/EventRow.css");
+ 
+-        if (end_time != null && Util.is_the_all_day (start_time, end_time)) {
+-            is_allday = true;
++            time_manager = Services.TimeManager.get_default ();
+         }
+ 
+-        unowned string icon_name = "office-calendar-symbolic";
+-        if (end_time == null) {
+-            icon_name = "alarm-symbolic";
++        construct {
++            start_time = Util.ical_to_date_time (component.get_dtstart ());
++            end_time = Util.ical_to_date_time (component.get_dtend ());
++
++            if (end_time != null && Util.is_the_all_day (start_time, end_time)) {
++                is_allday = true;
++            }
++
++            unowned string icon_name = "office-calendar-symbolic";
++            if (end_time == null) {
++                icon_name = "alarm-symbolic";
++            }
++
++            event_image = new Gtk.Image.from_icon_name (icon_name, Gtk.IconSize.MENU);
++            event_image.valign = Gtk.Align.START;
++
++            unowned Gtk.StyleContext event_image_context = event_image.get_style_context ();
++            event_image_context.add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++
++            var name_label = new Gtk.Label (component.get_summary ());
++            name_label.hexpand = true;
++            name_label.ellipsize = Pango.EllipsizeMode.END;
++            name_label.lines = 3;
++            name_label.max_width_chars = 30;
++            name_label.wrap = true;
++            name_label.wrap_mode = Pango.WrapMode.WORD_CHAR;
++            name_label.xalign = 0;
++
++            unowned Gtk.StyleContext name_label_context = name_label.get_style_context ();
++            name_label_context.add_class ("title");
++            name_label_context.add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++
++            time_label = new Gtk.Label (null);
++            time_label.use_markup = true;
++            time_label.xalign = 0;
++            time_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL);
++
++            grid = new Gtk.Grid ();
++            grid.column_spacing = 6;
++            grid.margin = 3;
++            grid.margin_start = grid.margin_end = 6;
++            grid.attach (event_image, 0, 0);
++            grid.attach (name_label, 1, 0);
++            if (!is_allday) {
++                grid.attach (time_label, 1, 1);
++            }
++
++            unowned Gtk.StyleContext grid_context = grid.get_style_context ();
++            grid_context.add_class ("event");
++            grid_context.add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++
++            add (grid);
++
++            set_color ();
++            cal.notify["color"].connect (set_color);
++
++            update_timelabel ();
++            time_manager.notify["is-12h"].connect (update_timelabel);
+         }
+ 
+-        event_image = new Gtk.Image.from_icon_name (icon_name, Gtk.IconSize.MENU);
+-        event_image.valign = Gtk.Align.START;
+-
+-        unowned Gtk.StyleContext event_image_context = event_image.get_style_context ();
+-        event_image_context.add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+-
+-        var name_label = new Gtk.Label (component.get_summary ());
+-        name_label.hexpand = true;
+-        name_label.ellipsize = Pango.EllipsizeMode.END;
+-        name_label.lines = 3;
+-        name_label.max_width_chars = 30;
+-        name_label.wrap = true;
+-        name_label.wrap_mode = Pango.WrapMode.WORD_CHAR;
+-        name_label.xalign = 0;
+-
+-        unowned Gtk.StyleContext name_label_context = name_label.get_style_context ();
+-        name_label_context.add_class ("title");
+-        name_label_context.add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+-
+-        time_label = new Gtk.Label (null);
+-        time_label.use_markup = true;
+-        time_label.xalign = 0;
+-        time_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL);
+-
+-        grid = new Gtk.Grid ();
+-        grid.column_spacing = 6;
+-        grid.margin = 3;
+-        grid.margin_start = grid.margin_end = 6;
+-        grid.attach (event_image, 0, 0);
+-        grid.attach (name_label, 1, 0);
+-        if (!is_allday) {
+-            grid.attach (time_label, 1, 1);
++        private void update_timelabel () {
++            var time_format = Granite.DateTime.get_default_time_format (time_manager.is_12h);
++            time_label.label = "<small>%s – %s</small>".printf (start_time.format (time_format), end_time.format (time_format));
+         }
+ 
+-        unowned Gtk.StyleContext grid_context = grid.get_style_context ();
+-        grid_context.add_class ("event");
+-        grid_context.add_provider (css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+-
+-        add (grid);
+-
+-        set_color ();
+-        cal.notify["color"].connect (set_color);
+-
+-        update_timelabel ();
+-        time_manager.notify["is-12h"].connect (update_timelabel);
+-    }
+-
+-    private void update_timelabel () {
+-        var time_format = Granite.DateTime.get_default_time_format (time_manager.is_12h);
+-        time_label.label = "<small>%s – %s</small>".printf (start_time.format (time_format), end_time.format (time_format));
+-    }
+-
+-    private void set_color () {
+-        Util.set_event_calendar_color (cal, grid);
+-        Util.set_event_calendar_color (cal, event_image);
++        private void set_color () {
++            Util.set_event_calendar_color (cal, grid);
++            Util.set_event_calendar_color (cal, event_image);
++        }
+     }
+ }
+diff --git a/src/Widgets/calendar/Grid.vala b/src/Widgets/Grid.vala
+similarity index 95%
+rename from src/Widgets/calendar/Grid.vala
+rename to src/Widgets/Grid.vala
+index 2b48636..165d11d 100644
+--- a/src/Widgets/calendar/Grid.vala
++++ b/src/Widgets/Grid.vala
+@@ -20,11 +20,11 @@
+  *              Corentin Noël <corentin@elementaryos.org>
+  */
+ 
+-namespace DateTime.Widgets {
++namespace DateTimeIndicator {
+ /**
+  * Represents the entire date grid as a table.
+  */
+-    public class Grid : Gtk.Grid {
++    public class Widgets.Grid : Gtk.Grid {
+         public Util.DateRange grid_range { get; private set; }
+ 
+         /*
+@@ -59,7 +59,7 @@ namespace DateTime.Widgets {
+             hexpand = true;
+             attach (week_sep_revealer, 1, 1, 1, 6);
+ 
+-            DateTime.Indicator.settings.bind ("show-weeks", week_sep_revealer, "reveal-child", GLib.SettingsBindFlags.DEFAULT);
++            Indicator.settings.bind ("show-weeks", week_sep_revealer, "reveal-child", GLib.SettingsBindFlags.DEFAULT);
+ 
+             data = new Gee.HashMap<uint, GridDay> ();
+             events |= Gdk.EventMask.SCROLL_MASK;
+@@ -77,7 +77,7 @@ namespace DateTime.Widgets {
+             day.set_selected (true);
+             day.set_state_flags (Gtk.StateFlags.FOCUSED, false);
+             selection_changed (selected_date);
+-            var calmodel = CalendarModel.get_default ();
++            var calmodel = Models.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 ();
+ 
+@@ -127,7 +127,7 @@ namespace DateTime.Widgets {
+             /* Create new widgets for the new range */
+ 
+             var date = Util.strip_time (today);
+-            date = date.add_days (CalendarModel.get_default ().week_starts_on - date.get_day_of_week ());
++            date = date.add_days (Models.CalendarModel.get_default ().week_starts_on - date.get_day_of_week ());
+             foreach (var label in header_labels) {
+                 label.label = date.format ("%a");
+                 date = date.add_days (1);
+@@ -221,7 +221,7 @@ namespace DateTime.Widgets {
+                 week_labels[c].add (week_label);
+                 week_labels[c].show_all ();
+ 
+-                DateTime.Indicator.settings.bind ("show-weeks", week_labels[c], "reveal-child", GLib.SettingsBindFlags.DEFAULT);
++                Indicator.settings.bind ("show-weeks", week_labels[c], "reveal-child", GLib.SettingsBindFlags.DEFAULT);
+ 
+                 attach (week_labels[c], 0, c + 1);
+ 
+diff --git a/src/Widgets/GridDay.vala b/src/Widgets/GridDay.vala
+new file mode 100644
+index 0000000..8c44443
+--- /dev/null
++++ b/src/Widgets/GridDay.vala
+@@ -0,0 +1,180 @@
++// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
++/*-
++ * Copyright (c) 2011–2018 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 <http://www.gnu.org/licenses/>.
++ *
++ * Authored by: Maxwell Barvian
++ *              Corentin Noël <corentin@elementaryos.org>
++ */
++
++namespace DateTimeIndicator {
++/**
++ * Represents a single day on the grid.
++ */
++    public class Widgets.GridDay : Gtk.EventBox {
++        /*
++         * Event emitted when the day is double clicked or the ENTER key is pressed.
++         */
++        public signal void on_event_add (GLib.DateTime date);
++
++        public GLib.DateTime date { get; construct set; }
++
++        private static Gtk.CssProvider provider;
++        private static Models.CalendarModel model;
++
++        private Gee.HashMap<string, Gtk.Widget> event_dots;
++        private Gtk.Grid event_grid;
++        private Gtk.Label label;
++        private bool valid_grab = false;
++
++        public GridDay (GLib.DateTime date) {
++            Object (date: date);
++        }
++
++        static construct {
++            model = Models.CalendarModel.get_default ();
++
++            provider = new Gtk.CssProvider ();
++            provider.load_from_resource ("/io/elementary/desktop/wingpanel/datetime/GridDay.css");
++        }
++
++        construct {
++            unowned Gtk.StyleContext style_context = get_style_context ();
++            style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++            style_context.add_class ("circular");
++
++            label = new Gtk.Label (null);
++
++            event_grid = new Gtk.Grid ();
++            event_grid.halign = Gtk.Align.CENTER;
++            event_grid.height_request = 6;
++
++            var grid = new Gtk.Grid ();
++            grid.halign = grid.valign = Gtk.Align.CENTER;
++            grid.attach (label, 0, 0);
++            grid.attach (event_grid, 0, 1);
++
++            can_focus = true;
++            events |= Gdk.EventMask.BUTTON_PRESS_MASK;
++            events |= Gdk.EventMask.KEY_PRESS_MASK;
++            events |= Gdk.EventMask.SMOOTH_SCROLL_MASK;
++            set_size_request (35, 35);
++            halign = Gtk.Align.CENTER;
++            hexpand = true;
++            add (grid);
++            show_all ();
++
++            // Signals and handlers
++            button_press_event.connect (on_button_press);
++            key_press_event.connect (on_key_press);
++            scroll_event.connect ((event) => {return Util.on_scroll_event (event);});
++
++            notify["date"].connect (() => {
++                label.label = date.get_day_of_month ().to_string ();
++            });
++
++            event_dots = new Gee.HashMap<string, Gtk.Widget> ();
++
++            model.events_added.connect (add_event_dots);
++            model.events_removed.connect (remove_event_dots);
++        }
++
++        private void add_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
++            foreach (var component in events) {
++                if (event_dots.size >= 3) {
++                    return;
++                }
++
++                if (Util.calcomp_is_on_day (component, date)) {
++                    unowned ICal.Component ical = component.get_icalcomponent ();
++
++                    var event_uid = ical.get_uid ();
++                    if (!event_dots.has_key (event_uid)) {
++                        var event_dot = new Gtk.Image ();
++                        event_dot.gicon = new ThemedIcon ("pager-checked-symbolic");
++                        event_dot.pixel_size = 6;
++
++                        unowned Gtk.StyleContext style_context = event_dot.get_style_context ();
++                        style_context.add_class (Granite.STYLE_CLASS_ACCENT);
++                        style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++
++                        var source_calendar = (E.SourceCalendar?) source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
++                        Util.set_event_calendar_color (source_calendar, event_dot);
++
++                        event_dots[event_uid] = event_dot;
++
++                        event_grid.add (event_dot);
++                    }
++                }
++            }
++
++            event_grid.show_all ();
++        }
++
++        private void remove_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
++            foreach (var component in events) {
++                unowned ICal.Component ical = component.get_icalcomponent ();
++                var event_uid = ical.get_uid ();
++                var dot = event_dots[event_uid];
++                if (dot != null) {
++                    dot.destroy ();
++                    event_dots.remove (event_uid);
++                }
++            }
++        }
++
++        public void set_selected (bool selected) {
++            if (selected) {
++                set_state_flags (Gtk.StateFlags.SELECTED, true);
++            } else {
++                set_state_flags (Gtk.StateFlags.NORMAL, true);
++            }
++        }
++
++        public void grab_focus_force () {
++            valid_grab = true;
++            grab_focus ();
++        }
++
++        public override void grab_focus () {
++            if (valid_grab) {
++                base.grab_focus ();
++                valid_grab = false;
++            }
++        }
++
++        public void sensitive_container (bool sens) {
++            label.sensitive = sens;
++            event_grid.sensitive = sens;
++        }
++
++        private bool on_button_press (Gdk.EventButton event) {
++            if (event.type == Gdk.EventType.2BUTTON_PRESS && event.button == Gdk.BUTTON_PRIMARY)
++                on_event_add (date);
++            valid_grab = true;
++            grab_focus ();
++            return false;
++        }
++
++        private bool on_key_press (Gdk.EventKey event) {
++            if (event.keyval == Gdk.keyval_from_name ("Return") ) {
++                on_event_add (date);
++                return true;
++            }
++
++            return false;
++        }
++    }
++}
+diff --git a/src/Widgets/PanelLabel.vala b/src/Widgets/PanelLabel.vala
+index f253f9b..dff9a21 100644
+--- a/src/Widgets/PanelLabel.vala
++++ b/src/Widgets/PanelLabel.vala
+@@ -17,55 +17,57 @@
+  * Boston, MA 02110-1301 USA.
+  */
+ 
+-public class DateTime.Widgets.PanelLabel : Gtk.Grid {
+-    private Gtk.Label date_label;
+-    private Gtk.Label time_label;
+-    private Services.TimeManager time_manager;
++namespace DateTimeIndicator {
++    public class Widgets.PanelLabel : Gtk.Grid {
++        private Gtk.Label date_label;
++        private Gtk.Label time_label;
++        private Services.TimeManager time_manager;
+ 
+-    public string clock_format { get; set; }
+-    public bool clock_show_seconds { get; set; }
+-    public bool clock_show_weekday { get; set; }
++        public string clock_format { get; set; }
++        public bool clock_show_seconds { get; set; }
++        public bool clock_show_weekday { get; set; }
+ 
+-    construct {
+-        date_label = new Gtk.Label (null);
+-        date_label.margin_end = 12;
++        construct {
++            date_label = new Gtk.Label (null);
++            date_label.margin_end = 12;
+ 
+-        var date_revealer = new Gtk.Revealer ();
+-        date_revealer.transition_type = Gtk.RevealerTransitionType.SLIDE_LEFT;
+-        date_revealer.add (date_label);
++            var date_revealer = new Gtk.Revealer ();
++            date_revealer.transition_type = Gtk.RevealerTransitionType.SLIDE_LEFT;
++            date_revealer.add (date_label);
+ 
+-        time_label = new Gtk.Label (null);
++            time_label = new Gtk.Label (null);
+ 
+-        valign = Gtk.Align.CENTER;
+-        add (date_revealer);
+-        add (time_label);
++            valign = Gtk.Align.CENTER;
++            add (date_revealer);
++            add (time_label);
+ 
+-        var clock_settings = new GLib.Settings ("io.elementary.desktop.wingpanel.datetime");
+-        clock_settings.bind ("clock-format", this, "clock-format", SettingsBindFlags.DEFAULT);
+-        clock_settings.bind ("clock-show-seconds", this, "clock-show-seconds", SettingsBindFlags.DEFAULT);
+-        clock_settings.bind ("clock-show-date", date_revealer, "reveal_child", SettingsBindFlags.DEFAULT);
+-        clock_settings.bind ("clock-show-weekday", this, "clock-show-weekday", SettingsBindFlags.DEFAULT);
++            var clock_settings = new GLib.Settings ("io.elementary.desktop.wingpanel.datetime");
++            clock_settings.bind ("clock-format", this, "clock-format", SettingsBindFlags.DEFAULT);
++            clock_settings.bind ("clock-show-seconds", this, "clock-show-seconds", SettingsBindFlags.DEFAULT);
++            clock_settings.bind ("clock-show-date", date_revealer, "reveal_child", SettingsBindFlags.DEFAULT);
++            clock_settings.bind ("clock-show-weekday", this, "clock-show-weekday", SettingsBindFlags.DEFAULT);
+ 
+-        notify.connect (() => {
+-            update_labels ();
+-        });
++            notify.connect (() => {
++                update_labels ();
++            });
+ 
+-        time_manager = Services.TimeManager.get_default ();
+-        time_manager.minute_changed.connect (update_labels);
+-        time_manager.notify["is-12h"].connect (update_labels);
+-    }
+-
+-    private void update_labels () {
+-        string date_format;
+-        if (clock_format == "ISO8601") {
+-            date_format = "%F";
+-        } else {
+-            date_format = Granite.DateTime.get_default_date_format (clock_show_weekday, true, false);
++            time_manager = Services.TimeManager.get_default ();
++            time_manager.minute_changed.connect (update_labels);
++            time_manager.notify["is-12h"].connect (update_labels);
+         }
+ 
+-        date_label.label = time_manager.format (date_format);
++        private void update_labels () {
++            string date_format;
++            if (clock_format == "ISO8601") {
++                date_format = "%F";
++            } else {
++                date_format = Granite.DateTime.get_default_date_format (clock_show_weekday, true, false);
++            }
+ 
+-        string time_format = Granite.DateTime.get_default_time_format (time_manager.is_12h, clock_show_seconds);
+-        time_label.label = time_manager.format (time_format);
++            date_label.label = time_manager.format (date_format);
++
++            string time_format = Granite.DateTime.get_default_time_format (time_manager.is_12h, clock_show_seconds);
++            time_label.label = time_manager.format (time_format);
++        }
+     }
+ }
+diff --git a/src/Widgets/calendar/CalendarView.vala b/src/Widgets/calendar/CalendarView.vala
+deleted file mode 100644
+index ef3f8f9..0000000
+--- a/src/Widgets/calendar/CalendarView.vala
++++ /dev/null
+@@ -1,183 +0,0 @@
+-/*-
+- * Copyright (c) 2011–2018 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 <http://www.gnu.org/licenses/>.
+- *
+- * Authored by: Maxwell Barvian
+- *              Corentin Noël <corentin@elementaryos.org>
+- */
+-
+-public class DateTime.Widgets.CalendarView : Gtk.Grid {
+-    public signal void day_double_click ();
+-    public signal void event_updates ();
+-    public signal void selection_changed (GLib.DateTime? new_date);
+-
+-    public GLib.DateTime? selected_date { get; private set; }
+-
+-    private Grid grid;
+-    private Gtk.Stack stack;
+-    private Gtk.Grid big_grid;
+-
+-    construct {
+-        var label = new Gtk.Label (new GLib.DateTime.now_local ().format (_("%OB, %Y")));
+-        label.hexpand = true;
+-        label.margin_start = 6;
+-        label.xalign = 0;
+-        label.width_chars = 13;
+-
+-        var provider = new Gtk.CssProvider ();
+-        provider.load_from_resource ("/io/elementary/desktop/wingpanel/datetime/ControlHeader.css");
+-
+-        var label_style_context = label.get_style_context ();
+-        label_style_context.add_class ("header-label");
+-        label_style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+-
+-        var left_button = new Gtk.Button.from_icon_name ("pan-start-symbolic");
+-        var center_button = new Gtk.Button.from_icon_name ("office-calendar-symbolic");
+-        center_button.tooltip_text = _("Go to today's date");
+-        var right_button = new Gtk.Button.from_icon_name ("pan-end-symbolic");
+-
+-        var box_buttons = new Gtk.Grid ();
+-        box_buttons.margin_end = 6;
+-        box_buttons.valign = Gtk.Align.CENTER;
+-        box_buttons.get_style_context ().add_class (Gtk.STYLE_CLASS_LINKED);
+-        box_buttons.add (left_button);
+-        box_buttons.add (center_button);
+-        box_buttons.add (right_button);
+-
+-        big_grid = create_big_grid ();
+-
+-        stack = new Gtk.Stack ();
+-        stack.add (big_grid);
+-        stack.show_all ();
+-        stack.expand = true;
+-
+-        stack.notify["transition-running"].connect (() => {
+-            if (stack.transition_running == false) {
+-                stack.get_children ().foreach ((child) => {
+-                    if (child != stack.visible_child) {
+-                        child.destroy ();
+-                    }
+-                });
+-            }
+-        });
+-
+-        column_spacing = 6;
+-        row_spacing = 6;
+-        margin_start = margin_end = 10;
+-        attach (label, 0, 0);
+-        attach (box_buttons, 1, 0);
+-        attach (stack, 0, 1, 2);
+-
+-        var model = CalendarModel.get_default ();
+-        model.notify["data-range"].connect (() => {
+-            label.label = model.month_start.format (_("%OB, %Y"));
+-
+-            sync_with_model ();
+-
+-            selected_date = null;
+-            selection_changed (selected_date);
+-        });
+-
+-        left_button.clicked.connect (() => {
+-            model.change_month (-1);
+-        });
+-
+-        right_button.clicked.connect (() => {
+-            model.change_month (1);
+-        });
+-
+-        center_button.clicked.connect (() => {
+-            show_today ();
+-        });
+-    }
+-
+-    private Gtk.Grid create_big_grid () {
+-        grid = new DateTime.Widgets.Grid ();
+-        grid.show_all ();
+-
+-        grid.on_event_add.connect ((date) => {
+-            show_date_in_maya (date);
+-            day_double_click ();
+-        });
+-
+-        grid.selection_changed.connect ((date) => {
+-            selected_date = date;
+-            selection_changed (date);
+-        });
+-
+-        return grid;
+-    }
+-
+-    public void show_today () {
+-        var calmodel = CalendarModel.get_default ();
+-        var today = Util.strip_time (new GLib.DateTime.now_local ());
+-        var start = Util.get_start_of_month (today);
+-        selected_date = today;
+-        if (!start.equal (calmodel.month_start)) {
+-            calmodel.month_start = start;
+-        }
+-        sync_with_model ();
+-
+-        grid.set_focus_to_today ();
+-    }
+-
+-    // TODO: As far as maya supports it use the Dbus Activation feature to run the calendar-app.
+-    public void show_date_in_maya (GLib.DateTime date) {
+-        var command = "io.elementary.calendar --show-day %s".printf (date.format ("%F"));
+-
+-        try {
+-            var appinfo = AppInfo.create_from_commandline (command, null, AppInfoCreateFlags.NONE);
+-            appinfo.launch_uris (null, null);
+-        } catch (GLib.Error e) {
+-            var dialog = new Granite.MessageDialog.with_image_from_icon_name (
+-                _("Unable To Launch Calendar"),
+-                _("The program \"io.elementary.calendar\" may not be installed"),
+-                "dialog-error"
+-            );
+-            dialog.show_error_details (e.message);
+-            dialog.run ();
+-            dialog.destroy ();
+-        }
+-    }
+-
+-    /* Sets the calendar widgets to the date range of the model */
+-    private void sync_with_model () {
+-        var model = CalendarModel.get_default ();
+-        if (grid.grid_range != null && (model.data_range.equals (grid.grid_range) || grid.grid_range.first_dt.compare (model.data_range.first_dt) == 0)) {
+-            grid.update_today ();
+-            return; // nothing else to do
+-        }
+-
+-        GLib.DateTime previous_first = null;
+-        if (grid.grid_range != null)
+-            previous_first = grid.grid_range.first_dt;
+-
+-        big_grid = create_big_grid ();
+-        stack.add (big_grid);
+-
+-        grid.set_range (model.data_range, model.month_start);
+-        grid.update_weeks (model.data_range.first_dt, model.num_weeks);
+-
+-        if (previous_first != null) {
+-            if (previous_first.compare (grid.grid_range.first_dt) == -1) {
+-                stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT;
+-            } else {
+-                stack.transition_type = Gtk.StackTransitionType.SLIDE_RIGHT;
+-            }
+-        }
+-
+-        stack.set_visible_child (big_grid);
+-    }
+-}
+diff --git a/src/Widgets/calendar/GridDay.vala b/src/Widgets/calendar/GridDay.vala
+deleted file mode 100644
+index a9b5a28..0000000
+--- a/src/Widgets/calendar/GridDay.vala
++++ /dev/null
+@@ -1,178 +0,0 @@
+-// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
+-/*-
+- * Copyright (c) 2011–2018 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 <http://www.gnu.org/licenses/>.
+- *
+- * Authored by: Maxwell Barvian
+- *              Corentin Noël <corentin@elementaryos.org>
+- */
+-
+-/**
+- * Represents a single day on the grid.
+- */
+-public class DateTime.Widgets.GridDay : Gtk.EventBox {
+-    /*
+-     * Event emitted when the day is double clicked or the ENTER key is pressed.
+-     */
+-    public signal void on_event_add (GLib.DateTime date);
+-
+-    public GLib.DateTime date { get; construct set; }
+-
+-    private static Gtk.CssProvider provider;
+-    private static Widgets.CalendarModel model;
+-
+-    private Gee.HashMap<string, Gtk.Widget> event_dots;
+-    private Gtk.Grid event_grid;
+-    private Gtk.Label label;
+-    private bool valid_grab = false;
+-
+-    public GridDay (GLib.DateTime date) {
+-        Object (date: date);
+-    }
+-
+-    static construct {
+-        model = Widgets.CalendarModel.get_default ();
+-
+-        provider = new Gtk.CssProvider ();
+-        provider.load_from_resource ("/io/elementary/desktop/wingpanel/datetime/GridDay.css");
+-    }
+-
+-    construct {
+-        unowned Gtk.StyleContext style_context = get_style_context ();
+-        style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+-        style_context.add_class ("circular");
+-
+-        label = new Gtk.Label (null);
+-
+-        event_grid = new Gtk.Grid ();
+-        event_grid.halign = Gtk.Align.CENTER;
+-        event_grid.height_request = 6;
+-
+-        var grid = new Gtk.Grid ();
+-        grid.halign = grid.valign = Gtk.Align.CENTER;
+-        grid.attach (label, 0, 0);
+-        grid.attach (event_grid, 0, 1);
+-
+-        can_focus = true;
+-        events |= Gdk.EventMask.BUTTON_PRESS_MASK;
+-        events |= Gdk.EventMask.KEY_PRESS_MASK;
+-        events |= Gdk.EventMask.SMOOTH_SCROLL_MASK;
+-        set_size_request (35, 35);
+-        halign = Gtk.Align.CENTER;
+-        hexpand = true;
+-        add (grid);
+-        show_all ();
+-
+-        // Signals and handlers
+-        button_press_event.connect (on_button_press);
+-        key_press_event.connect (on_key_press);
+-        scroll_event.connect ((event) => {return Util.on_scroll_event (event);});
+-
+-        notify["date"].connect (() => {
+-            label.label = date.get_day_of_month ().to_string ();
+-        });
+-
+-        event_dots = new Gee.HashMap<string, Gtk.Widget> ();
+-
+-        model.events_added.connect (add_event_dots);
+-        model.events_removed.connect (remove_event_dots);
+-    }
+-
+-    private void add_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+-        foreach (var component in events) {
+-            if (event_dots.size >= 3) {
+-                return;
+-            }
+-
+-            if (Util.calcomp_is_on_day (component, date)) {
+-                unowned ICal.Component ical = component.get_icalcomponent ();
+-
+-                var event_uid = ical.get_uid ();
+-                if (!event_dots.has_key (event_uid)) {
+-                    var event_dot = new Gtk.Image ();
+-                    event_dot.gicon = new ThemedIcon ("pager-checked-symbolic");
+-                    event_dot.pixel_size = 6;
+-
+-                    unowned Gtk.StyleContext style_context = event_dot.get_style_context ();
+-                    style_context.add_class (Granite.STYLE_CLASS_ACCENT);
+-                    style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+-
+-                    var source_calendar = (E.SourceCalendar?) source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
+-                    Util.set_event_calendar_color (source_calendar, event_dot);
+-
+-                    event_dots[event_uid] = event_dot;
+-
+-                    event_grid.add (event_dot);
+-                }
+-            }
+-        }
+-
+-        event_grid.show_all ();
+-    }
+-
+-    private void remove_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+-        foreach (var component in events) {
+-            unowned ICal.Component ical = component.get_icalcomponent ();
+-            var event_uid = ical.get_uid ();
+-            var dot = event_dots[event_uid];
+-            if (dot != null) {
+-                dot.destroy ();
+-                event_dots.remove (event_uid);
+-            }
+-        }
+-    }
+-
+-    public void set_selected (bool selected) {
+-        if (selected) {
+-            set_state_flags (Gtk.StateFlags.SELECTED, true);
+-        } else {
+-            set_state_flags (Gtk.StateFlags.NORMAL, true);
+-        }
+-    }
+-
+-    public void grab_focus_force () {
+-        valid_grab = true;
+-        grab_focus ();
+-    }
+-
+-    public override void grab_focus () {
+-        if (valid_grab) {
+-            base.grab_focus ();
+-            valid_grab = false;
+-        }
+-    }
+-
+-    public void sensitive_container (bool sens) {
+-        label.sensitive = sens;
+-        event_grid.sensitive = sens;
+-    }
+-
+-    private bool on_button_press (Gdk.EventButton event) {
+-        if (event.type == Gdk.EventType.2BUTTON_PRESS && event.button == Gdk.BUTTON_PRIMARY)
+-            on_event_add (date);
+-        valid_grab = true;
+-        grab_focus ();
+-        return false;
+-    }
+-
+-    private bool on_key_press (Gdk.EventKey event) {
+-        if (event.keyval == Gdk.keyval_from_name ("Return") ) {
+-            on_event_add (date);
+-            return true;
+-        }
+-
+-        return false;
+-    }
+-}
+
+From 11f11073d74dac1d831560c3a92687531aaf846e Mon Sep 17 00:00:00 2001
+From: Dirli <litandrej85@gmail.com>
+Date: Thu, 9 Apr 2020 20:46:31 +0300
+Subject: [PATCH 2/8] Events list moved to a separate file
+
+---
+ meson.build                    |   1 +
+ src/Indicator.vala             | 113 +++------------------------------
+ src/Widgets/EventsListBox.vala | 102 +++++++++++++++++++++++++++++
+ 3 files changed, 113 insertions(+), 103 deletions(-)
+ create mode 100644 src/Widgets/EventsListBox.vala
+
+diff --git a/meson.build b/meson.build
+index b44c5bd..1b52276 100644
+--- a/meson.build
++++ b/meson.build
+@@ -46,6 +46,7 @@ shared_module(
+     'src/Models/CalendarModel.vala',
+     'src/Widgets/CalendarView.vala',
+     'src/Widgets/EventRow.vala',
++    'src/Widgets/EventsListBox.vala',
+     'src/Widgets/Grid.vala',
+     'src/Widgets/GridDay.vala',
+     'src/Widgets/PanelLabel.vala',
+diff --git a/src/Indicator.vala b/src/Indicator.vala
+index b712c12..6a8d001 100644
+--- a/src/Indicator.vala
++++ b/src/Indicator.vala
+@@ -22,9 +22,9 @@ namespace DateTimeIndicator {
+         public static GLib.Settings settings;
+ 
+         private Widgets.PanelLabel panel_label;
+-        private Gtk.Grid main_grid;
+         private Widgets.CalendarView calendar;
+-        private Gtk.ListBox event_listbox;
++        private Widgets.EventsListBox event_listbox;
++        private Gtk.Grid main_grid;
+         private uint update_events_idle_source = 0;
+ 
+         public Indicator () {
+@@ -56,24 +56,7 @@ namespace DateTimeIndicator {
+                 calendar = new Widgets.CalendarView ();
+                 calendar.margin_bottom = 6;
+ 
+-                var placeholder_label = new Gtk.Label (_("No Events on This Day"));
+-                placeholder_label.wrap = true;
+-                placeholder_label.wrap_mode = Pango.WrapMode.WORD;
+-                placeholder_label.margin_start = 12;
+-                placeholder_label.margin_end = 12;
+-                placeholder_label.max_width_chars = 20;
+-                placeholder_label.justify = Gtk.Justification.CENTER;
+-                placeholder_label.show_all ();
+-
+-                var placeholder_style_context = placeholder_label.get_style_context ();
+-                placeholder_style_context.add_class (Gtk.STYLE_CLASS_DIM_LABEL);
+-                placeholder_style_context.add_class (Granite.STYLE_CLASS_H3_LABEL);
+-
+-                event_listbox = new Gtk.ListBox ();
+-                event_listbox.selection_mode = Gtk.SelectionMode.NONE;
+-                event_listbox.set_header_func (header_update_func);
+-                event_listbox.set_placeholder (placeholder_label);
+-                event_listbox.set_sort_func (sort_function);
++                event_listbox = new Widgets.EventsListBox ();
+ 
+                 var scrolled_window = new Gtk.ScrolledWindow (null, null);
+                 scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER;
+@@ -84,11 +67,11 @@ namespace DateTimeIndicator {
+ 
+                 main_grid = new Gtk.Grid ();
+                 main_grid.margin_top = 12;
+-                main_grid.attach (calendar, 0, 0);
+-                main_grid.attach (new Gtk.Separator (Gtk.Orientation.VERTICAL), 1, 0);
+-                main_grid.attach (scrolled_window, 2, 0);
+-                main_grid.attach (new Wingpanel.Widgets.Separator (), 0, 2, 3);
+-                main_grid.attach (settings_button, 0, 3, 3);
++                main_grid.attach (calendar,                                     0, 0);
++                main_grid.attach (new Wingpanel.Widgets.Separator (),           0, 1);
++                main_grid.attach (settings_button,                              0, 2);
++                main_grid.attach (new Gtk.Separator (Gtk.Orientation.VERTICAL), 1, 0, 1, 3);
++                main_grid.attach (scrolled_window,                              2, 0, 1, 3);
+ 
+                 var size_group = new Gtk.SizeGroup (Gtk.SizeGroupMode.HORIZONTAL);
+                 size_group.add_widget (calendar);
+@@ -119,52 +102,6 @@ namespace DateTimeIndicator {
+             return main_grid;
+         }
+ 
+-        private void header_update_func (Gtk.ListBoxRow lbrow, Gtk.ListBoxRow? lbbefore) {
+-            var row = (EventRow) lbrow;
+-            if (lbbefore != null) {
+-                var before = (EventRow) lbbefore;
+-                if (row.is_allday == before.is_allday) {
+-                    row.set_header (null);
+-                    return;
+-                }
+-
+-                if (row.is_allday != before.is_allday) {
+-                    var header_label = new Granite.HeaderLabel (_("During the Day"));
+-                    header_label.margin_start = header_label.margin_end = 6;
+-
+-                    row.set_header (header_label);
+-                    return;
+-                }
+-            } else {
+-                if (row.is_allday) {
+-                    var allday_header = new Granite.HeaderLabel (_("All Day"));
+-                    allday_header.margin_start = allday_header.margin_end = 6;
+-
+-                    row.set_header (allday_header);
+-                }
+-                return;
+-            }
+-        }
+-
+-        [CCode (instance_pos = -1)]
+-        private int sort_function (Gtk.ListBoxRow child1, Gtk.ListBoxRow child2) {
+-            var e1 = (EventRow) child1;
+-            var e2 = (EventRow) child2;
+-
+-            if (e1.start_time.compare (e2.start_time) != 0) {
+-                return e1.start_time.compare (e2.start_time);
+-            }
+-
+-            // If they have the same date, sort them wholeday first
+-            if (e1.is_allday) {
+-                return -1;
+-            } else if (e2.is_allday) {
+-                return 1;
+-            }
+-
+-            return 0;
+-        }
+-
+         private void update_events_model (E.Source source, Gee.Collection<ECal.Component> events) {
+             idle_update_events ();
+         }
+@@ -174,42 +111,12 @@ namespace DateTimeIndicator {
+                 GLib.Source.remove (update_events_idle_source);
+             }
+ 
+-            update_events_idle_source = GLib.Idle.add (update_events);
+-        }
+-
+-        private bool update_events () {
+-            foreach (unowned Gtk.Widget widget in event_listbox.get_children ()) {
+-                widget.destroy ();
+-            }
++            update_events_idle_source = GLib.Idle.add (() => {
++                event_listbox.update_events (calendar.selected_date);
+ 
+-            if (calendar.selected_date == null) {
+                 update_events_idle_source = 0;
+                 return GLib.Source.REMOVE;
+-            }
+-
+-            var date = calendar.selected_date;
+-
+-            var model = Models.CalendarModel.get_default ();
+-
+-            var events_on_day = new Gee.TreeMap<string, EventRow> ();
+-
+-            model.source_events.@foreach ((source, component_map) => {
+-                foreach (var comp in component_map.get_values ()) {
+-                    if (Util.calcomp_is_on_day (comp, date)) {
+-                        unowned ICal.Component ical = comp.get_icalcomponent ();
+-                        var event_uid = ical.get_uid ();
+-                        if (!events_on_day.has_key (event_uid)) {
+-                            events_on_day[event_uid] = new EventRow (date, ical, source);
+-
+-                            event_listbox.add (events_on_day[event_uid]);
+-                        }
+-                    }
+-                }
+             });
+-
+-            event_listbox.show_all ();
+-            update_events_idle_source = 0;
+-            return GLib.Source.REMOVE;
+         }
+ 
+         public override void opened () {
+diff --git a/src/Widgets/EventsListBox.vala b/src/Widgets/EventsListBox.vala
+new file mode 100644
+index 0000000..547e4c5
+--- /dev/null
++++ b/src/Widgets/EventsListBox.vala
+@@ -0,0 +1,102 @@
++namespace DateTimeIndicator {
++    public class Widgets.EventsListBox : Gtk.ListBox {
++
++        public EventsListBox () {
++            selection_mode = Gtk.SelectionMode.NONE;
++
++            var placeholder_label = new Gtk.Label (_("No Events on This Day"));
++            placeholder_label.wrap = true;
++            placeholder_label.wrap_mode = Pango.WrapMode.WORD;
++            placeholder_label.margin_start = 12;
++            placeholder_label.margin_end = 12;
++            placeholder_label.max_width_chars = 20;
++            placeholder_label.justify = Gtk.Justification.CENTER;
++            placeholder_label.show_all ();
++
++            var placeholder_style_context = placeholder_label.get_style_context ();
++            placeholder_style_context.add_class (Gtk.STYLE_CLASS_DIM_LABEL);
++            placeholder_style_context.add_class (Granite.STYLE_CLASS_H3_LABEL);
++
++            set_header_func (header_update_func);
++            set_placeholder (placeholder_label);
++            set_sort_func (sort_function);
++        }
++
++        public void update_events (GLib.DateTime? selected_date) {
++            foreach (unowned Gtk.Widget widget in get_children ()) {
++                widget.destroy ();
++            }
++
++            if (selected_date == null) {
++                return;
++            }
++
++            var model = Models.CalendarModel.get_default ();
++
++            var events_on_day = new Gee.TreeMap<string, EventRow> ();
++
++            model.source_events.@foreach ((source, component_map) => {
++                foreach (var comp in component_map.get_values ()) {
++                    if (Util.calcomp_is_on_day (comp, selected_date)) {
++                        unowned ICal.Component ical = comp.get_icalcomponent ();
++                        var event_uid = ical.get_uid ();
++                        if (!events_on_day.has_key (event_uid)) {
++                            events_on_day[event_uid] = new EventRow (selected_date, ical, source);
++
++                            add (events_on_day[event_uid]);
++                        }
++                    }
++                }
++            });
++
++            show_all ();
++            return;
++        }
++
++        private void header_update_func (Gtk.ListBoxRow lbrow, Gtk.ListBoxRow? lbbefore) {
++            var row = (EventRow) lbrow;
++            if (lbbefore != null) {
++                var before = (EventRow) lbbefore;
++                if (row.is_allday == before.is_allday) {
++                    row.set_header (null);
++                    return;
++                }
++
++                if (row.is_allday != before.is_allday) {
++                    var header_label = new Granite.HeaderLabel (_("During the Day"));
++                    header_label.margin_start = header_label.margin_end = 6;
++
++                    row.set_header (header_label);
++                    return;
++                }
++            } else {
++                if (row.is_allday) {
++                    var allday_header = new Granite.HeaderLabel (_("All Day"));
++                    allday_header.margin_start = allday_header.margin_end = 6;
++
++                    row.set_header (allday_header);
++                }
++                return;
++            }
++        }
++
++        [CCode (instance_pos = -1)]
++        private int sort_function (Gtk.ListBoxRow child1, Gtk.ListBoxRow child2) {
++            var e1 = (EventRow) child1;
++            var e2 = (EventRow) child2;
++
++            if (e1.start_time.compare (e2.start_time) != 0) {
++                return e1.start_time.compare (e2.start_time);
++            }
++
++            // If they have the same date, sort them wholeday first
++            if (e1.is_allday) {
++                return -1;
++            } else if (e2.is_allday) {
++                return 1;
++            }
++
++            return 0;
++        }
++    }
++}
+
+From 262d91676f78f031bcfbff637344e52c63ab89bd Mon Sep 17 00:00:00 2001
+From: Dirli <litandrej85@gmail.com>
+Date: Sat, 11 Apr 2020 21:56:33 +0300
+Subject: [PATCH 3/8] added event manager
+
+---
+ meson.build                     |   1 +
+ src/Indicator.vala              |  38 ++++--
+ src/Models/CalendarModel.vala   | 224 +------------------------------
+ src/Services/EventsManager.vala | 226 ++++++++++++++++++++++++++++++++
+ src/Util/Util.vala              |  57 ++------
+ src/Widgets/CalendarView.vala   |   9 ++
+ src/Widgets/EventsListBox.vala  |   7 +-
+ src/Widgets/Grid.vala           |  31 +++++
+ src/Widgets/GridDay.vala        | 111 +++++++++++-----
+ 9 files changed, 385 insertions(+), 319 deletions(-)
+ create mode 100644 src/Services/EventsManager.vala
+
+diff --git a/meson.build b/meson.build
+index 1b52276..e348e3d 100644
+--- a/meson.build
++++ b/meson.build
+@@ -50,6 +50,7 @@ shared_module(
+     'src/Widgets/Grid.vala',
+     'src/Widgets/GridDay.vala',
+     'src/Widgets/PanelLabel.vala',
++    'src/Services/EventsManager.vala',
+     'src/Services/TimeManager.vala',
+     dependencies: [
+         dependency('glib-2.0'),
+diff --git a/src/Indicator.vala b/src/Indicator.vala
+index 6a8d001..bf4358f 100644
+--- a/src/Indicator.vala
++++ b/src/Indicator.vala
+@@ -24,9 +24,14 @@ namespace DateTimeIndicator {
+         private Widgets.PanelLabel panel_label;
+         private Widgets.CalendarView calendar;
+         private Widgets.EventsListBox event_listbox;
++
++        private Services.EventsManager event_manager;
++
+         private Gtk.Grid main_grid;
+         private uint update_events_idle_source = 0;
+ 
++        private bool opened_widget = false;
++
+         public Indicator () {
+             Object (
+                 code_name: Wingpanel.Indicator.DATETIME,
+@@ -56,10 +61,22 @@ namespace DateTimeIndicator {
+                 calendar = new Widgets.CalendarView ();
+                 calendar.margin_bottom = 6;
+ 
++                event_manager = new Services.EventsManager ();
++                event_manager.events_updated.connect (update_events_model);
++                event_manager.events_added.connect ((source, events) => {
++                    calendar.add_event_dots (source, events);
++                    update_events_model (source, events);
++                });
++                event_manager.events_removed.connect ((source, events) => {
++                    calendar.remove_event_dots (source, events);
++                    update_events_model (source, events);
++                });
++
+                 event_listbox = new Widgets.EventsListBox ();
+ 
+                 var scrolled_window = new Gtk.ScrolledWindow (null, null);
+                 scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER;
++                scrolled_window.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC;
+                 scrolled_window.add (event_listbox);
+ 
+                 var settings_button = new Gtk.ModelButton ();
+@@ -90,6 +107,12 @@ namespace DateTimeIndicator {
+                     close ();
+                 });
+ 
++                var model = Models.CalendarModel.get_default ();
++                model.notify["month-start"].connect (() => {
++                    model.compute_ranges ();
++                    event_manager.load_all_sources ();
++                });
++
+                 settings_button.clicked.connect (() => {
+                     try {
+                         AppInfo.launch_default_for_uri ("settings://time", null);
+@@ -103,7 +126,9 @@ namespace DateTimeIndicator {
+         }
+ 
+         private void update_events_model (E.Source source, Gee.Collection<ECal.Component> events) {
+-            idle_update_events ();
++            if (opened_widget) {
++                idle_update_events ();
++            }
+         }
+ 
+         private void idle_update_events () {
+@@ -112,7 +137,7 @@ namespace DateTimeIndicator {
+             }
+ 
+             update_events_idle_source = GLib.Idle.add (() => {
+-                event_listbox.update_events (calendar.selected_date);
++                event_listbox.update_events (calendar.selected_date, event_manager.source_events);
+ 
+                 update_events_idle_source = 0;
+                 return GLib.Source.REMOVE;
+@@ -122,18 +147,15 @@ namespace DateTimeIndicator {
+         public override void opened () {
+             calendar.show_today ();
+ 
+-            Models.CalendarModel.get_default ().events_added.connect (update_events_model);
+-            Models.CalendarModel.get_default ().events_updated.connect (update_events_model);
+-            Models.CalendarModel.get_default ().events_removed.connect (update_events_model);
++            opened_widget = true;
+         }
+ 
+         public override void closed () {
+-            Models.CalendarModel.get_default ().events_added.disconnect (update_events_model);
+-            Models.CalendarModel.get_default ().events_updated.disconnect (update_events_model);
+-            Models.CalendarModel.get_default ().events_removed.disconnect (update_events_model);
++            opened_widget = false;
+         }
+     }
+ }
++
+ public Wingpanel.Indicator get_indicator (Module module) {
+     debug ("Activating DateTime Indicator");
+     var indicator = new DateTimeIndicator.Indicator ();
+diff --git a/src/Models/CalendarModel.vala b/src/Models/CalendarModel.vala
+index 965b93e..d60a9ac 100644
+--- a/src/Models/CalendarModel.vala
++++ b/src/Models/CalendarModel.vala
+@@ -35,17 +35,6 @@ namespace DateTimeIndicator {
+         /* The start of week, ie. Monday=1 or Sunday=7 */
+         public GLib.DateWeekday week_starts_on { get; set; }
+ 
+-        public HashTable<E.Source, Gee.TreeMultiMap<string, ECal.Component>> source_events { get; private set; }
+-
+-        /* Notifies when events are added, updated, or removed */
+-        public signal void events_added (E.Source source, Gee.Collection<ECal.Component> events);
+-        public signal void events_updated (E.Source source, Gee.Collection<ECal.Component> events);
+-        public signal void events_removed (E.Source source, Gee.Collection<ECal.Component> events);
+-
+-        private E.SourceRegistry registry { get; private set; }
+-        private HashTable<string, ECal.Client> source_client;
+-        private HashTable<string, ECal.ClientView> source_view;
+-
+         private static CalendarModel? calendar_model = null;
+ 
+         public static CalendarModel get_default () {
+@@ -59,12 +48,6 @@ namespace DateTimeIndicator {
+         }
+ 
+         construct {
+-            open.begin ();
+-
+-            source_client = new HashTable<string, ECal.Client> (str_hash, str_equal);
+-            source_events = new HashTable<E.Source, Gee.TreeMultiMap<string, ECal.Component> > (Util.source_hash_func, Util.source_equal_func);
+-            source_view = new HashTable<string, ECal.ClientView> (str_hash, str_equal);
+-
+             int week_start = Posix.NLTime.FIRST_WEEKDAY.to_string ().data[0];
+             if (week_start >= 1 && week_start <= 7) {
+                 week_starts_on = (GLib.DateWeekday) (week_start - 1);
+@@ -72,66 +55,6 @@ namespace DateTimeIndicator {
+ 
+             month_start = Util.get_start_of_month ();
+             compute_ranges ();
+-            notify["month-start"].connect (on_parameter_changed);
+-        }
+-
+-        private async void open () {
+-            try {
+-                registry = yield new E.SourceRegistry (null);
+-                registry.source_removed.connect (remove_source);
+-                registry.source_added.connect ((source) => add_source_async.begin (source));
+-
+-                // Add sources
+-                registry.list_sources (E.SOURCE_EXTENSION_CALENDAR).foreach ((source) => {
+-                    E.SourceCalendar cal = (E.SourceCalendar)source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
+-                    if (cal.selected == true && source.enabled == true) {
+-                        add_source_async.begin (source);
+-                    }
+-                });
+-
+-                load_all_sources ();
+-            } catch (GLib.Error error) {
+-                critical (error.message);
+-            }
+-        }
+-
+-        private void load_all_sources () {
+-            lock (source_client) {
+-                foreach (var id in source_client.get_keys ()) {
+-                    var source = registry.ref_source (id);
+-                    E.SourceCalendar cal = (E.SourceCalendar)source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
+-
+-                    if (cal.selected == true && source.enabled == true) {
+-                        load_source (source);
+-                    }
+-                }
+-            }
+-        }
+-
+-        private void remove_source (E.Source source) {
+-            debug ("Removing source '%s'", source.dup_display_name ());
+-            /* Already out of the model, so do nothing */
+-            unowned string uid = source.get_uid ();
+-
+-            if (!source_view.contains (uid)) {
+-                return;
+-            }
+-
+-            var current_view = source_view.get (uid);
+-            try {
+-                current_view.stop ();
+-            } catch (Error e) {
+-                warning (e.message);
+-            }
+-
+-            source_view.remove (uid);
+-            lock (source_client) {
+-                source_client.remove (uid);
+-            }
+-
+-            var events = source_events.get (source).get_values ().read_only_view;
+-            events_removed (source, events);
+-            source_events.remove (source);
+         }
+ 
+         public void change_month (int relative) {
+@@ -144,7 +67,7 @@ namespace DateTimeIndicator {
+ 
+         /* --- Helper Methods ---// */
+ 
+-        private void compute_ranges () {
++        public void compute_ranges () {
+             var month_end = month_start.add_full (0, 1, -1);
+ 
+             int dow = month_start.get_day_of_week ();
+@@ -182,150 +105,5 @@ namespace DateTimeIndicator {
+ 
+             debug (@"Date ranges: ($data_range_first <= $month_start < $month_end <= $data_range_last)");
+         }
+-
+-        private void load_source (E.Source source) {
+-            /* create empty source-event map */
+-            var events = new Gee.TreeMultiMap<string, ECal.Component> (
+-                (GLib.CompareDataFunc<ECal.Component>?) GLib.strcmp,
+-                (GLib.CompareDataFunc<ECal.Component>?) Util.calcomponent_compare_func
+-            );
+-            source_events.set (source, events);
+-            /* query client view */
+-            var iso_first = ECal.isodate_from_time_t ((time_t)data_range.first_dt.to_unix ());
+-            var iso_last = ECal.isodate_from_time_t ((time_t)data_range.last_dt.add_days (1).to_unix ());
+-            var query = @"(occur-in-time-range? (make-time \"$iso_first\") (make-time \"$iso_last\"))";
+-
+-            ECal.Client client;
+-            lock (source_client) {
+-                client = source_client.get (source.dup_uid ());
+-            }
+-
+-            if (client == null) {
+-                return;
+-            }
+-
+-            debug ("Getting client-view for source '%s'", source.dup_display_name ());
+-            client.get_view.begin (query, null, (obj, results) => {
+-                var view = on_client_view_received (results, source, client);
+-                view.objects_added.connect ((objects) => on_objects_added (source, client, objects));
+-                view.objects_removed.connect ((objects) => on_objects_removed (source, client, objects));
+-                view.objects_modified.connect ((objects) => on_objects_modified (source, client, objects));
+-                try {
+-                    view.start ();
+-                } catch (Error e) {
+-                    critical (e.message);
+-                }
+-
+-                source_view.set (source.dup_uid (), view);
+-            });
+-        }
+-
+-        private async void add_source_async (E.Source source) {
+-            debug ("Adding source '%s'", source.dup_display_name ());
+-            try {
+-                var client = (ECal.Client) ECal.Client.connect_sync (source, ECal.ClientSourceType.EVENTS, -1, null);
+-                source_client.insert (source.dup_uid (), client);
+-            } catch (Error e) {
+-                critical (e.message);
+-            }
+-
+-            Idle.add (() => {
+-                load_source (source);
+-
+-                return false;
+-            });
+-        }
+-
+-        private void debug_event (E.Source source, ECal.Component event) {
+-            unowned ICal.Component comp = event.get_icalcomponent ();
+-            debug (@"Event ['$(comp.get_summary())', $(source.dup_display_name()), $(comp.get_uid()))]");
+-        }
+-
+-        /* --- Signal Handlers ---// */
+-        private void on_parameter_changed () {
+-            compute_ranges ();
+-            load_all_sources ();
+-        }
+-
+-        private ECal.ClientView on_client_view_received (AsyncResult results, E.Source source, ECal.Client client) {
+-            ECal.ClientView view;
+-            try {
+-                debug ("Received client-view for source '%s'", source.dup_display_name ());
+-                bool status = client.get_view.end (results, out view);
+-                assert (status == true);
+-            } catch (Error e) {
+-                critical ("Error loading client-view from source '%s': %s", source.dup_display_name (), e.message);
+-            }
+-
+-            return view;
+-        }
+-
+-#if E_CAL_2_0
+-        private void on_objects_added (E.Source source, ECal.Client client, SList<ICal.Component> objects) {
+-#else
+-        private void on_objects_added (E.Source source, ECal.Client client, SList<weak ICal.Component> objects) {
+-#endif
+-            debug (@"Received $(objects.length()) added event(s) for source '%s'", source.dup_display_name ());
+-            var events = source_events.get (source);
+-            var added_events = new Gee.ArrayList<ECal.Component> ((Gee.EqualDataFunc<ECal.Component>?) Util.calcomponent_equal_func);
+-            objects.foreach ((comp) => {
+-                unowned string uid = comp.get_uid ();
+-#if E_CAL_2_0
+-                client.generate_instances_for_object_sync (comp, (time_t) data_range.first_dt.to_unix (), (time_t) data_range.last_dt.to_unix (), null, (comp, start, end) => {
+-                    var event = new ECal.Component.from_icalcomponent (comp);
+-#else
+-                client.generate_instances_for_object_sync (comp, (time_t) data_range.first_dt.to_unix (), (time_t) data_range.last_dt.to_unix (), (event, start, end) => {
+-#endif
+-                    debug_event (source, event);
+-                    events.set (uid, event);
+-                    added_events.add (event);
+-                    return true;
+-                });
+-            });
+-        }
+-
+-#if E_CAL_2_0
+-        private void on_objects_modified (E.Source source, ECal.Client client, SList<ICal.Component> objects) {
+-#else
+-        private void on_objects_modified (E.Source source, ECal.Client client, SList<weak ICal.Component> objects) {
+-#endif
+-            debug (@"Received $(objects.length()) modified event(s) for source '%s'", source.dup_display_name ());
+-            var updated_events = new Gee.ArrayList<ECal.Component> ((Gee.EqualDataFunc<ECal.Component>?) Util.calcomponent_equal_func);
+-
+-            objects.foreach ((comp) => {
+-                unowned string uid = comp.get_uid ();
+-                var events = source_events.get (source).get (uid);
+-                updated_events.add_all (events);
+-                foreach (var event in events) {
+-                    debug_event (source, event);
+-                }
+-            });
+-
+-            events_updated (source, updated_events.read_only_view);
+-        }
+-
+-#if E_CAL_2_0
+-        private void on_objects_removed (E.Source source, ECal.Client client, SList<ECal.ComponentId?> cids) {
+-#else
+-        private void on_objects_removed (E.Source source, ECal.Client client, SList<weak ECal.ComponentId?> cids) {
+-#endif
+-            debug (@"Received $(cids.length()) removed event(s) for source '%s'", source.dup_display_name ());
+-            var events = source_events.get (source);
+-            var removed_events = new Gee.ArrayList<ECal.Component> ((Gee.EqualDataFunc<ECal.Component>?) Util.calcomponent_equal_func);
+-
+-            cids.foreach ((cid) => {
+-                if (cid == null) {
+-                    return;
+-                }
+-
+-                var comps = events.get (cid.get_uid ());
+-                foreach (ECal.Component event in comps) {
+-                    removed_events.add (event);
+-                    debug_event (source, event);
+-                }
+-            });
+-
+-            events_removed (source, removed_events.read_only_view);
+-        }
+     }
+ }
+diff --git a/src/Services/EventsManager.vala b/src/Services/EventsManager.vala
+new file mode 100644
+index 0000000..d939777
+--- /dev/null
++++ b/src/Services/EventsManager.vala
+@@ -0,0 +1,226 @@
++namespace DateTimeIndicator {
++    public class Services.EventsManager : GLib.Object {
++        public signal void events_added (E.Source source, Gee.Collection<ECal.Component> events);
++        public signal void events_updated (E.Source source, Gee.Collection<ECal.Component> events);
++        public signal void events_removed (E.Source source, Gee.Collection<ECal.Component> events);
++
++        public HashTable<E.Source, Gee.TreeMultiMap<string, ECal.Component>> source_events { get; private set; }
++
++        private E.SourceRegistry registry { get; private set; }
++        private HashTable<string, ECal.Client> source_client;
++        private HashTable<string, ECal.ClientView> source_view;
++
++        public EventsManager () {
++
++        }
++
++        construct {
++            source_client = new HashTable<string, ECal.Client> (str_hash, str_equal);
++            source_events = new HashTable<E.Source, Gee.TreeMultiMap<string, ECal.Component> > (Util.source_hash_func, Util.source_equal_func);
++            source_view = new HashTable<string, ECal.ClientView> (str_hash, str_equal);
++
++            open.begin ();
++        }
++
++        private async void open () {
++            try {
++                registry = yield new E.SourceRegistry (null);
++                registry.source_removed.connect (remove_source);
++                registry.source_added.connect ((source) => add_source_async.begin (source));
++
++                // Add sources
++                registry.list_sources (E.SOURCE_EXTENSION_CALENDAR).foreach ((source) => {
++                    E.SourceCalendar cal = (E.SourceCalendar)source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
++                    if (cal.selected == true && source.enabled == true) {
++                        add_source_async.begin (source);
++                    }
++                });
++
++                load_all_sources ();
++            } catch (GLib.Error error) {
++                critical (error.message);
++            }
++        }
++
++        public void load_all_sources () {
++            lock (source_client) {
++                foreach (var id in source_client.get_keys ()) {
++                    var source = registry.ref_source (id);
++                    E.SourceCalendar cal = (E.SourceCalendar)source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
++
++                    if (cal.selected == true && source.enabled == true) {
++                        load_source (source);
++                    }
++                }
++            }
++        }
++
++        private void remove_source (E.Source source) {
++            debug ("Removing source '%s'", source.dup_display_name ());
++            /* Already out of the model, so do nothing */
++            unowned string uid = source.get_uid ();
++
++            if (!source_view.contains (uid)) {
++                return;
++            }
++
++            var current_view = source_view.get (uid);
++            try {
++                current_view.stop ();
++            } catch (Error e) {
++                warning (e.message);
++            }
++
++            source_view.remove (uid);
++            lock (source_client) {
++                source_client.remove (uid);
++            }
++
++            var events = source_events.get (source).get_values ().read_only_view;
++            events_removed (source, events);
++            source_events.remove (source);
++        }
++
++        private void load_source (E.Source source) {
++            var model = Models.CalendarModel.get_default ();
++
++            /* create empty source-event map */
++            var events = new Gee.TreeMultiMap<string, ECal.Component> (
++                (GLib.CompareDataFunc<ECal.Component>?) GLib.strcmp,
++                (GLib.CompareDataFunc<ECal.Component>?) Util.calcomponent_compare_func
++            );
++            source_events.set (source, events);
++            /* query client view */
++            var iso_first = ECal.isodate_from_time_t ((time_t) model.data_range.first_dt.to_unix ());
++            var iso_last = ECal.isodate_from_time_t ((time_t) model.data_range.last_dt.add_days (1).to_unix ());
++            var query = @"(occur-in-time-range? (make-time \"$iso_first\") (make-time \"$iso_last\"))";
++
++            ECal.Client client;
++            lock (source_client) {
++                client = source_client.get (source.dup_uid ());
++            }
++
++            if (client == null) {
++                return;
++            }
++
++            debug ("Getting client-view for source '%s'", source.dup_display_name ());
++            client.get_view.begin (query, null, (obj, results) => {
++                var view = on_client_view_received (results, source, client);
++                view.objects_added.connect ((objects) => on_objects_added (source, client, objects));
++                view.objects_removed.connect ((objects) => on_objects_removed (source, client, objects));
++                view.objects_modified.connect ((objects) => on_objects_modified (source, client, objects));
++                try {
++                    view.start ();
++                } catch (Error e) {
++                    critical (e.message);
++                }
++
++                source_view.set (source.dup_uid (), view);
++            });
++        }
++
++        private async void add_source_async (E.Source source) {
++            debug ("Adding source '%s'", source.dup_display_name ());
++            try {
++                var client = (ECal.Client) ECal.Client.connect_sync (source, ECal.ClientSourceType.EVENTS, -1, null);
++                source_client.insert (source.dup_uid (), client);
++            } catch (Error e) {
++                critical (e.message);
++            }
++
++            Idle.add (() => {
++                load_source (source);
++
++                return false;
++            });
++        }
++
++        private void debug_event (E.Source source, ECal.Component event) {
++            unowned ICal.Component comp = event.get_icalcomponent ();
++            debug (@"Event ['$(comp.get_summary())', $(source.dup_display_name()), $(comp.get_uid()))]");
++        }
++
++        private ECal.ClientView on_client_view_received (AsyncResult results, E.Source source, ECal.Client client) {
++            ECal.ClientView view;
++            try {
++                debug ("Received client-view for source '%s'", source.dup_display_name ());
++                bool status = client.get_view.end (results, out view);
++                assert (status == true);
++            } catch (Error e) {
++                critical ("Error loading client-view from source '%s': %s", source.dup_display_name (), e.message);
++            }
++
++            return view;
++        }
++
++#if E_CAL_2_0
++        private void on_objects_added (E.Source source, ECal.Client client, SList<ICal.Component> objects) {
++#else
++        private void on_objects_added (E.Source source, ECal.Client client, SList<weak ICal.Component> objects) {
++#endif
++            debug (@"Received $(objects.length()) added event(s) for source '%s'", source.dup_display_name ());
++            var events = source_events.get (source);
++            var added_events = new Gee.ArrayList<ECal.Component> ((Gee.EqualDataFunc<ECal.Component>?) Util.calcomponent_equal_func);
++            var model = Models.CalendarModel.get_default ();
++            objects.foreach ((comp) => {
++                unowned string uid = comp.get_uid ();
++#if E_CAL_2_0
++                client.generate_instances_for_object_sync (comp, (time_t) model.data_range.first_dt.to_unix (), (time_t) model.data_range.last_dt.to_unix (), null, (comp, start, end) => {
++                    var event = new ECal.Component.from_icalcomponent (comp);
++#else
++                client.generate_instances_for_object_sync (comp, (time_t) model.data_range.first_dt.to_unix (), (time_t) model.data_range.last_dt.to_unix (), (event, start, end) => {
++#endif
++                    debug_event (source, event);
++                    events.set (uid, event);
++                    added_events.add (event);
++                    return true;
++                });
++            });
++        }
++
++#if E_CAL_2_0
++        private void on_objects_modified (E.Source source, ECal.Client client, SList<ICal.Component> objects) {
++#else
++        private void on_objects_modified (E.Source source, ECal.Client client, SList<weak ICal.Component> objects) {
++#endif
++            debug (@"Received $(objects.length()) modified event(s) for source '%s'", source.dup_display_name ());
++            var updated_events = new Gee.ArrayList<ECal.Component> ((Gee.EqualDataFunc<ECal.Component>?) Util.calcomponent_equal_func);
++
++            objects.foreach ((comp) => {
++                unowned string uid = comp.get_uid ();
++                var events = source_events.get (source).get (uid);
++                updated_events.add_all (events);
++                foreach (var event in events) {
++                    debug_event (source, event);
++                }
++            });
++
++            events_updated (source, updated_events.read_only_view);
++        }
++
++#if E_CAL_2_0
++        private void on_objects_removed (E.Source source, ECal.Client client, SList<ECal.ComponentId?> cids) {
++#else
++        private void on_objects_removed (E.Source source, ECal.Client client, SList<weak ECal.ComponentId?> cids) {
++#endif
++            debug (@"Received $(cids.length()) removed event(s) for source '%s'", source.dup_display_name ());
++            var events = source_events.get (source);
++            var removed_events = new Gee.ArrayList<ECal.Component> ((Gee.EqualDataFunc<ECal.Component>?) Util.calcomponent_equal_func);
++
++            cids.foreach ((cid) => {
++                if (cid == null) {
++                    return;
++                }
++
++                var comps = events.get (cid.get_uid ());
++                foreach (ECal.Component event in comps) {
++                    removed_events.add (event);
++                    debug_event (source, event);
++                }
++            });
++
++            events_removed (source, removed_events.read_only_view);
++        }
++    }
++}
+diff --git a/src/Util/Util.vala b/src/Util/Util.vala
+index c261f4b..b0bdf98 100644
+--- a/src/Util/Util.vala
++++ b/src/Util/Util.vala
+@@ -20,47 +20,6 @@
+  */
+ 
+ namespace DateTimeIndicator.Util {
+-    static bool has_scrolled = false;
+-
+-    public bool on_scroll_event (Gdk.EventScroll event) {
+-        double delta_x;
+-        double delta_y;
+-        event.get_scroll_deltas (out delta_x, out delta_y);
+-
+-        double choice = delta_x;
+-
+-        if (((int)delta_x).abs () < ((int)delta_y).abs ()) {
+-            choice = delta_y;
+-        }
+-
+-        /* It's mouse scroll ! */
+-        if (choice == 1 || choice == -1) {
+-            Models.CalendarModel.get_default ().change_month ((int)choice);
+-
+-            return true;
+-        }
+-
+-        if (has_scrolled == true) {
+-            return true;
+-        }
+-
+-        if (choice > 0.3) {
+-            reset_timer.begin ();
+-            Models.CalendarModel.get_default ().change_month (1);
+-
+-            return true;
+-        }
+-
+-        if (choice < -0.3) {
+-            reset_timer.begin ();
+-            Models.CalendarModel.get_default ().change_month (-1);
+-
+-            return true;
+-        }
+-
+-        return false;
+-    }
+-
+     public GLib.DateTime get_start_of_month (owned GLib.DateTime? date = null) {
+         if (date == null) {
+             date = new GLib.DateTime.now_local ();
+@@ -225,12 +184,12 @@ namespace DateTimeIndicator.Util {
+         return a.dup_uid () == b.dup_uid ();
+     }
+ 
+-    public async void reset_timer () {
+-        has_scrolled = true;
+-        Timeout.add (500, () => {
+-            has_scrolled = false;
+-
+-            return false;
+-        });
+-    }
++    // public async void reset_timer () {
++    //     has_scrolled = true;
++    //     Timeout.add (500, () => {
++    //         has_scrolled = false;
++    //
++    //         return false;
++    //     });
++    // }
+ }
+diff --git a/src/Widgets/CalendarView.vala b/src/Widgets/CalendarView.vala
+index 65cee28..070580f 100644
+--- a/src/Widgets/CalendarView.vala
++++ b/src/Widgets/CalendarView.vala
+@@ -181,5 +181,14 @@ namespace DateTimeIndicator {
+ 
+             stack.set_visible_child (big_grid);
+         }
++
++        public void add_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
++            grid.add_event_dots (source, events);
++        }
++
++
++        public void remove_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
++            grid.remove_event_dots (source, events);
++        }
+     }
+ }
+diff --git a/src/Widgets/EventsListBox.vala b/src/Widgets/EventsListBox.vala
+index 547e4c5..c25af2e 100644
+--- a/src/Widgets/EventsListBox.vala
++++ b/src/Widgets/EventsListBox.vala
+@@ -1,6 +1,5 @@
+ namespace DateTimeIndicator {
+     public class Widgets.EventsListBox : Gtk.ListBox {
+-
+         public EventsListBox () {
+             selection_mode = Gtk.SelectionMode.NONE;
+ 
+@@ -22,7 +21,7 @@ namespace DateTimeIndicator {
+             set_sort_func (sort_function);
+         }
+ 
+-        public void update_events (GLib.DateTime? selected_date) {
++        public void update_events (GLib.DateTime? selected_date, HashTable<E.Source, Gee.TreeMultiMap<string, ECal.Component>> source_events) {
+             foreach (unowned Gtk.Widget widget in get_children ()) {
+                 widget.destroy ();
+             }
+@@ -31,11 +30,9 @@ namespace DateTimeIndicator {
+                 return;
+             }
+ 
+-            var model = Models.CalendarModel.get_default ();
+-
+             var events_on_day = new Gee.TreeMap<string, EventRow> ();
+ 
+-            model.source_events.@foreach ((source, component_map) => {
++            source_events.@foreach ((source, component_map) => {
+                 foreach (var comp in component_map.get_values ()) {
+                     if (Util.calcomp_is_on_day (comp, selected_date)) {
+                         unowned ICal.Component ical = comp.get_icalcomponent ();
+diff --git a/src/Widgets/Grid.vala b/src/Widgets/Grid.vala
+index 165d11d..660f212 100644
+--- a/src/Widgets/Grid.vala
++++ b/src/Widgets/Grid.vala
+@@ -261,5 +261,36 @@ namespace DateTimeIndicator {
+             return date.get_year () * 10000 + date.get_month () * 100 + date.get_day_of_month ();
+         }
+ 
++        public void add_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
++            data.foreach ((entry) => {
++
++                foreach (var component in events) {
++                    if (entry.value.skip_day ()) {
++                        return true;
++                    }
++
++                    if (Util.calcomp_is_on_day (component, entry.value.date)) {
++                        entry.value.add_dots (source, component.get_icalcomponent ());
++                    }
++                }
++
++                entry.value.show_event_grid ();
++
++                return true;
++            });
++        }
++
++        public void remove_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
++            foreach (var component in events) {
++                unowned ICal.Component ical = component.get_icalcomponent ();
++                var event_uid = ical.get_uid ();
++                data.foreach ((entry) => {
++                    if (entry.value.exist_event (event_uid)) {
++                        entry.value.remove_dots (event_uid);
++                    }
++                    return true;
++                });
++            }
++        }
+     }
+ }
+diff --git a/src/Widgets/GridDay.vala b/src/Widgets/GridDay.vala
+index 8c44443..00f82ea 100644
+--- a/src/Widgets/GridDay.vala
++++ b/src/Widgets/GridDay.vala
+@@ -31,6 +31,8 @@ namespace DateTimeIndicator {
+ 
+         public GLib.DateTime date { get; construct set; }
+ 
++        private bool has_scrolled = false;
++
+         private static Gtk.CssProvider provider;
+         private static Models.CalendarModel model;
+ 
+@@ -79,59 +81,100 @@ namespace DateTimeIndicator {
+             // Signals and handlers
+             button_press_event.connect (on_button_press);
+             key_press_event.connect (on_key_press);
+-            scroll_event.connect ((event) => {return Util.on_scroll_event (event);});
++            scroll_event.connect (on_scroll_event);
+ 
+             notify["date"].connect (() => {
+                 label.label = date.get_day_of_month ().to_string ();
+             });
+ 
+             event_dots = new Gee.HashMap<string, Gtk.Widget> ();
+-
+-            model.events_added.connect (add_event_dots);
+-            model.events_removed.connect (remove_event_dots);
+         }
+ 
+-        private void add_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+-            foreach (var component in events) {
+-                if (event_dots.size >= 3) {
+-                    return;
+-                }
++        public bool on_scroll_event (Gdk.EventScroll event) {
++            double delta_x;
++            double delta_y;
++            event.get_scroll_deltas (out delta_x, out delta_y);
++
++            double choice = delta_x;
++
++            if (((int)delta_x).abs () < ((int)delta_y).abs ()) {
++                choice = delta_y;
++            }
++
++            /* It's mouse scroll ! */
++            if (choice == 1 || choice == -1) {
++                Models.CalendarModel.get_default ().change_month ((int)choice);
+ 
+-                if (Util.calcomp_is_on_day (component, date)) {
+-                    unowned ICal.Component ical = component.get_icalcomponent ();
++                return true;
++            }
+ 
+-                    var event_uid = ical.get_uid ();
+-                    if (!event_dots.has_key (event_uid)) {
+-                        var event_dot = new Gtk.Image ();
+-                        event_dot.gicon = new ThemedIcon ("pager-checked-symbolic");
+-                        event_dot.pixel_size = 6;
++            if (has_scrolled == true) {
++                return true;
++            }
+ 
+-                        unowned Gtk.StyleContext style_context = event_dot.get_style_context ();
+-                        style_context.add_class (Granite.STYLE_CLASS_ACCENT);
+-                        style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++            if (choice > 0.3) {
++                reset_timer.begin ();
++                Models.CalendarModel.get_default ().change_month (1);
+ 
+-                        var source_calendar = (E.SourceCalendar?) source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
+-                        Util.set_event_calendar_color (source_calendar, event_dot);
++                return true;
++            }
+ 
+-                        event_dots[event_uid] = event_dot;
++            if (choice < -0.3) {
++                reset_timer.begin ();
++                Models.CalendarModel.get_default ().change_month (-1);
+ 
+-                        event_grid.add (event_dot);
+-                    }
+-                }
++                return true;
+             }
+ 
++            return false;
++        }
++
++        public async void reset_timer () {
++            has_scrolled = true;
++            Timeout.add (500, () => {
++                has_scrolled = false;
++
++                return false;
++            });
++        }
++
++        public bool skip_day () {
++            return event_dots.size >= 3 ? true : false;
++        }
++
++        public void show_event_grid () {
+             event_grid.show_all ();
+         }
+ 
+-        private void remove_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+-            foreach (var component in events) {
+-                unowned ICal.Component ical = component.get_icalcomponent ();
+-                var event_uid = ical.get_uid ();
+-                var dot = event_dots[event_uid];
+-                if (dot != null) {
+-                    dot.destroy ();
+-                    event_dots.remove (event_uid);
+-                }
++        public void add_dots (E.Source source, ICal.Component ical) {
++            var event_uid = ical.get_uid ();
++            if (!event_dots.has_key (event_uid)) {
++                var event_dot = new Gtk. Image ();
++                event_dot.gicon = new ThemedIcon ("pager-checked-symbolic");
++                event_dot.pixel_size = 6;
++
++                unowned Gtk.StyleContext style_context = event_dot.get_style_context ();
++                style_context.add_class (Granite.STYLE_CLASS_ACCENT);
++                style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++
++                var source_calendar = (E.SourceCalendar?) source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
++                Util.set_event_calendar_color (source_calendar, event_dot);
++
++                event_dots[event_uid] = event_dot;
++
++                event_grid.add (event_dot);
++            }
++        }
++
++        public bool exist_event (string ical_uid) {
++            return event_dots.has_key (ical_uid);
++        }
++
++        public void remove_dots (string event_uid) {
++            var dot = event_dots[event_uid];
++            if (dot != null) {
++                dot.destroy ();
++                event_dots.unset (event_uid);
+             }
+         }
+ 
+
+From c99db5e63b9c48aa1d069b97b98b0c07481de27d Mon Sep 17 00:00:00 2001
+From: Dirli <litandrej85@gmail.com>
+Date: Sat, 11 Apr 2020 23:44:14 +0300
+Subject: [PATCH 4/8] rename calendar elements
+
+---
+ meson.build                                   |  4 +--
+ .../{GridDay.vala => CalendarDay.vala}        |  4 +--
+ src/Widgets/{Grid.vala => CalendarGrid.vala}  | 26 +++++++--------
+ src/Widgets/CalendarView.vala                 | 33 +++++++++----------
+ 4 files changed, 33 insertions(+), 34 deletions(-)
+ rename src/Widgets/{GridDay.vala => CalendarDay.vala} (98%)
+ rename src/Widgets/{Grid.vala => CalendarGrid.vala} (91%)
+
+diff --git a/meson.build b/meson.build
+index e348e3d..dcdd9b2 100644
+--- a/meson.build
++++ b/meson.build
+@@ -44,11 +44,11 @@ shared_module(
+     'src/Util/DateRange.vala',
+     'src/Util/Util.vala',
+     'src/Models/CalendarModel.vala',
++    'src/Widgets/CalendarDay.vala',
++    'src/Widgets/CalendarGrid.vala',
+     'src/Widgets/CalendarView.vala',
+     'src/Widgets/EventRow.vala',
+     'src/Widgets/EventsListBox.vala',
+-    'src/Widgets/Grid.vala',
+-    'src/Widgets/GridDay.vala',
+     'src/Widgets/PanelLabel.vala',
+     'src/Services/EventsManager.vala',
+     'src/Services/TimeManager.vala',
+diff --git a/src/Widgets/GridDay.vala b/src/Widgets/CalendarDay.vala
+similarity index 98%
+rename from src/Widgets/GridDay.vala
+rename to src/Widgets/CalendarDay.vala
+index 00f82ea..a5ca920 100644
+--- a/src/Widgets/GridDay.vala
++++ b/src/Widgets/CalendarDay.vala
+@@ -23,7 +23,7 @@ namespace DateTimeIndicator {
+ /**
+  * Represents a single day on the grid.
+  */
+-    public class Widgets.GridDay : Gtk.EventBox {
++    public class Widgets.CalendarDay : Gtk.EventBox {
+         /*
+          * Event emitted when the day is double clicked or the ENTER key is pressed.
+          */
+@@ -41,7 +41,7 @@ namespace DateTimeIndicator {
+         private Gtk.Label label;
+         private bool valid_grab = false;
+ 
+-        public GridDay (GLib.DateTime date) {
++        public CalendarDay (GLib.DateTime date) {
+             Object (date: date);
+         }
+ 
+diff --git a/src/Widgets/Grid.vala b/src/Widgets/CalendarGrid.vala
+similarity index 91%
+rename from src/Widgets/Grid.vala
+rename to src/Widgets/CalendarGrid.vala
+index 660f212..66e2757 100644
+--- a/src/Widgets/Grid.vala
++++ b/src/Widgets/CalendarGrid.vala
+@@ -24,7 +24,7 @@ namespace DateTimeIndicator {
+ /**
+  * Represents the entire date grid as a table.
+  */
+-    public class Widgets.Grid : Gtk.Grid {
++    public class Widgets.CalendarGrid : Gtk.Grid {
+         public Util.DateRange grid_range { get; private set; }
+ 
+         /*
+@@ -34,8 +34,8 @@ namespace DateTimeIndicator {
+ 
+         public signal void selection_changed (GLib.DateTime new_date);
+ 
+-        private Gee.HashMap<uint, GridDay> data;
+-        private GridDay selected_gridday;
++        private Gee.HashMap<uint, Widgets.CalendarDay> data;
++        private Widgets.CalendarDay selected_gridday;
+         private Gtk.Label[] header_labels;
+         private Gtk.Revealer[] week_labels;
+ 
+@@ -61,12 +61,12 @@ namespace DateTimeIndicator {
+ 
+             Indicator.settings.bind ("show-weeks", week_sep_revealer, "reveal-child", GLib.SettingsBindFlags.DEFAULT);
+ 
+-            data = new Gee.HashMap<uint, GridDay> ();
++            data = new Gee.HashMap<uint, Widgets.CalendarDay> ();
+             events |= Gdk.EventMask.SCROLL_MASK;
+             events |= Gdk.EventMask.SMOOTH_SCROLL_MASK;
+         }
+ 
+-        private void on_day_focus_in (GridDay day) {
++        private void on_day_focus_in (Widgets.CalendarDay day) {
+             debug ("on_day_focus_in %s", day.date.to_string ());
+             if (selected_gridday != null) {
+                 selected_gridday.set_selected (false);
+@@ -94,7 +94,7 @@ namespace DateTimeIndicator {
+             Gee.List<GLib.DateTime> dates = grid_range.to_list ();
+             for (int i = 0; i < dates.size; i++) {
+                 var date = dates[i];
+-                GridDay? day = data[day_hash (date)];
++                Widgets.CalendarDay? day = data[day_hash (date)];
+                 if (day != null && day.name == "today") {
+                     day.grab_focus_force ();
+                     return;
+@@ -119,7 +119,7 @@ namespace DateTimeIndicator {
+ 
+             var new_dates = new_range.to_list ();
+ 
+-            var data_new = new Gee.HashMap<uint, GridDay> ();
++            var data_new = new Gee.HashMap<uint, Widgets.CalendarDay> ();
+ 
+             /* Assert that a valid number of weeks should be displayed */
+             assert (new_dates.size % 7 == 0);
+@@ -138,7 +138,7 @@ namespace DateTimeIndicator {
+ 
+             for (i = 0; i < new_dates.size; i++) {
+                 var new_date = new_dates[i];
+-                GridDay day;
++                Widgets.CalendarDay day;
+ 
+                 if (i < old_dates.size) {
+                     /* A widget already exists for this date, just change it */
+@@ -147,7 +147,7 @@ namespace DateTimeIndicator {
+                     day = update_day (data[day_hash (old_date)], new_date, today, month_start);
+                 } else {
+                     /* Still update_day to get the color of etc. right */
+-                    day = update_day (new GridDay (new_date), new_date, today, month_start);
++                    day = update_day (new Widgets.CalendarDay (new_date), new_date, today, month_start);
+                     day.on_event_add.connect ((date) => on_event_add (date));
+                     day.scroll_event.connect ((event) => { scroll_event (event); return false; });
+                     day.focus_in_event.connect ((event) => {
+@@ -182,9 +182,9 @@ namespace DateTimeIndicator {
+         }
+ 
+         /**
+-         * Updates the given GridDay so that it shows the given date. Changes to its style etc.
++         * Updates the given CalendarDay so that it shows the given date. Changes to its style etc.
+          */
+-        private GridDay update_day (GridDay day, GLib.DateTime new_date, GLib.DateTime today, GLib.DateTime month_start) {
++        private Widgets.CalendarDay update_day (Widgets.CalendarDay day, GLib.DateTime new_date, GLib.DateTime today, GLib.DateTime month_start) {
+             update_today_style (day, new_date, today);
+             if (new_date.get_month () == month_start.get_month ()) {
+                 day.sensitive_container (true);
+@@ -237,13 +237,13 @@ namespace DateTimeIndicator {
+             int i = 0;
+             for (i = 0; i < dates.size; i++) {
+                 var date = dates[i];
+-                GridDay? day = data[day_hash (date)];
++                Widgets.CalendarDay? day = data[day_hash (date)];
+                 if (day == null) return;
+                 update_today_style (day, date, today);
+             }
+         }
+ 
+-        private void update_today_style (GridDay day, GLib.DateTime date, GLib.DateTime today) {
++        private void update_today_style (Widgets.CalendarDay day, GLib.DateTime date, GLib.DateTime today) {
+             if (date.get_day_of_year () == today.get_day_of_year () && date.get_year () == today.get_year ()) {
+                 day.name = "today";
+                 day.get_style_context ().add_class (Granite.STYLE_CLASS_ACCENT);
+diff --git a/src/Widgets/CalendarView.vala b/src/Widgets/CalendarView.vala
+index 070580f..db2139c 100644
+--- a/src/Widgets/CalendarView.vala
++++ b/src/Widgets/CalendarView.vala
+@@ -26,7 +26,7 @@ namespace DateTimeIndicator {
+ 
+         public GLib.DateTime? selected_date { get; private set; }
+ 
+-        private Widgets.Grid grid;
++        private Widgets.CalendarGrid calendar_grid;
+         private Gtk.Stack stack;
+         private Gtk.Grid big_grid;
+ 
+@@ -105,20 +105,20 @@ namespace DateTimeIndicator {
+         }
+ 
+         private Gtk.Grid create_big_grid () {
+-            grid = new Widgets.Grid ();
+-            grid.show_all ();
++            calendar_grid = new Widgets.CalendarGrid ();
++            calendar_grid.show_all ();
+ 
+-            grid.on_event_add.connect ((date) => {
++            calendar_grid.on_event_add.connect ((date) => {
+                 show_date_in_maya (date);
+                 day_double_click ();
+             });
+ 
+-            grid.selection_changed.connect ((date) => {
++            calendar_grid.selection_changed.connect ((date) => {
+                 selected_date = date;
+                 selection_changed (date);
+             });
+ 
+-            return grid;
++            return calendar_grid;
+         }
+ 
+         public void show_today () {
+@@ -131,7 +131,7 @@ namespace DateTimeIndicator {
+             }
+             sync_with_model ();
+ 
+-            grid.set_focus_to_today ();
++            calendar_grid.set_focus_to_today ();
+         }
+ 
+         // TODO: As far as maya supports it use the Dbus Activation feature to run the calendar-app.
+@@ -156,23 +156,23 @@ namespace DateTimeIndicator {
+         /* Sets the calendar widgets to the date range of the model */
+         private void sync_with_model () {
+             var model = Models.CalendarModel.get_default ();
+-            if (grid.grid_range != null && (model.data_range.equals (grid.grid_range) || grid.grid_range.first_dt.compare (model.data_range.first_dt) == 0)) {
+-                grid.update_today ();
++            if (calendar_grid.grid_range != null && (model.data_range.equals (calendar_grid.grid_range) || calendar_grid.grid_range.first_dt.compare (model.data_range.first_dt) == 0)) {
++                calendar_grid.update_today ();
+                 return; // nothing else to do
+             }
+ 
+             GLib.DateTime previous_first = null;
+-            if (grid.grid_range != null)
+-                previous_first = grid.grid_range.first_dt;
++            if (calendar_grid.grid_range != null)
++                previous_first = calendar_grid.grid_range.first_dt;
+ 
+             big_grid = create_big_grid ();
+             stack.add (big_grid);
+ 
+-            grid.set_range (model.data_range, model.month_start);
+-            grid.update_weeks (model.data_range.first_dt, model.num_weeks);
++            calendar_grid.set_range (model.data_range, model.month_start);
++            calendar_grid.update_weeks (model.data_range.first_dt, model.num_weeks);
+ 
+             if (previous_first != null) {
+-                if (previous_first.compare (grid.grid_range.first_dt) == -1) {
++                if (previous_first.compare (calendar_grid.grid_range.first_dt) == -1) {
+                     stack.transition_type = Gtk.StackTransitionType.SLIDE_LEFT;
+                 } else {
+                     stack.transition_type = Gtk.StackTransitionType.SLIDE_RIGHT;
+@@ -183,12 +183,11 @@ namespace DateTimeIndicator {
+         }
+ 
+         public void add_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+-            grid.add_event_dots (source, events);
++            calendar_grid.add_event_dots (source, events);
+         }
+ 
+-
+         public void remove_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+-            grid.remove_event_dots (source, events);
++            calendar_grid.remove_event_dots (source, events);
+         }
+     }
+ }
+
+From 6b1b15305a9c90ff8b7e7244727a225984d34a14 Mon Sep 17 00:00:00 2001
+From: Dirli <litandrej85@gmail.com>
+Date: Sun, 12 Apr 2020 03:10:24 +0300
+Subject: [PATCH 5/8] evolution data server now optional
+
+---
+ meson.build                   | 55 ++++++++++++++---------
+ meson_options.txt             |  1 +
+ src/Indicator.vala            | 43 ++++++++++--------
+ src/Util/Util.vala            | 85 ++++++++++++++++-------------------
+ src/Widgets/CalendarDay.vala  |  2 +
+ src/Widgets/CalendarGrid.vala |  2 +
+ src/Widgets/CalendarView.vala |  2 +
+ 7 files changed, 104 insertions(+), 86 deletions(-)
+ create mode 100644 meson_options.txt
+
+diff --git a/meson.build b/meson.build
+index dcdd9b2..5fee9ab 100644
+--- a/meson.build
++++ b/meson.build
+@@ -24,18 +24,40 @@ gresource = gnome.compile_resources(
+ wingpanel_dep = dependency('wingpanel-2.0')
+ wingpanel_indicatorsdir = wingpanel_dep.get_pkgconfig_variable('indicatorsdir', define_variable: ['libdir', libdir])
+ 
++deps = [
++    dependency('glib-2.0'),
++    dependency('gobject-2.0'),
++    dependency('granite'),
++    dependency('gtk+-3.0'),
++    wingpanel_dep,
++    meson.get_compiler('vala').find_library('posix')
++]
+ 
+-libecal_dep = dependency('libecal-2.0', required: false)
+-if libecal_dep.found()
+-    libical_dep = dependency('libical-glib')
+-    add_project_arguments('--define=E_CAL_2_0', language: 'vala')
+-    add_project_arguments('-DLIBICAL_GLIB_UNSTABLE_API=1', language: 'c')
+-else
+-    libecal_dep = dependency('libecal-1.2', version: '>=3.8.0')
+-    libical_dep = dependency('libical')
+-    add_project_arguments('--vapidir', join_paths(meson.current_source_dir(), 'vapi'), language: 'vala')
++opt_files = []
++
++if get_option('evo')
++    libecal_dep = dependency ('libecal-2.0', required: false)
++    if libecal_dep.found()
++        deps += dependency('libical-glib')
++        deps += libecal_dep
++        add_project_arguments('--define=E_CAL_2_0', language: 'vala')
++        add_project_arguments('-DLIBICAL_GLIB_UNSTABLE_API=1', language: 'c')
++    else
++        deps += dependency('libecal-1.2', version: '>=3.8.0')
++        deps += dependency('libical')
++        add_project_arguments('--vapidir', join_paths(meson.current_source_dir(), 'vapi'), language: 'vala')
++    endif
++
++    deps += dependency ('libedataserver-1.2')
++    opt_files += files (
++        'src/Widgets/EventRow.vala',
++        'src/Widgets/EventsListBox.vala',
++        'src/Services/EventsManager.vala',
++    )
++    add_project_arguments('--define=USE_EVO', language: 'vala')
+ endif
+ 
++
+ shared_module(
+     meson.project_name(),
+     gresource,
+@@ -47,21 +69,10 @@ shared_module(
+     'src/Widgets/CalendarDay.vala',
+     'src/Widgets/CalendarGrid.vala',
+     'src/Widgets/CalendarView.vala',
+-    'src/Widgets/EventRow.vala',
+-    'src/Widgets/EventsListBox.vala',
+     'src/Widgets/PanelLabel.vala',
+-    'src/Services/EventsManager.vala',
+     'src/Services/TimeManager.vala',
+-    dependencies: [
+-        dependency('glib-2.0'),
+-        dependency('gobject-2.0'),
+-        dependency('granite'),
+-        dependency('gtk+-3.0'),
+-        libecal_dep,
+-        dependency('libedataserver-1.2'),
+-        libical_dep,
+-        wingpanel_dep,
+-    ],
++    opt_files,
++    dependencies: deps,
+     install: true,
+     install_dir : wingpanel_indicatorsdir,
+ )
+diff --git a/meson_options.txt b/meson_options.txt
+new file mode 100644
+index 0000000..a1c9f0e
+--- /dev/null
++++ b/meson_options.txt
+@@ -0,0 +1 @@
++option ('evo', type : 'boolean', value : true)
+diff --git a/src/Indicator.vala b/src/Indicator.vala
+index bf4358f..31c7af5 100644
+--- a/src/Indicator.vala
++++ b/src/Indicator.vala
+@@ -23,9 +23,11 @@ namespace DateTimeIndicator {
+ 
+         private Widgets.PanelLabel panel_label;
+         private Widgets.CalendarView calendar;
+-        private Widgets.EventsListBox event_listbox;
+ 
++#if USE_EVO
++        private Widgets.EventsListBox event_listbox;
+         private Services.EventsManager event_manager;
++#endif
+ 
+         private Gtk.Grid main_grid;
+         private uint update_events_idle_source = 0;
+@@ -60,7 +62,20 @@ namespace DateTimeIndicator {
+             if (main_grid == null) {
+                 calendar = new Widgets.CalendarView ();
+                 calendar.margin_bottom = 6;
++                calendar.day_double_click.connect (() => {
++                    close ();
++                });
++
++                var settings_button = new Gtk.ModelButton ();
++                settings_button.text = _("Date & Time Settings…");
+ 
++                main_grid = new Gtk.Grid ();
++                main_grid.margin_top = 12;
++                main_grid.attach (calendar,                           0, 0);
++                main_grid.attach (new Wingpanel.Widgets.Separator (), 0, 1);
++                main_grid.attach (settings_button,                    0, 2);
++
++#if USE_EVO
+                 event_manager = new Services.EventsManager ();
+                 event_manager.events_updated.connect (update_events_model);
+                 event_manager.events_added.connect ((source, events) => {
+@@ -73,20 +88,16 @@ namespace DateTimeIndicator {
+                 });
+ 
+                 event_listbox = new Widgets.EventsListBox ();
++                event_listbox.row_activated.connect ((row) => {
++                    calendar.show_date_in_maya (((EventRow) row).date);
++                    close ();
++                });
+ 
+                 var scrolled_window = new Gtk.ScrolledWindow (null, null);
+                 scrolled_window.hscrollbar_policy = Gtk.PolicyType.NEVER;
+                 scrolled_window.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC;
+                 scrolled_window.add (event_listbox);
+ 
+-                var settings_button = new Gtk.ModelButton ();
+-                settings_button.text = _("Date & Time Settings…");
+-
+-                main_grid = new Gtk.Grid ();
+-                main_grid.margin_top = 12;
+-                main_grid.attach (calendar,                                     0, 0);
+-                main_grid.attach (new Wingpanel.Widgets.Separator (),           0, 1);
+-                main_grid.attach (settings_button,                              0, 2);
+                 main_grid.attach (new Gtk.Separator (Gtk.Orientation.VERTICAL), 1, 0, 1, 3);
+                 main_grid.attach (scrolled_window,                              2, 0, 1, 3);
+ 
+@@ -94,23 +105,17 @@ namespace DateTimeIndicator {
+                 size_group.add_widget (calendar);
+                 size_group.add_widget (event_listbox);
+ 
+-                calendar.day_double_click.connect (() => {
+-                    close ();
+-                });
+-
+                 calendar.selection_changed.connect ((date) => {
+                     idle_update_events ();
+                 });
+-
+-                event_listbox.row_activated.connect ((row) => {
+-                    calendar.show_date_in_maya (((EventRow) row).date);
+-                    close ();
+-                });
++#endif
+ 
+                 var model = Models.CalendarModel.get_default ();
+                 model.notify["month-start"].connect (() => {
+                     model.compute_ranges ();
++#if USE_EVO
+                     event_manager.load_all_sources ();
++#endif
+                 });
+ 
+                 settings_button.clicked.connect (() => {
+@@ -125,6 +130,7 @@ namespace DateTimeIndicator {
+             return main_grid;
+         }
+ 
++#if USE_EVO
+         private void update_events_model (E.Source source, Gee.Collection<ECal.Component> events) {
+             if (opened_widget) {
+                 idle_update_events ();
+@@ -143,6 +149,7 @@ namespace DateTimeIndicator {
+                 return GLib.Source.REMOVE;
+             });
+         }
++#endif
+ 
+         public override void opened () {
+             calendar.show_today ();
+diff --git a/src/Util/Util.vala b/src/Util/Util.vala
+index b0bdf98..26b343a 100644
+--- a/src/Util/Util.vala
++++ b/src/Util/Util.vala
+@@ -32,38 +32,6 @@ namespace DateTimeIndicator.Util {
+         return datetime.add_full (0, 0, 0, -datetime.get_hour (), -datetime.get_minute (), -datetime.get_second ());
+     }
+ 
+-    /**
+-     * Converts the given ICal.Time to a DateTime.
+-     */
+-    public TimeZone timezone_from_ical (ICal.Time date) {
+-        int is_daylight;
+-        var interval = date.get_timezone ().get_utc_offset (null, out is_daylight);
+-        bool is_positive = interval >= 0;
+-        interval = interval.abs ();
+-        var hours = (interval / 3600);
+-        var minutes = (interval % 3600) / 60;
+-        var hour_string = "%s%02d:%02d".printf (is_positive ? "+" : "-", hours, minutes);
+-
+-        return new TimeZone (hour_string);
+-    }
+-
+-    /**
+-     * Converts the given ICal.Time to a DateTime.
+-     * XXX : Track next versions of evolution in order to convert ICal.Timezone to GLib.TimeZone with a dedicated function…
+-     */
+-    public GLib.DateTime ical_to_date_time (ICal.Time date) {
+-#if E_CAL_2_0
+-        int year, month, day, hour, minute, second;
+-        date.get_date (out year, out month, out day);
+-        date.get_time (out hour, out minute, out second);
+-        return new GLib.DateTime (timezone_from_ical (date), year, month,
+-            day, hour, minute, second);
+-#else
+-        return new GLib.DateTime (timezone_from_ical (date), date.year, date.month,
+-            date.day, date.hour, date.minute, date.second);
+-#endif
+-    }
+-
+     /**
+      * Say if an event lasts all day.
+      */
+@@ -78,6 +46,7 @@ namespace DateTimeIndicator.Util {
+         }
+     }
+ 
++#if USE_EVO
+     private Gee.HashMap<string, Gtk.CssProvider>? providers;
+     public void set_event_calendar_color (E.SourceCalendar cal, Gtk.Widget widget) {
+         if (providers == null) {
+@@ -104,6 +73,38 @@ namespace DateTimeIndicator.Util {
+         style_context.add_provider (providers[color], Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+     }
+ 
++    /**
++     * Converts the given ICal.Time to a DateTime.
++     */
++    public TimeZone timezone_from_ical (ICal.Time date) {
++        int is_daylight;
++        var interval = date.get_timezone ().get_utc_offset (null, out is_daylight);
++        bool is_positive = interval >= 0;
++        interval = interval.abs ();
++        var hours = (interval / 3600);
++        var minutes = (interval % 3600) / 60;
++        var hour_string = "%s%02d:%02d".printf (is_positive ? "+" : "-", hours, minutes);
++
++        return new TimeZone (hour_string);
++    }
++
++    /**
++     * Converts the given ICal.Time to a DateTime.
++     * XXX : Track next versions of evolution in order to convert ICal.Timezone to GLib.TimeZone with a dedicated function…
++     */
++    public GLib.DateTime ical_to_date_time (ICal.Time date) {
++#if E_CAL_2_0
++        int year, month, day, hour, minute, second;
++        date.get_date (out year, out month, out day);
++        date.get_time (out hour, out minute, out second);
++        return new GLib.DateTime (timezone_from_ical (date), year, month,
++            day, hour, minute, second);
++#else
++        return new GLib.DateTime (timezone_from_ical (date), date.year, date.month,
++            date.day, date.hour, date.minute, date.second);
++#endif
++    }
++
+     /*
+      * Gee Utility Functions
+      */
+@@ -113,6 +114,11 @@ namespace DateTimeIndicator.Util {
+         return key.dup_uid (). hash ();
+     }
+ 
++    /* Returns true if 'a' and 'b' are the same E.Source */
++    public bool source_equal_func (E.Source a, E.Source b) {
++        return a.dup_uid () == b.dup_uid ();
++    }
++
+     /* Returns true if 'a' and 'b' are the same ECal.Component */
+     public bool calcomponent_equal_func (ECal.Component a, ECal.Component b) {
+         return a.get_id ().equal (b.get_id ());
+@@ -178,18 +184,5 @@ namespace DateTimeIndicator.Util {
+ 
+         return false;
+     }
+-
+-    /* Returns true if 'a' and 'b' are the same E.Source */
+-    public bool source_equal_func (E.Source a, E.Source b) {
+-        return a.dup_uid () == b.dup_uid ();
+-    }
+-
+-    // public async void reset_timer () {
+-    //     has_scrolled = true;
+-    //     Timeout.add (500, () => {
+-    //         has_scrolled = false;
+-    //
+-    //         return false;
+-    //     });
+-    // }
++#endif
+ }
+diff --git a/src/Widgets/CalendarDay.vala b/src/Widgets/CalendarDay.vala
+index a5ca920..f9c742a 100644
+--- a/src/Widgets/CalendarDay.vala
++++ b/src/Widgets/CalendarDay.vala
+@@ -146,6 +146,7 @@ namespace DateTimeIndicator {
+             event_grid.show_all ();
+         }
+ 
++#if USE_EVO
+         public void add_dots (E.Source source, ICal.Component ical) {
+             var event_uid = ical.get_uid ();
+             if (!event_dots.has_key (event_uid)) {
+@@ -177,6 +178,7 @@ namespace DateTimeIndicator {
+                 event_dots.unset (event_uid);
+             }
+         }
++#endif
+ 
+         public void set_selected (bool selected) {
+             if (selected) {
+diff --git a/src/Widgets/CalendarGrid.vala b/src/Widgets/CalendarGrid.vala
+index 66e2757..1d1e06c 100644
+--- a/src/Widgets/CalendarGrid.vala
++++ b/src/Widgets/CalendarGrid.vala
+@@ -261,6 +261,7 @@ namespace DateTimeIndicator {
+             return date.get_year () * 10000 + date.get_month () * 100 + date.get_day_of_month ();
+         }
+ 
++#if USE_EVO
+         public void add_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+             data.foreach ((entry) => {
+ 
+@@ -292,5 +293,6 @@ namespace DateTimeIndicator {
+                 });
+             }
+         }
++#endif
+     }
+ }
+diff --git a/src/Widgets/CalendarView.vala b/src/Widgets/CalendarView.vala
+index db2139c..77c233c 100644
+--- a/src/Widgets/CalendarView.vala
++++ b/src/Widgets/CalendarView.vala
+@@ -182,6 +182,7 @@ namespace DateTimeIndicator {
+             stack.set_visible_child (big_grid);
+         }
+ 
++#if USE_EVO
+         public void add_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+             calendar_grid.add_event_dots (source, events);
+         }
+@@ -189,5 +190,6 @@ namespace DateTimeIndicator {
+         public void remove_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+             calendar_grid.remove_event_dots (source, events);
+         }
++#endif
+     }
+ }
+
+From 16715f5114c0597d6961880bf877f04414400334 Mon Sep 17 00:00:00 2001
+From: Dirli <litandrej85@gmail.com>
+Date: Mon, 13 Apr 2020 22:21:07 +0300
+Subject: [PATCH 6/8] returned dots in the calendar
+
+---
+ src/Indicator.vala              |  6 ++--
+ src/Services/EventsManager.vala | 28 +++++----------
+ src/Widgets/CalendarDay.vala    | 62 ++++++++++++++++++---------------
+ src/Widgets/CalendarGrid.vala   | 29 ++++++---------
+ 4 files changed, 57 insertions(+), 68 deletions(-)
+
+diff --git a/src/Indicator.vala b/src/Indicator.vala
+index 31c7af5..c7550aa 100644
+--- a/src/Indicator.vala
++++ b/src/Indicator.vala
+@@ -105,8 +105,10 @@ namespace DateTimeIndicator {
+                 size_group.add_widget (calendar);
+                 size_group.add_widget (event_listbox);
+ 
+-                calendar.selection_changed.connect ((date) => {
+-                    idle_update_events ();
++                event_manager.open.begin ((obj, res) => {
++                    calendar.selection_changed.connect ((date) => {
++                        idle_update_events ();
++                    });
+                 });
+ #endif
+ 
+diff --git a/src/Services/EventsManager.vala b/src/Services/EventsManager.vala
+index d939777..ad0397d 100644
+--- a/src/Services/EventsManager.vala
++++ b/src/Services/EventsManager.vala
+@@ -10,33 +10,25 @@ namespace DateTimeIndicator {
+         private HashTable<string, ECal.Client> source_client;
+         private HashTable<string, ECal.ClientView> source_view;
+ 
+-        public EventsManager () {
+-
+-        }
+-
+         construct {
+             source_client = new HashTable<string, ECal.Client> (str_hash, str_equal);
+             source_events = new HashTable<E.Source, Gee.TreeMultiMap<string, ECal.Component> > (Util.source_hash_func, Util.source_equal_func);
+             source_view = new HashTable<string, ECal.ClientView> (str_hash, str_equal);
+-
+-            open.begin ();
+         }
+ 
+-        private async void open () {
++        public async void open () {
+             try {
+                 registry = yield new E.SourceRegistry (null);
+                 registry.source_removed.connect (remove_source);
+-                registry.source_added.connect ((source) => add_source_async.begin (source));
++                registry.source_added.connect (add_source);
+ 
+                 // Add sources
+                 registry.list_sources (E.SOURCE_EXTENSION_CALENDAR).foreach ((source) => {
+-                    E.SourceCalendar cal = (E.SourceCalendar)source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
++                    E.SourceCalendar cal = (E.SourceCalendar) source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
+                     if (cal.selected == true && source.enabled == true) {
+-                        add_source_async.begin (source);
++                        add_source (source);
+                     }
+                 });
+-
+-                load_all_sources ();
+             } catch (GLib.Error error) {
+                 critical (error.message);
+             }
+@@ -46,7 +38,7 @@ namespace DateTimeIndicator {
+             lock (source_client) {
+                 foreach (var id in source_client.get_keys ()) {
+                     var source = registry.ref_source (id);
+-                    E.SourceCalendar cal = (E.SourceCalendar)source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
++                    E.SourceCalendar cal = (E.SourceCalendar) source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
+ 
+                     if (cal.selected == true && source.enabled == true) {
+                         load_source (source);
+@@ -120,7 +112,7 @@ namespace DateTimeIndicator {
+             });
+         }
+ 
+-        private async void add_source_async (E.Source source) {
++        private void add_source (E.Source source) {
+             debug ("Adding source '%s'", source.dup_display_name ());
+             try {
+                 var client = (ECal.Client) ECal.Client.connect_sync (source, ECal.ClientSourceType.EVENTS, -1, null);
+@@ -129,11 +121,7 @@ namespace DateTimeIndicator {
+                 critical (e.message);
+             }
+ 
+-            Idle.add (() => {
+-                load_source (source);
+-
+-                return false;
+-            });
++            load_source (source);
+         }
+ 
+         private void debug_event (E.Source source, ECal.Component event) {
+@@ -177,6 +165,8 @@ namespace DateTimeIndicator {
+                     return true;
+                 });
+             });
++
++            events_added (source, added_events.read_only_view);
+         }
+ 
+ #if E_CAL_2_0
+diff --git a/src/Widgets/CalendarDay.vala b/src/Widgets/CalendarDay.vala
+index f9c742a..a2c4922 100644
+--- a/src/Widgets/CalendarDay.vala
++++ b/src/Widgets/CalendarDay.vala
+@@ -36,7 +36,8 @@ namespace DateTimeIndicator {
+         private static Gtk.CssProvider provider;
+         private static Models.CalendarModel model;
+ 
+-        private Gee.HashMap<string, Gtk.Widget> event_dots;
++        // private Gee.HashMap<string, Gtk.Widget> event_dots;
++        private Gee.ArrayList<string> event_dots;
+         private Gtk.Grid event_grid;
+         private Gtk.Label label;
+         private bool valid_grab = false;
+@@ -87,7 +88,8 @@ namespace DateTimeIndicator {
+                 label.label = date.get_day_of_month ().to_string ();
+             });
+ 
+-            event_dots = new Gee.HashMap<string, Gtk.Widget> ();
++            // event_dots = new Gee.HashMap<string, Gtk.Widget> ();
++            event_dots = new Gee.ArrayList<string> ();
+         }
+ 
+         public bool on_scroll_event (Gdk.EventScroll event) {
+@@ -138,44 +140,46 @@ namespace DateTimeIndicator {
+             });
+         }
+ 
+-        public bool skip_day () {
+-            return event_dots.size >= 3 ? true : false;
+-        }
+-
+-        public void show_event_grid () {
+-            event_grid.show_all ();
+-        }
+-
+ #if USE_EVO
+         public void add_dots (E.Source source, ICal.Component ical) {
+             var event_uid = ical.get_uid ();
+-            if (!event_dots.has_key (event_uid)) {
+-                var event_dot = new Gtk. Image ();
+-                event_dot.gicon = new ThemedIcon ("pager-checked-symbolic");
+-                event_dot.pixel_size = 6;
++            if (event_dots.contains (event_uid)) {
++                return;
++            }
+ 
+-                unowned Gtk.StyleContext style_context = event_dot.get_style_context ();
+-                style_context.add_class (Granite.STYLE_CLASS_ACCENT);
+-                style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
++            event_dots.add (event_uid);
++            if (event_dots.size > 3) {
++                return;
++            }
+ 
+-                var source_calendar = (E.SourceCalendar?) source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
+-                Util.set_event_calendar_color (source_calendar, event_dot);
++            var event_dot = new Gtk.Image ();
++            event_dot.gicon = new ThemedIcon ("pager-checked-symbolic");
++            event_dot.pixel_size = 6;
+ 
+-                event_dots[event_uid] = event_dot;
++            unowned Gtk.StyleContext style_context = event_dot.get_style_context ();
++            style_context.add_class (Granite.STYLE_CLASS_ACCENT);
++            style_context.add_provider (provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ 
+-                event_grid.add (event_dot);
+-            }
+-        }
++            var source_calendar = (E.SourceCalendar?) source.get_extension (E.SOURCE_EXTENSION_CALENDAR);
++            Util.set_event_calendar_color (source_calendar, event_dot);
+ 
+-        public bool exist_event (string ical_uid) {
+-            return event_dots.has_key (ical_uid);
++            event_grid.add (event_dot);
++            event_dot.show ();
+         }
+ 
+         public void remove_dots (string event_uid) {
+-            var dot = event_dots[event_uid];
+-            if (dot != null) {
+-                dot.destroy ();
+-                event_dots.unset (event_uid);
++            if (event_dots.contains (event_uid)) {
++                return;
++            }
++
++            event_dots.remove (event_uid);
++            if (event_dots.size >= 3) {
++                return;
++            }
++
++            var w = event_grid.get_children ();
++            if (w.length () > 0) {
++                w.nth_data (0).destroy ();
+             }
+         }
+ #endif
+diff --git a/src/Widgets/CalendarGrid.vala b/src/Widgets/CalendarGrid.vala
+index 1d1e06c..c544404 100644
+--- a/src/Widgets/CalendarGrid.vala
++++ b/src/Widgets/CalendarGrid.vala
+@@ -263,22 +263,16 @@ namespace DateTimeIndicator {
+ 
+ #if USE_EVO
+         public void add_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+-            data.foreach ((entry) => {
+-
+-                foreach (var component in events) {
+-                    if (entry.value.skip_day ()) {
+-                        return true;
+-                    }
+-
+-                    if (Util.calcomp_is_on_day (component, entry.value.date)) {
+-                        entry.value.add_dots (source, component.get_icalcomponent ());
+-                    }
++            foreach (var component in events) {
++                unowned ICal.Component? icomp = component.get_icalcomponent ();
++                ICal.Time? start_time = icomp.get_dtstart ();
++                time_t start_unix = start_time.as_timet ();
++                var t = new DateTime.from_unix_utc (start_unix);
++                var d_hash = day_hash (t);
++                if (data.has_key (d_hash)) {
++                    data[d_hash].add_dots (source, component.get_icalcomponent ());
+                 }
+-
+-                entry.value.show_event_grid ();
+-
+-                return true;
+-            });
++            }
+         }
+ 
+         public void remove_event_dots (E.Source source, Gee.Collection<ECal.Component> events) {
+@@ -286,9 +280,8 @@ namespace DateTimeIndicator {
+                 unowned ICal.Component ical = component.get_icalcomponent ();
+                 var event_uid = ical.get_uid ();
+                 data.foreach ((entry) => {
+-                    if (entry.value.exist_event (event_uid)) {
+-                        entry.value.remove_dots (event_uid);
+-                    }
++                    entry.value.remove_dots (event_uid);
++
+                     return true;
+                 });
+             }
+
+From 149da28659883b01ceb0773b35c018d31aa1d912 Mon Sep 17 00:00:00 2001
+From: Dirli <litandrej85@gmail.com>
+Date: Tue, 14 Apr 2020 00:16:01 +0300
+Subject: [PATCH 7/8] issue #55
+
+---
+ src/Services/EventsManager.vala |  2 ++
+ src/Widgets/CalendarDay.vala    |  2 +-
+ src/Widgets/CalendarGrid.vala   | 11 +++++++----
+ 3 files changed, 10 insertions(+), 5 deletions(-)
+
+diff --git a/src/Services/EventsManager.vala b/src/Services/EventsManager.vala
+index ad0397d..959762f 100644
+--- a/src/Services/EventsManager.vala
++++ b/src/Services/EventsManager.vala
+@@ -208,6 +208,8 @@ namespace DateTimeIndicator {
+                     removed_events.add (event);
+                     debug_event (source, event);
+                 }
++
++                events.remove_all (cid.get_uid ());
+             });
+ 
+             events_removed (source, removed_events.read_only_view);
+diff --git a/src/Widgets/CalendarDay.vala b/src/Widgets/CalendarDay.vala
+index a2c4922..10d088c 100644
+--- a/src/Widgets/CalendarDay.vala
++++ b/src/Widgets/CalendarDay.vala
+@@ -168,7 +168,7 @@ namespace DateTimeIndicator {
+         }
+ 
+         public void remove_dots (string event_uid) {
+-            if (event_dots.contains (event_uid)) {
++            if (!event_dots.contains (event_uid)) {
+                 return;
+             }
+ 
+diff --git a/src/Widgets/CalendarGrid.vala b/src/Widgets/CalendarGrid.vala
+index c544404..6e6cbdb 100644
+--- a/src/Widgets/CalendarGrid.vala
++++ b/src/Widgets/CalendarGrid.vala
+@@ -279,11 +279,14 @@ namespace DateTimeIndicator {
+             foreach (var component in events) {
+                 unowned ICal.Component ical = component.get_icalcomponent ();
+                 var event_uid = ical.get_uid ();
+-                data.foreach ((entry) => {
+-                    entry.value.remove_dots (event_uid);
+ 
+-                    return true;
+-                });
++                ICal.Time? start_time = ical.get_dtstart ();
++                time_t start_unix = start_time.as_timet ();
++                var t = new DateTime.from_unix_utc (start_unix);
++                var d_hash = day_hash (t);
++                if (data.has_key (d_hash)) {
++                    data[d_hash].remove_dots (event_uid);
++                }
+             }
+         }
+ #endif
+
+From a3910e2b8242b8c4837cc764da7f268a02d05d6e Mon Sep 17 00:00:00 2001
+From: Dirli <litandrej85@gmail.com>
+Date: Fri, 17 Apr 2020 00:52:53 +0300
+Subject: [PATCH 8/8] issue #127
+
+---
+ src/Indicator.vala              |  1 +
+ src/Models/CalendarModel.vala   | 17 ++++++---
+ src/Services/EventsManager.vala | 17 +++++++++
+ src/Widgets/CalendarDay.vala    |  3 +-
+ src/Widgets/CalendarGrid.vala   | 66 ++++++++++++++++++++++-----------
+ src/Widgets/CalendarView.vala   | 17 ++++-----
+ src/Widgets/EventsListBox.vala  |  6 ++-
+ 7 files changed, 87 insertions(+), 40 deletions(-)
+
+diff --git a/src/Indicator.vala b/src/Indicator.vala
+index c7550aa..63614e0 100644
+--- a/src/Indicator.vala
++++ b/src/Indicator.vala
+@@ -116,6 +116,7 @@ namespace DateTimeIndicator {
+                 model.notify["month-start"].connect (() => {
+                     model.compute_ranges ();
+ #if USE_EVO
++                    event_listbox.clear_list ();
+                     event_manager.load_all_sources ();
+ #endif
+                 });
+diff --git a/src/Models/CalendarModel.vala b/src/Models/CalendarModel.vala
+index d60a9ac..48b6e78 100644
+--- a/src/Models/CalendarModel.vala
++++ b/src/Models/CalendarModel.vala
+@@ -57,12 +57,17 @@ namespace DateTimeIndicator {
+             compute_ranges ();
+         }
+ 
+-        public void change_month (int relative) {
+-            month_start = month_start.add_months (relative);
+-        }
+-
+-        public void change_year (int relative) {
+-            month_start = month_start.add_years (relative);
++        public void change_month (int m_relative, int y_relative = 0) {
++            if (y_relative == 0) {
++                month_start = month_start.add_months (m_relative);
++            } else {
++                if (m_relative == 0) {
++                    month_start = month_start.add_years (y_relative);
++                } else {
++                    GLib.DateTime tmp_date = month_start.add_months (m_relative);
++                    month_start = tmp_date.add_years (y_relative);
++                }
++            }
+         }
+ 
+         /* --- Helper Methods ---// */
+diff --git a/src/Services/EventsManager.vala b/src/Services/EventsManager.vala
+index 959762f..6ece557 100644
+--- a/src/Services/EventsManager.vala
++++ b/src/Services/EventsManager.vala
+@@ -1,3 +1,20 @@
++/*
++ * Copyright (c) 2011-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 <http://www.gnu.org/licenses/>.
++ */
++
+ namespace DateTimeIndicator {
+     public class Services.EventsManager : GLib.Object {
+         public signal void events_added (E.Source source, Gee.Collection<ECal.Component> events);
+diff --git a/src/Widgets/CalendarDay.vala b/src/Widgets/CalendarDay.vala
+index 10d088c..735fdc1 100644
+--- a/src/Widgets/CalendarDay.vala
++++ b/src/Widgets/CalendarDay.vala
+@@ -36,7 +36,6 @@ namespace DateTimeIndicator {
+         private static Gtk.CssProvider provider;
+         private static Models.CalendarModel model;
+ 
+-        // private Gee.HashMap<string, Gtk.Widget> event_dots;
+         private Gee.ArrayList<string> event_dots;
+         private Gtk.Grid event_grid;
+         private Gtk.Label label;
+@@ -105,7 +104,7 @@ namespace DateTimeIndicator {
+ 
+             /* It's mouse scroll ! */
+             if (choice == 1 || choice == -1) {
+-                Models.CalendarModel.get_default ().change_month ((int)choice);
++                Models.CalendarModel.get_default ().change_month ((int) choice);
+ 
+                 return true;
+             }
+diff --git a/src/Widgets/CalendarGrid.vala b/src/Widgets/CalendarGrid.vala
+index 6e6cbdb..ef8edb6 100644
+--- a/src/Widgets/CalendarGrid.vala
++++ b/src/Widgets/CalendarGrid.vala
+@@ -32,7 +32,7 @@ namespace DateTimeIndicator {
+          */
+         public signal void on_event_add (GLib.DateTime date);
+ 
+-        public signal void selection_changed (GLib.DateTime new_date);
++        public signal void selection_changed (GLib.DateTime new_date, bool up);
+ 
+         private Gee.HashMap<uint, Widgets.CalendarDay> data;
+         private Widgets.CalendarDay selected_gridday;
+@@ -66,25 +66,32 @@ namespace DateTimeIndicator {
+             events |= Gdk.EventMask.SMOOTH_SCROLL_MASK;
+         }
+ 
+-        private void on_day_focus_in (Widgets.CalendarDay day) {
+-            debug ("on_day_focus_in %s", day.date.to_string ());
++        private bool on_day_focus_in (Gdk.EventFocus event) {
++            var day = get_focus_child ();
++            if (day == null) {
++                return false;
++            }
++
+             if (selected_gridday != null) {
+                 selected_gridday.set_selected (false);
+             }
+ 
+-            var selected_date = day.date;
+-            selected_gridday = day;
+-            day.set_selected (true);
++            var selected_date = (day as Widgets.CalendarDay).date;
++            selected_gridday = day as Widgets.CalendarDay;
++            (day as Widgets.CalendarDay).set_selected (true);
+             day.set_state_flags (Gtk.StateFlags.FOCUSED, false);
+-            selection_changed (selected_date);
+             var calmodel = Models.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 ();
+ 
+             if (date_month != 0 || date_year != 0) {
+-                calmodel.change_month (date_month);
+-                calmodel.change_year (date_year);
++                selection_changed (selected_date, false);
++                calmodel.change_month (date_month, date_year);
++            } else {
++                selection_changed (selected_date, true);
+             }
++
++            return false;
+         }
+ 
+         public void set_focus_to_today () {
+@@ -106,7 +113,7 @@ namespace DateTimeIndicator {
+          * Sets the given range to be displayed in the grid. Note that the number of days
+          * must remain the same.
+          */
+-        public void set_range (Util.DateRange new_range, GLib.DateTime month_start) {
++        public void set_range (Util.DateRange new_range, GLib.DateTime month_start, GLib.DateTime? selected_date) {
+             var today = new GLib.DateTime.now_local ();
+ 
+             Gee.List<GLib.DateTime> old_dates;
+@@ -138,28 +145,46 @@ namespace DateTimeIndicator {
+ 
+             for (i = 0; i < new_dates.size; i++) {
+                 var new_date = new_dates[i];
+-                Widgets.CalendarDay day;
++                Widgets.CalendarDay? day = null;
+ 
+                 if (i < old_dates.size) {
+                     /* A widget already exists for this date, just change it */
+ 
+                     var old_date = old_dates[i];
+-                    day = update_day (data[day_hash (old_date)], new_date, today, month_start);
+-                } else {
++                    var d_hash = day_hash (old_date);
++                    if (data.has_key (d_hash)) {
++                        day = data[d_hash];
++                    }
++                }
++
++                if (day == null) {
+                     /* Still update_day to get the color of etc. right */
+-                    day = update_day (new Widgets.CalendarDay (new_date), new_date, today, month_start);
++                    day = new Widgets.CalendarDay (new_date);
+                     day.on_event_add.connect ((date) => on_event_add (date));
+-                    day.scroll_event.connect ((event) => { scroll_event (event); return false; });
+-                    day.focus_in_event.connect ((event) => {
+-                        on_day_focus_in (day);
+-
++                    day.scroll_event.connect ((event) => {
++                        scroll_event (event);
+                         return false;
+                     });
++                    day.focus_in_event.connect (on_day_focus_in);
+ 
+                     attach (day, col + 2, row);
+                     day.show_all ();
+                 }
+ 
++                update_day (day, new_date, month_start);
++                update_today_style (day, new_date, today);
++                if (selected_date != null && day.date.equal (selected_date)) {
++                    /* disabled the signal to avoid unnecessary signals and selected
++                    * the specified day from the new period */
++                    debug (@"focus selected day $selected_date");
++                    day.focus_in_event.disconnect (on_day_focus_in);
++                    day.grab_focus_force ();
++                    day.set_selected (true);
++                    day.set_state_flags (Gtk.StateFlags.FOCUSED, false);
++                    selected_gridday = day;
++                    day.focus_in_event.connect (on_day_focus_in);
++                }
++
+                 col = (col + 1) % 7;
+                 row = (col == 0) ? row + 1 : row;
+                 data_new.set (day_hash (new_date), day);
+@@ -184,8 +209,7 @@ namespace DateTimeIndicator {
+         /**
+          * Updates the given CalendarDay so that it shows the given date. Changes to its style etc.
+          */
+-        private Widgets.CalendarDay update_day (Widgets.CalendarDay day, GLib.DateTime new_date, GLib.DateTime today, GLib.DateTime month_start) {
+-            update_today_style (day, new_date, today);
++        private void update_day (Widgets.CalendarDay day, GLib.DateTime new_date, GLib.DateTime month_start) {
+             if (new_date.get_month () == month_start.get_month ()) {
+                 day.sensitive_container (true);
+             } else {
+@@ -193,8 +217,6 @@ namespace DateTimeIndicator {
+             }
+ 
+             day.date = new_date;
+-
+-            return day;
+         }
+ 
+         public void update_weeks (GLib.DateTime date, int nr_of_weeks) {
+diff --git a/src/Widgets/CalendarView.vala b/src/Widgets/CalendarView.vala
+index 77c233c..fe957ab 100644
+--- a/src/Widgets/CalendarView.vala
++++ b/src/Widgets/CalendarView.vala
+@@ -85,10 +85,7 @@ namespace DateTimeIndicator {
+             model.notify["data-range"].connect (() => {
+                 label.label = model.month_start.format (_("%OB, %Y"));
+ 
+-                sync_with_model ();
+-
+-                selected_date = null;
+-                selection_changed (selected_date);
++                sync_with_model (selected_date != null);
+             });
+ 
+             left_button.clicked.connect (() => {
+@@ -113,9 +110,11 @@ namespace DateTimeIndicator {
+                 day_double_click ();
+             });
+ 
+-            calendar_grid.selection_changed.connect ((date) => {
++            calendar_grid.selection_changed.connect ((date, up) => {
+                 selected_date = date;
+-                selection_changed (date);
++                if (up) {
++                    selection_changed (date);
++                }
+             });
+ 
+             return calendar_grid;
+@@ -154,9 +153,9 @@ namespace DateTimeIndicator {
+         }
+ 
+         /* Sets the calendar widgets to the date range of the model */
+-        private void sync_with_model () {
++        private void sync_with_model (bool show_selected = false) {
+             var model = Models.CalendarModel.get_default ();
+-            if (calendar_grid.grid_range != null && (model.data_range.equals (calendar_grid.grid_range) || calendar_grid.grid_range.first_dt.compare (model.data_range.first_dt) == 0)) {
++            if (!show_selected && calendar_grid.grid_range != null && (model.data_range.equals (calendar_grid.grid_range) || calendar_grid.grid_range.first_dt.compare (model.data_range.first_dt) == 0)) {
+                 calendar_grid.update_today ();
+                 return; // nothing else to do
+             }
+@@ -168,7 +167,7 @@ namespace DateTimeIndicator {
+             big_grid = create_big_grid ();
+             stack.add (big_grid);
+ 
+-            calendar_grid.set_range (model.data_range, model.month_start);
++            calendar_grid.set_range (model.data_range, model.month_start, show_selected ? selected_date : null);
+             calendar_grid.update_weeks (model.data_range.first_dt, model.num_weeks);
+ 
+             if (previous_first != null) {
+diff --git a/src/Widgets/EventsListBox.vala b/src/Widgets/EventsListBox.vala
+index c25af2e..9022072 100644
+--- a/src/Widgets/EventsListBox.vala
++++ b/src/Widgets/EventsListBox.vala
+@@ -21,10 +21,14 @@ namespace DateTimeIndicator {
+             set_sort_func (sort_function);
+         }
+ 
+-        public void update_events (GLib.DateTime? selected_date, HashTable<E.Source, Gee.TreeMultiMap<string, ECal.Component>> source_events) {
++        public void clear_list () {
+             foreach (unowned Gtk.Widget widget in get_children ()) {
+                 widget.destroy ();
+             }
++        }
++
++        public void update_events (GLib.DateTime? selected_date, HashTable<E.Source, Gee.TreeMultiMap<string, ECal.Component>> source_events) {
++            clear_list ();
+ 
+             if (selected_date == null) {
+                 return;
diff --git a/pkgs/desktops/pantheon/desktop/wingpanel-indicators/datetime/default.nix b/pkgs/desktops/pantheon/desktop/wingpanel-indicators/datetime/default.nix
index ed70687e050..c6a0e2f7779 100644
--- a/pkgs/desktops/pantheon/desktop/wingpanel-indicators/datetime/default.nix
+++ b/pkgs/desktops/pantheon/desktop/wingpanel-indicators/datetime/default.nix
@@ -1,5 +1,5 @@
 { stdenv
-, fetchFromGitHub 
+, fetchFromGitHub
 , pantheon
 , pkgconfig
 , meson
@@ -28,6 +28,15 @@ stdenv.mkDerivation rec {
     sha256 = "0a0pqrpmrdd5pch30lizr9righlc7165z7krmnaxrzd0fvfkbr2h";
   };
 
+  patches = [
+    # https://github.com/elementary/wingpanel-indicator-datetime/pull/207
+    # Fixes lots of issues despite being rejected upstream
+    # https://github.com/elementary/wingpanel-indicator-datetime/issues/206
+    # https://github.com/elementary/wingpanel-indicator-datetime/issues/55
+    # https://github.com/elementary/wingpanel-indicator-datetime/issues/127
+    ./207.patch
+  ];
+
   passthru = {
     updateScript = pantheon.updateScript {
       attrPath = "pantheon.${pname}";
diff --git a/pkgs/desktops/pantheon/desktop/wingpanel/default.nix b/pkgs/desktops/pantheon/desktop/wingpanel/default.nix
index 64ebee4f13d..ff9925c5357 100644
--- a/pkgs/desktops/pantheon/desktop/wingpanel/default.nix
+++ b/pkgs/desktops/pantheon/desktop/wingpanel/default.nix
@@ -65,6 +65,13 @@ stdenv.mkDerivation rec {
     patchShebangs meson/post_install.py
   '';
 
+  preFixup = ''
+    gappsWrapperArgs+=(
+      # this theme is required
+      --prefix XDG_DATA_DIRS : "${elementary-gtk-theme}/share"
+    )
+  '';
+
   meta = with stdenv.lib; {
     description = "The extensible top panel for Pantheon";
     longDescription = ''