[evolution] Calendar: Implement 'Year View'



commit 68a00993d22527ce8e258c0da504356d918bb58f
Author: Milan Crha <mcrha redhat com>
Date:   Fri Mar 25 13:49:02 2022 +0100

    Calendar: Implement 'Year View'
    
    Let the calendar show events as a whole year.

 data/icons/CMakeLists.txt                          |    8 +
 .../hicolor_actions_16x16_view-calendar-year.png   |  Bin 0 -> 583 bytes
 .../hicolor_actions_16x16_view-calendar-year.svg   |  322 ++++
 .../hicolor_actions_22x22_view-calendar-year.png   |  Bin 0 -> 884 bytes
 .../hicolor_actions_22x22_view-calendar-year.svg   |  467 +++++
 .../hicolor_actions_24x24_view-calendar-year.png   |  Bin 0 -> 948 bytes
 .../hicolor_actions_32x32_view-calendar-year.png   |  Bin 0 -> 1133 bytes
 .../hicolor_actions_32x32_view-calendar-year.svg   |  460 +++++
 ...hicolor_actions_scalable_view-calendar-year.svg |  506 +++++
 data/org.gnome.evolution.calendar.gschema.xml.in   |   31 +-
 data/ui/evolution-calendars.ui                     |   11 +-
 data/views/calendar/galview.xml                    |    2 +
 .../evolution-util/evolution-util-docs.sgml.in     |    5 +
 po/POTFILES.in                                     |    1 +
 src/calendar/gui/CMakeLists.txt                    |    2 +
 src/calendar/gui/calendar-view.c                   |   15 +
 src/calendar/gui/calendar-view.h                   |    6 +
 src/calendar/gui/comp-util.c                       |  496 +++++
 src/calendar/gui/comp-util.h                       |   37 +
 src/calendar/gui/e-cal-list-view.c                 |   48 +-
 src/calendar/gui/e-cal-model.c                     |   71 +-
 src/calendar/gui/e-calendar-view.c                 |  203 +-
 src/calendar/gui/e-calendar-view.h                 |   15 +-
 src/calendar/gui/e-day-view.c                      |   12 +-
 src/calendar/gui/e-week-view.c                     |   12 +-
 src/calendar/gui/e-year-view.c                     | 2014 ++++++++++++++++++++
 src/calendar/gui/e-year-view.h                     |   60 +
 src/e-util/CMakeLists.txt                          |    3 +
 src/e-util/e-misc-utils.c                          |   27 +
 src/e-util/e-misc-utils.h                          |    3 +
 src/e-util/e-month-widget.c                        | 1290 +++++++++++++
 src/e-util/e-month-widget.h                        |  106 ++
 src/e-util/e-util.h                                |    1 +
 src/e-util/test-month-widget.c                     |  438 +++++
 src/modules/calendar/e-cal-shell-content.c         |   91 +-
 src/modules/calendar/e-cal-shell-content.h         |    1 +
 src/modules/calendar/e-cal-shell-view-actions.c    |  309 +--
 src/modules/calendar/e-cal-shell-view-actions.h    |   10 +
 src/modules/calendar/e-cal-shell-view-private.c    |    8 +-
 src/modules/calendar/e-cal-shell-view.c            |    3 +-
 src/shell/main.c                                   |    1 +
 41 files changed, 6750 insertions(+), 345 deletions(-)
---
diff --git a/data/icons/CMakeLists.txt b/data/icons/CMakeLists.txt
index 64d2395b32..e2777be6e8 100644
--- a/data/icons/CMakeLists.txt
+++ b/data/icons/CMakeLists.txt
@@ -73,12 +73,14 @@ set(private_icons
        hicolor_actions_16x16_view-calendar-month.png
        hicolor_actions_16x16_view-calendar-week.png
        hicolor_actions_16x16_view-calendar-workweek.png
+       hicolor_actions_16x16_view-calendar-year.png
        hicolor_actions_22x22_go-today.png
        hicolor_actions_22x22_view-calendar-day.png
        hicolor_actions_22x22_view-calendar-list.png
        hicolor_actions_22x22_view-calendar-month.png
        hicolor_actions_22x22_view-calendar-week.png
        hicolor_actions_22x22_view-calendar-workweek.png
+       hicolor_actions_22x22_view-calendar-year.png
        hicolor_actions_24x24_go-today.png
        hicolor_actions_24x24_mail-archive.png
        hicolor_actions_24x24_query-free-busy.png
@@ -87,11 +89,13 @@ set(private_icons
        hicolor_actions_24x24_view-calendar-month.png
        hicolor_actions_24x24_view-calendar-week.png
        hicolor_actions_24x24_view-calendar-workweek.png
+       hicolor_actions_24x24_view-calendar-year.png
        hicolor_actions_32x32_view-calendar-day.png
        hicolor_actions_32x32_view-calendar-list.png
        hicolor_actions_32x32_view-calendar-month.png
        hicolor_actions_32x32_view-calendar-week.png
        hicolor_actions_32x32_view-calendar-workweek.png
+       hicolor_actions_32x32_view-calendar-year.png
        hicolor_actions_scalable_markdown-bold.svg
        hicolor_actions_scalable_markdown-bold-dark.svg
        hicolor_actions_scalable_markdown-bullets.svg
@@ -117,6 +121,7 @@ set(private_icons
        hicolor_actions_scalable_view-calendar-month.svg
        hicolor_actions_scalable_view-calendar-week.svg
        hicolor_actions_scalable_view-calendar-workweek.svg
+       hicolor_actions_scalable_view-calendar-year.svg
        hicolor_categories_24x24_preferences-calendar-and-tasks.svg
        hicolor_categories_24x24_preferences-certificates.svg
        hicolor_categories_24x24_preferences-composer.svg
@@ -788,17 +793,20 @@ set(noinst_icons
        hicolor_actions_16x16_view-calendar-month.svg
        hicolor_actions_16x16_view-calendar-week.svg
        hicolor_actions_16x16_view-calendar-workweek.svg
+       hicolor_actions_16x16_view-calendar-year.svg
        hicolor_actions_22x22_go-today.svg
        hicolor_actions_22x22_view-calendar-day.svg
        hicolor_actions_22x22_view-calendar-list.svg
        hicolor_actions_22x22_view-calendar-month.svg
        hicolor_actions_22x22_view-calendar-week.svg
        hicolor_actions_22x22_view-calendar-workweek.svg
+       hicolor_actions_22x22_view-calendar-year.svg
        hicolor_actions_32x32_view-calendar-day.svg
        hicolor_actions_32x32_view-calendar-list.svg
        hicolor_actions_32x32_view-calendar-month.svg
        hicolor_actions_32x32_view-calendar-week.svg
        hicolor_actions_32x32_view-calendar-workweek.svg
+       hicolor_actions_32x32_view-calendar-year.svg
        hicolor_places_16x16_mail-inbox.svg
        hicolor_places_16x16_mail-outbox.svg
        hicolor_places_16x16_mail-sent.svg
diff --git a/data/icons/hicolor_actions_16x16_view-calendar-year.png 
b/data/icons/hicolor_actions_16x16_view-calendar-year.png
new file mode 100644
index 0000000000..a1a51613fb
Binary files /dev/null and b/data/icons/hicolor_actions_16x16_view-calendar-year.png differ
diff --git a/data/icons/hicolor_actions_16x16_view-calendar-year.svg 
b/data/icons/hicolor_actions_16x16_view-calendar-year.svg
new file mode 100644
index 0000000000..3fbb15f760
--- /dev/null
+++ b/data/icons/hicolor_actions_16x16_view-calendar-year.svg
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="16"
+   height="16"
+   id="svg4908"
+   version="1.0"
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:dc="http://purl.org/dc/elements/1.1/";>
+  <defs
+     id="defs4910">
+    <linearGradient
+       id="linearGradient5721">
+      <stop
+         style="stop-color:#888a85;stop-opacity:1;"
+         offset="0"
+         id="stop5723" />
+      <stop
+         style="stop-color:#9c9e9a;stop-opacity:1"
+         offset="1"
+         id="stop5725" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3702">
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="0"
+         id="stop3704" />
+      <stop
+         id="stop3710"
+         offset="0.5"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop3706" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3702"
+       id="linearGradient2098"
+       gradientUnits="userSpaceOnUse"
+       x1="25.058096"
+       y1="47.027729"
+       x2="25.058096"
+       y2="39.999443" />
+    <radialGradient
+       xlink:href="#linearGradient3688"
+       id="radialGradient2096"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.003784,0,0,1.4,-20.01187,-104.4)"
+       cx="4.9929786"
+       cy="43.5"
+       fx="4.9929786"
+       fy="43.5"
+       r="2.5" />
+    <linearGradient
+       id="linearGradient3688">
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0"
+         id="stop3690" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop3692" />
+    </linearGradient>
+    <radialGradient
+       xlink:href="#linearGradient3688"
+       id="radialGradient2094"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.003784,0,0,1.4,27.98813,-17.4)"
+       cx="4.9929786"
+       cy="43.5"
+       fx="4.9929786"
+       fy="43.5"
+       r="2.5" />
+    <linearGradient
+       id="linearGradient6956">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0"
+         id="stop6958" />
+      <stop
+         style="stop-color:#d3d7cf;stop-opacity:1"
+         offset="1"
+         id="stop6960" />
+    </linearGradient>
+    <radialGradient
+       xlink:href="#linearGradient6956"
+       id="radialGradient10802"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.2933438,0,0,2.2505457,45.56813,-21.76062)"
+       cx="-15.113025"
+       cy="15.017189"
+       fx="-15.113025"
+       fy="15.017189"
+       r="9" />
+    <linearGradient
+       id="linearGradient3177">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3179" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3181" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3177"
+       id="linearGradient3183"
+       x1="12.78919"
+       y1="0.21081063"
+       x2="16.43354"
+       y2="19.430988"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.6825708,0,0,0.7003708,-0.1195141,-0.9647651)" />
+    <linearGradient
+       id="linearGradient3167">
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:1;"
+         offset="0"
+         id="stop3169" />
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:0;"
+         offset="1"
+         id="stop3171" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3167"
+       id="linearGradient3186"
+       gradientUnits="userSpaceOnUse"
+       x1="5.6787376"
+       y1="-9.7172785"
+       x2="17.825142"
+       y2="11.213716"
+       gradientTransform="matrix(0.670403,0,0,0.9224329,25.939391,0.3499054)" />
+    <linearGradient
+       xlink:href="#linearGradient5721"
+       id="linearGradient5727"
+       x1="10.871767"
+       y1="3.3058643"
+       x2="10.871767"
+       y2="5.0445838"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.7899997,0,0,0.9324837,-0.569546,-0.6992715)" />
+  </defs>
+  <metadata
+     id="metadata4913">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <rect
+     
style="fill:url(#radialGradient10802);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.7;stroke-opacity:1"
+     id="rect5985"
+     width="14.971417"
+     height="14.911932"
+     x="0.50830561"
+     y="0.53378403"
+     rx="1.0218275"
+     ry="1.0218276" />
+  <rect
+     
style="opacity:0.51;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.7;stroke-opacity:1"
+     id="rect6964"
+     width="12.958357"
+     height="12.923835"
+     x="1.5246743"
+     y="1.5249711"
+     rx="0"
+     ry="0" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.315875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect5148-3-5-6"
+     width="1.0844724"
+     height="2"
+     x="-3.999999"
+     y="-14"
+     transform="scale(-1)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.315875;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect5148-6-6-6-1"
+     width="1.0844724"
+     height="2"
+     x="-7.0844707"
+     y="-14"
+     transform="scale(-1)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.396606;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3163-3-5-9-9"
+     width="1.0844724"
+     height="4.0844727"
+     x="12"
+     y="-6.9999981"
+     transform="rotate(90)" />
+  <path
+     
style="opacity:0.222222;fill:none;fill-opacity:1;stroke:url(#linearGradient3183);stroke-width:1;stroke-miterlimit:4;stroke-opacity:1"
+     d="M 1.5499625,4.1234083 1.4951763,1.4865324 H 14.555758 l -0.03695,2.4512981"
+     id="rect3175" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.339278;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-3"
+     width="1.0731258"
+     height="3"
+     x="9"
+     y="-13"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.339278;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-5"
+     width="1.0731258"
+     height="3"
+     x="6.9268742"
+     y="-13"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.339278;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-7"
+     width="1.0731258"
+     height="3"
+     x="4.9268742"
+     y="-13"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.339278;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161"
+     width="1.0731258"
+     height="3"
+     x="2.9268744"
+     y="-13"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.77137;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect5154"
+     width="1.0844724"
+     height="11.926874"
+     x="-9.0844727"
+     y="-13.926874"
+     transform="scale(-1)" />
+  <g
+     id="g4660"
+     transform="translate(0.99999905)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.446715;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-3-5"
+       width="1.0844724"
+       height="4"
+       x="-3.000001"
+       y="-6"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.446715;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-6-6"
+       width="1.0844724"
+       height="4"
+       x="-6.0844727"
+       y="-6"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.392484;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-7-2"
+       width="1.0844724"
+       height="4"
+       x="5"
+       y="-6"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.396606;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-5-9"
+       width="1.0844724"
+       height="4.0844727"
+       x="2"
+       y="-6"
+       transform="rotate(90)" />
+  </g>
+  <g
+     id="g4660-2"
+     transform="translate(0.99999809,5)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.446715;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-3-5-2"
+       width="1.0844724"
+       height="4"
+       x="-3.000001"
+       y="-6"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.446715;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-6-6-8"
+       width="1.0844724"
+       height="4"
+       x="-6.0844727"
+       y="-6"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.392484;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-7-2-9"
+       width="1.0844724"
+       height="4"
+       x="5"
+       y="-6"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.396606;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-5-9-7"
+       width="1.0844724"
+       height="4.0844727"
+       x="2"
+       y="-6"
+       transform="rotate(90)" />
+  </g>
+</svg>
diff --git a/data/icons/hicolor_actions_22x22_view-calendar-year.png 
b/data/icons/hicolor_actions_22x22_view-calendar-year.png
new file mode 100644
index 0000000000..8b26960f26
Binary files /dev/null and b/data/icons/hicolor_actions_22x22_view-calendar-year.png differ
diff --git a/data/icons/hicolor_actions_22x22_view-calendar-year.svg 
b/data/icons/hicolor_actions_22x22_view-calendar-year.svg
new file mode 100644
index 0000000000..75e3f7638e
--- /dev/null
+++ b/data/icons/hicolor_actions_22x22_view-calendar-year.svg
@@ -0,0 +1,467 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="22"
+   height="22"
+   id="svg4908"
+   version="1.0"
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:dc="http://purl.org/dc/elements/1.1/";>
+  <defs
+     id="defs4910">
+    <linearGradient
+       id="linearGradient5721">
+      <stop
+         style="stop-color:#888a85;stop-opacity:1;"
+         offset="0"
+         id="stop5723" />
+      <stop
+         style="stop-color:#9c9e9a;stop-opacity:1"
+         offset="1"
+         id="stop5725" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3702">
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="0"
+         id="stop3704" />
+      <stop
+         id="stop3710"
+         offset="0.5"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop3706" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3702"
+       id="linearGradient2098"
+       gradientUnits="userSpaceOnUse"
+       x1="25.058096"
+       y1="47.027729"
+       x2="25.058096"
+       y2="39.999443" />
+    <radialGradient
+       xlink:href="#linearGradient3688"
+       id="radialGradient2096"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.003784,0,0,1.4,-20.01187,-104.4)"
+       cx="4.9929786"
+       cy="43.5"
+       fx="4.9929786"
+       fy="43.5"
+       r="2.5" />
+    <linearGradient
+       id="linearGradient3688">
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0"
+         id="stop3690" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop3692" />
+    </linearGradient>
+    <radialGradient
+       xlink:href="#linearGradient3688"
+       id="radialGradient2094"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.003784,0,0,1.4,27.98813,-17.4)"
+       cx="4.9929786"
+       cy="43.5"
+       fx="4.9929786"
+       fy="43.5"
+       r="2.5" />
+    <linearGradient
+       id="linearGradient6956">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0"
+         id="stop6958" />
+      <stop
+         style="stop-color:#d3d7cf;stop-opacity:1"
+         offset="1"
+         id="stop6960" />
+    </linearGradient>
+    <radialGradient
+       xlink:href="#linearGradient6956"
+       id="radialGradient10802"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.9201066,0,0,2.732559,58.869544,-25.660551)"
+       cx="-15.113025"
+       cy="15.017189"
+       fx="-15.113025"
+       fy="15.017189"
+       r="9" />
+    <linearGradient
+       id="linearGradient3177">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3179" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3181" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3177"
+       id="linearGradient3183"
+       x1="12.78919"
+       y1="0.21081063"
+       x2="16.43354"
+       y2="19.430988"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0.8932504,0,0,0.8425764,0.4073914,-0.5013004)" />
+    <linearGradient
+       id="linearGradient3167">
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:1;"
+         offset="0"
+         id="stop3169" />
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:0;"
+         offset="1"
+         id="stop3171" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3167"
+       id="linearGradient3186"
+       gradientUnits="userSpaceOnUse"
+       x1="5.6787376"
+       y1="-9.7172785"
+       x2="17.825142"
+       y2="11.213716"
+       gradientTransform="matrix(0.670403,0,0,0.9224329,25.939391,0.3499054)" />
+    <linearGradient
+       xlink:href="#linearGradient5721"
+       id="linearGradient5727"
+       x1="10.871767"
+       y1="3.3058643"
+       x2="10.871767"
+       y2="5.0445838"
+       gradientUnits="userSpaceOnUse" />
+  </defs>
+  <metadata
+     id="metadata4913">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="g2043"
+     transform="matrix(0.55625,0,0,0.555556,-2.3342121,-4.4504485)">
+    <g
+       style="display:inline"
+       id="g2036">
+      <g
+         id="g3712"
+         style="opacity:0.4"
+         transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)">
+        <rect
+           y="40"
+           x="38"
+           height="7"
+           width="5"
+           id="rect2801"
+           
style="opacity:1;fill:url(#radialGradient2094);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+        <rect
+           transform="scale(-1)"
+           y="-47"
+           x="-10"
+           height="7"
+           width="5"
+           id="rect3696"
+           
style="opacity:1;fill:url(#radialGradient2096);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+        <rect
+           y="40"
+           x="10"
+           height="7.0000005"
+           width="28"
+           id="rect3700"
+           
style="opacity:1;fill:url(#linearGradient2098);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+      </g>
+    </g>
+  </g>
+  <rect
+     
style="fill:url(#radialGradient10802);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.7;stroke-opacity:1"
+     id="rect5985"
+     width="19.063053"
+     height="18.105713"
+     x="1.4950296"
+     y="1.408784"
+     rx="1.0218276"
+     ry="1.0218276" />
+  <g
+     id="g3001-6"
+     transform="translate(0.08447266,6)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.499442;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-3-0"
+       width="1.0844724"
+       height="5"
+       x="-10"
+       y="-8"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.499442;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-6-62"
+       width="1.0844724"
+       height="5"
+       x="-14"
+       y="-8"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-7-6"
+       width="1.0844724"
+       height="5"
+       x="6.9155273"
+       y="-13.915527"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-5-1"
+       width="1.0844724"
+       height="5"
+       x="3"
+       y="-13.915527"
+       transform="rotate(90)" />
+  </g>
+  <rect
+     
style="opacity:0.51;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.7;stroke-opacity:1"
+     id="rect6964"
+     width="17.018353"
+     height="15.979198"
+     x="2.5578046"
+     y="2.5523908"
+     rx="0"
+     ry="0" />
+  <g
+     id="g3001-1"
+     transform="translate(-6,6)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.499442;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-3-2"
+       width="1.0844724"
+       height="5"
+       x="-10"
+       y="-8"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.499442;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-6-7"
+       width="1.0844724"
+       height="5"
+       x="-14"
+       y="-8"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-7-0"
+       width="1.0844724"
+       height="5"
+       x="6.9155273"
+       y="-13.915527"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-5-93"
+       width="1.0844724"
+       height="5"
+       x="3"
+       y="-13.915527"
+       transform="rotate(90)" />
+  </g>
+  <g
+     id="g3641">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.386867;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-3-7"
+       width="1.0844724"
+       height="3"
+       x="-4"
+       y="-18"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.386867;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-6-9"
+       width="1.0844724"
+       height="3"
+       x="-8"
+       y="-18"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-5-2"
+       width="1.0844724"
+       height="5"
+       x="15"
+       y="-7.9155273"
+       transform="rotate(90)" />
+  </g>
+  <g
+     id="g3641-3"
+     transform="translate(6)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.386867;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-3-7-7"
+       width="1.0844724"
+       height="3"
+       x="-4"
+       y="-18"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.386867;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-6-9-5"
+       width="1.0844724"
+       height="3"
+       x="-8"
+       y="-18"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-5-2-9"
+       width="1.0844724"
+       height="5"
+       x="15"
+       y="-7.9155273"
+       transform="rotate(90)" />
+  </g>
+  <path
+     
style="opacity:0.222222;fill:none;fill-opacity:1;stroke:url(#linearGradient3183);stroke-width:1;stroke-miterlimit:4;stroke-opacity:1"
+     d="M 2.5921612,5.6199928 2.5204654,2.4477169 H 19.612275 l -0.04836,2.9490178"
+     id="rect3175" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.277019;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-3"
+     width="1.0731258"
+     height="2"
+     x="10"
+     y="-19"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.279294;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-3-9"
+     width="1.0731258"
+     height="2.0329857"
+     x="11.926874"
+     y="-19"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.277019;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-5"
+     width="1.0731258"
+     height="2"
+     x="7.9268742"
+     y="-19"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.277019;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-7"
+     width="1.0731258"
+     height="2"
+     x="5.9268742"
+     y="-19"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.277019;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161"
+     width="1.0731258"
+     height="2"
+     x="3.9268744"
+     y="-19"
+     transform="rotate(90)" />
+  <g
+     id="g3001"
+     transform="translate(0.08447266)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.499442;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-3"
+       width="1.0844724"
+       height="5"
+       x="-10"
+       y="-8"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.499442;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-6"
+       width="1.0844724"
+       height="5"
+       x="-14"
+       y="-8"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-7"
+       width="1.0844724"
+       height="5"
+       x="6.9155273"
+       y="-13.915527"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-5"
+       width="1.0844724"
+       height="5"
+       x="3"
+       y="-13.915527"
+       transform="rotate(90)" />
+  </g>
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.865058;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect5154"
+     width="1.0844724"
+     height="15"
+     x="-16.084473"
+     y="-18"
+     transform="scale(-1)" />
+  <g
+     id="g3001-3"
+     transform="translate(-5.9155273)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.499442;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-3-5"
+       width="1.0844724"
+       height="5"
+       x="-10"
+       y="-8"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.499442;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-6-6"
+       width="1.0844724"
+       height="5"
+       x="-14"
+       y="-8"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-7-2"
+       width="1.0844724"
+       height="5"
+       x="6.9155273"
+       y="-13.915527"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-5-9"
+       width="1.0844724"
+       height="5"
+       x="3"
+       y="-13.915527"
+       transform="rotate(90)" />
+  </g>
+</svg>
diff --git a/data/icons/hicolor_actions_24x24_view-calendar-year.png 
b/data/icons/hicolor_actions_24x24_view-calendar-year.png
new file mode 100644
index 0000000000..bedd075e48
Binary files /dev/null and b/data/icons/hicolor_actions_24x24_view-calendar-year.png differ
diff --git a/data/icons/hicolor_actions_32x32_view-calendar-year.png 
b/data/icons/hicolor_actions_32x32_view-calendar-year.png
new file mode 100644
index 0000000000..398f4ebcbf
Binary files /dev/null and b/data/icons/hicolor_actions_32x32_view-calendar-year.png differ
diff --git a/data/icons/hicolor_actions_32x32_view-calendar-year.svg 
b/data/icons/hicolor_actions_32x32_view-calendar-year.svg
new file mode 100644
index 0000000000..eccba09c66
--- /dev/null
+++ b/data/icons/hicolor_actions_32x32_view-calendar-year.svg
@@ -0,0 +1,460 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="32"
+   height="32"
+   id="svg4908"
+   version="1.0"
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:dc="http://purl.org/dc/elements/1.1/";>
+  <defs
+     id="defs4910">
+    <linearGradient
+       id="linearGradient5721">
+      <stop
+         style="stop-color:#888a85;stop-opacity:1;"
+         offset="0"
+         id="stop5723" />
+      <stop
+         style="stop-color:#9c9e9a;stop-opacity:1"
+         offset="1"
+         id="stop5725" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3702">
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="0"
+         id="stop3704" />
+      <stop
+         id="stop3710"
+         offset="0.5"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop3706" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3702"
+       id="linearGradient2098"
+       gradientUnits="userSpaceOnUse"
+       x1="25.058096"
+       y1="47.027729"
+       x2="25.058096"
+       y2="39.999443" />
+    <radialGradient
+       xlink:href="#linearGradient3688"
+       id="radialGradient2096"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.003784,0,0,1.4,-20.01187,-104.4)"
+       cx="4.9929786"
+       cy="43.5"
+       fx="4.9929786"
+       fy="43.5"
+       r="2.5" />
+    <linearGradient
+       id="linearGradient3688">
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0"
+         id="stop3690" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop3692" />
+    </linearGradient>
+    <radialGradient
+       xlink:href="#linearGradient3688"
+       id="radialGradient2094"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.003784,0,0,1.4,27.98813,-17.4)"
+       cx="4.9929786"
+       cy="43.5"
+       fx="4.9929786"
+       fy="43.5"
+       r="2.5" />
+    <linearGradient
+       id="linearGradient6956">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0"
+         id="stop6958" />
+      <stop
+         style="stop-color:#d3d7cf;stop-opacity:1"
+         offset="1"
+         id="stop6960" />
+    </linearGradient>
+    <radialGradient
+       xlink:href="#linearGradient6956"
+       id="radialGradient10802"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(4.1424206,0,0,3.7387745,83.909496,-34.341933)"
+       cx="-15.113025"
+       cy="15.017189"
+       fx="-15.113025"
+       fy="15.017189"
+       r="9" />
+    <linearGradient
+       id="linearGradient3177">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3179" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3181" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3177"
+       id="linearGradient3183"
+       x1="12.78919"
+       y1="0.21081063"
+       x2="16.43354"
+       y2="19.430988"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.312881,0,0,1.1466667,0.3677638,-0.4678414)" />
+    <linearGradient
+       id="linearGradient3167">
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:1;"
+         offset="0"
+         id="stop3169" />
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:0;"
+         offset="1"
+         id="stop3171" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3167"
+       id="linearGradient3186"
+       gradientUnits="userSpaceOnUse"
+       x1="5.6787376"
+       y1="-9.7172785"
+       x2="17.825142"
+       y2="11.213716"
+       gradientTransform="matrix(0.670403,0,0,0.9224329,25.939391,0.3499054)" />
+    <linearGradient
+       xlink:href="#linearGradient5721"
+       id="linearGradient5727"
+       x1="10.871767"
+       y1="3.3058643"
+       x2="10.871767"
+       y2="5.0445838"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(1.4456404,0,0,1.3811576,0.1041093,0.1405096)" />
+  </defs>
+  <metadata
+     id="metadata4913">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="g2043"
+     transform="matrix(0.7946124,0,0,0.6001617,-3.0679373,1.5156582)">
+    <g
+       style="display:inline"
+       id="g2036">
+      <g
+         id="g3712"
+         style="opacity:0.4"
+         transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)">
+        <rect
+           y="40"
+           x="38"
+           height="7"
+           width="5"
+           id="rect2801"
+           
style="opacity:1;fill:url(#radialGradient2094);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+        <rect
+           transform="scale(-1)"
+           y="-47"
+           x="-10"
+           height="7"
+           width="5"
+           id="rect3696"
+           
style="opacity:1;fill:url(#radialGradient2096);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+        <rect
+           y="40"
+           x="10"
+           height="7.0000005"
+           width="28"
+           id="rect3700"
+           
style="opacity:1;fill:url(#linearGradient2098);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+      </g>
+    </g>
+  </g>
+  <rect
+     
style="fill:url(#radialGradient10802);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.7;stroke-opacity:1"
+     id="rect5985"
+     width="27.042568"
+     height="24.772812"
+     x="2.5188484"
+     y="2.6951954"
+     rx="1.0218277"
+     ry="1.0218275" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.451407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect5148-2-6"
+     width="1.0844724"
+     height="4.0844727"
+     x="-14.084473"
+     y="-26"
+     transform="scale(-1)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.451407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect5148-6-6-1"
+     width="1.0844724"
+     height="4.0844727"
+     x="-20"
+     y="-26"
+     transform="scale(-1)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3163-3-8-9"
+     width="1.0844724"
+     height="5"
+     x="21.915527"
+     y="-19"
+     transform="rotate(90)" />
+  <g
+     id="g1778-6"
+     transform="translate(3.8418579e-8,8)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.590948;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-2"
+       width="1.0844724"
+       height="7"
+       x="-6.0844722"
+       y="-13"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.590948;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-6"
+       width="1.0844724"
+       height="7"
+       x="-12"
+       y="-13"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-1"
+       width="1.0844724"
+       height="5"
+       x="11.915527"
+       y="-11"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-8"
+       width="1.0844724"
+       height="5"
+       x="6"
+       y="-11"
+       transform="rotate(90)" />
+  </g>
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.451407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect5148-2-2"
+     width="1.0844724"
+     height="4.0844727"
+     x="-6.0844722"
+     y="-26"
+     transform="scale(-1)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.451407;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect5148-6-6-8"
+     width="1.0844724"
+     height="4.0844727"
+     x="-12"
+     y="-26"
+     transform="scale(-1)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3163-3-8-7"
+     width="1.0844724"
+     height="5"
+     x="21.915527"
+     y="-11"
+     transform="rotate(90)" />
+  <g
+     id="g1778-79"
+     transform="translate(8,8)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.590948;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-20"
+       width="1.0844724"
+       height="7"
+       x="-6.0844722"
+       y="-13"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.590948;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-2"
+       width="1.0844724"
+       height="7"
+       x="-12"
+       y="-13"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-37"
+       width="1.0844724"
+       height="5"
+       x="11.915527"
+       y="-11"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-5"
+       width="1.0844724"
+       height="5"
+       x="6"
+       y="-11"
+       transform="rotate(90)" />
+  </g>
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.393376;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-3"
+     width="1.0731258"
+     height="4.0329857"
+     x="11.926874"
+     y="-27.032986"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.393376;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-3-9"
+     width="1.0731258"
+     height="4.0329857"
+     x="13.926874"
+     y="-27"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.393376;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-5"
+     width="1.0731258"
+     height="4.0329857"
+     x="9.9268742"
+     y="-27.032986"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.393376;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161-7"
+     width="1.0731258"
+     height="4.0329857"
+     x="7.9268742"
+     y="-27.032986"
+     transform="rotate(90)" />
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.393376;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect3161"
+     width="1.0731258"
+     height="4.0329857"
+     x="5.9268742"
+     y="-27.032986"
+     transform="rotate(90)" />
+  <g
+     id="g1778">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.590948;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148"
+       width="1.0844724"
+       height="7"
+       x="-6.0844722"
+       y="-13"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.590948;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6"
+       width="1.0844724"
+       height="7"
+       x="-12"
+       y="-13"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163"
+       width="1.0844724"
+       height="5"
+       x="11.915527"
+       y="-11"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3"
+       width="1.0844724"
+       height="5"
+       x="6"
+       y="-11"
+       transform="rotate(90)" />
+  </g>
+  <g
+     id="g1778-7"
+     transform="translate(8,0.08447266)">
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.590948;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-0"
+       width="1.0844724"
+       height="7"
+       x="-6.0844722"
+       y="-13"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.590948;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5148-6-9"
+       width="1.0844724"
+       height="7"
+       x="-12"
+       y="-13"
+       transform="scale(-1)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-36"
+       width="1.0844724"
+       height="5"
+       x="11.915527"
+       y="-11"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.43881;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3163-3-0"
+       width="1.0844724"
+       height="5"
+       x="6"
+       y="-11"
+       transform="rotate(90)" />
+  </g>
+  <rect
+     
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.0487;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+     id="rect5154"
+     width="1.0844724"
+     height="22.044678"
+     x="-22.084473"
+     y="-26.044678"
+     transform="scale(-1)" />
+  <rect
+     
style="opacity:0.51;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.7;stroke-opacity:1"
+     id="rect6964"
+     width="25.016987"
+     height="22.940639"
+     x="3.5264854"
+     y="3.5691302"
+     rx="0"
+     ry="0" />
+  <path
+     
style="opacity:0.222222;fill:none;fill-opacity:1;stroke:url(#linearGradient3183);stroke-width:1;stroke-miterlimit:4;stroke-opacity:1"
+     d="M 3.5788935,7.8626592 3.4735166,3.545492 H 28.594708 l -0.07108,4.0133341"
+     id="rect3175" />
+</svg>
diff --git a/data/icons/hicolor_actions_scalable_view-calendar-year.svg 
b/data/icons/hicolor_actions_scalable_view-calendar-year.svg
new file mode 100644
index 0000000000..90346b6b5b
--- /dev/null
+++ b/data/icons/hicolor_actions_scalable_view-calendar-year.svg
@@ -0,0 +1,506 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="48"
+   height="48"
+   id="svg4908"
+   version="1.0"
+   xmlns:xlink="http://www.w3.org/1999/xlink";
+   xmlns="http://www.w3.org/2000/svg";
+   xmlns:svg="http://www.w3.org/2000/svg";
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+   xmlns:cc="http://creativecommons.org/ns#";
+   xmlns:dc="http://purl.org/dc/elements/1.1/";>
+  <defs
+     id="defs4910">
+    <linearGradient
+       id="linearGradient3702">
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="0"
+         id="stop3704" />
+      <stop
+         id="stop3710"
+         offset="0.5"
+         style="stop-color:black;stop-opacity:1;" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop3706" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3702"
+       id="linearGradient2098"
+       gradientUnits="userSpaceOnUse"
+       x1="25.058096"
+       y1="47.027729"
+       x2="25.058096"
+       y2="39.999443" />
+    <radialGradient
+       xlink:href="#linearGradient3688"
+       id="radialGradient2096"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.003784,0,0,1.4,-20.01187,-104.4)"
+       cx="4.9929786"
+       cy="43.5"
+       fx="4.9929786"
+       fy="43.5"
+       r="2.5" />
+    <linearGradient
+       id="linearGradient3688">
+      <stop
+         style="stop-color:black;stop-opacity:1;"
+         offset="0"
+         id="stop3690" />
+      <stop
+         style="stop-color:black;stop-opacity:0;"
+         offset="1"
+         id="stop3692" />
+    </linearGradient>
+    <radialGradient
+       xlink:href="#linearGradient3688"
+       id="radialGradient2094"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.003784,0,0,1.4,27.98813,-17.4)"
+       cx="4.9929786"
+       cy="43.5"
+       fx="4.9929786"
+       fy="43.5"
+       r="2.5" />
+    <linearGradient
+       id="linearGradient6956">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="0"
+         id="stop6958" />
+      <stop
+         style="stop-color:#d3d7cf;stop-opacity:1"
+         offset="1"
+         id="stop6960" />
+    </linearGradient>
+    <radialGradient
+       xlink:href="#linearGradient6956"
+       id="radialGradient10802"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(6.5938614,0,0,6.0581812,132.06639,-56.601701)"
+       cx="-15.113025"
+       cy="15.017189"
+       fx="-15.113025"
+       fy="15.017189"
+       r="9" />
+    <linearGradient
+       id="linearGradient3177">
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1;"
+         offset="0"
+         id="stop3179" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:0;"
+         offset="1"
+         id="stop3181" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3177"
+       id="linearGradient3183"
+       x1="12.78919"
+       y1="0.21081063"
+       x2="16.43354"
+       y2="19.430988"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(2.1524517,0,0,1.9039645,-1.6434481,-2.1392692)" />
+    <linearGradient
+       id="linearGradient3167">
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:1;"
+         offset="0"
+         id="stop3169" />
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:0;"
+         offset="1"
+         id="stop3171" />
+    </linearGradient>
+    <linearGradient
+       xlink:href="#linearGradient3167"
+       id="linearGradient3186"
+       gradientUnits="userSpaceOnUse"
+       x1="5.6787376"
+       y1="-9.7172785"
+       x2="17.825142"
+       y2="11.213716"
+       gradientTransform="matrix(0.670403,0,0,0.9224329,25.939391,0.3499054)" />
+  </defs>
+  <metadata
+     id="metadata4913">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage"; />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1">
+    <g
+       id="g2043"
+       transform="matrix(1.2024648,0,0,0.8635651,-4.8098618,5.2536993)">
+      <g
+         style="display:inline"
+         id="g2036">
+        <g
+           id="g3712"
+           style="opacity:0.4"
+           transform="matrix(1.052632,0,0,1.285713,-1.263158,-13.42854)">
+          <rect
+             y="40"
+             x="38"
+             height="7"
+             width="5"
+             id="rect2801"
+             
style="opacity:1;fill:url(#radialGradient2094);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+          <rect
+             transform="scale(-1,-1)"
+             y="-47"
+             x="-10"
+             height="7"
+             width="5"
+             id="rect3696"
+             
style="opacity:1;fill:url(#radialGradient2096);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+          <rect
+             y="40"
+             x="10"
+             height="7.0000005"
+             width="28"
+             id="rect3700"
+             
style="opacity:1;fill:url(#linearGradient2098);fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
 />
+        </g>
+      </g>
+    </g>
+    <rect
+       
style="opacity:1;fill:url(#radialGradient10802);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:0.99999976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.69999992;stroke-opacity:1"
+       id="rect5985"
+       width="43.046074"
+       height="40.141006"
+       x="2.5096188"
+       y="3.411984"
+       rx="2.2093277"
+       ry="2.2093277" />
+    <g
+       id="g1719">
+      <rect
+         
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.396629;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5699-9-9"
+         width="0.98789489"
+         height="5.0302887"
+         x="6"
+         y="36.969711" />
+      <rect
+         
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.396629;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5701-1-7"
+         width="0.98789489"
+         height="5.0302887"
+         x="13"
+         y="36.969711" />
+      <rect
+         
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.436236;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3163-2-3"
+         width="0.98789489"
+         height="8"
+         x="36.969711"
+         y="-14"
+         transform="rotate(90)" />
+    </g>
+    <g
+       id="g1719-9"
+       transform="translate(10,0.0302887)">
+      <rect
+         
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.396629;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5699-9-9-4"
+         width="0.98789489"
+         height="5.0302887"
+         x="6"
+         y="36.969711" />
+      <rect
+         
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.396629;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5701-1-7-7"
+         width="0.98789489"
+         height="5.0302887"
+         x="13"
+         y="36.969711" />
+      <rect
+         
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.436236;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3163-2-3-8"
+         width="0.98789489"
+         height="8"
+         x="36.969711"
+         y="-14"
+         transform="rotate(90)" />
+    </g>
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.596525;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3159-8"
+       width="1.150296"
+       height="15"
+       x="19"
+       y="-43"
+       transform="rotate(90)" />
+    <g
+       id="g1033-5"
+       transform="translate(1,11)">
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.407927;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3161-62"
+         width="0.96971166"
+         height="7"
+         x="13.030289"
+         y="-12"
+         transform="rotate(90)" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5699-9"
+         width="0.98789489"
+         height="8"
+         x="5"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5701-1"
+         width="0.98789489"
+         height="8"
+         x="12"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.436236;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3163-2"
+         width="0.98789489"
+         height="8"
+         x="6"
+         y="-13"
+         transform="rotate(90)" />
+    </g>
+    <g
+       id="g1033-7"
+       transform="translate(11,11)">
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.407927;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3161-0"
+         width="0.96971166"
+         height="7"
+         x="13.030289"
+         y="-12"
+         transform="rotate(90)" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5699-93"
+         width="0.98789489"
+         height="8"
+         x="5"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5701-6"
+         width="0.98789489"
+         height="8"
+         x="12"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.436236;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3163-0"
+         width="0.98789489"
+         height="8"
+         x="6"
+         y="-13"
+         transform="rotate(90)" />
+    </g>
+    <g
+       id="g1033-5-7"
+       transform="translate(1.012105,21)">
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.407927;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3161-62-9"
+         width="0.96971166"
+         height="7"
+         x="13.030289"
+         y="-12"
+         transform="rotate(90)" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5699-9-2"
+         width="0.98789489"
+         height="8"
+         x="5"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5701-1-0"
+         width="0.98789489"
+         height="8"
+         x="12"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.436236;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3163-2-2"
+         width="0.98789489"
+         height="8"
+         x="6"
+         y="-13"
+         transform="rotate(90)" />
+    </g>
+    <g
+       id="g1033-7-3"
+       transform="translate(11.012105,21)">
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.407927;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3161-0-7"
+         width="0.96971166"
+         height="7"
+         x="13.030289"
+         y="-12"
+         transform="rotate(90)" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5699-93-5"
+         width="0.98789489"
+         height="8"
+         x="5"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5701-6-9"
+         width="0.98789489"
+         height="8"
+         x="12"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.436236;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3163-0-2"
+         width="0.98789489"
+         height="8"
+         x="6"
+         y="-13"
+         transform="rotate(90)" />
+    </g>
+    <rect
+       
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.596525;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3159"
+       width="1.150296"
+       height="15"
+       x="7"
+       y="-43"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.596525;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3159-6"
+       width="1.150296"
+       height="15"
+       x="10"
+       y="-43"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.596525;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3159-2"
+       width="1.150296"
+       height="15"
+       x="12.849705"
+       y="-43"
+       transform="rotate(90)" />
+    <rect
+       
style="fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.596525;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect3159-61"
+       width="1.150296"
+       height="15"
+       x="16"
+       y="-43"
+       transform="rotate(90)" />
+    <g
+       id="g1033"
+       transform="translate(1.012105,1)">
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.407927;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3161"
+         width="0.96971166"
+         height="7"
+         x="13.030289"
+         y="-12"
+         transform="rotate(90)" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5699"
+         width="0.98789489"
+         height="8"
+         x="5"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5701"
+         width="0.98789489"
+         height="8"
+         x="12"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.436236;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3163"
+         width="0.98789489"
+         height="8"
+         x="6"
+         y="-13"
+         transform="rotate(90)" />
+    </g>
+    <g
+       id="g1033-3"
+       transform="translate(11.012105,1)">
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.407927;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3161-6"
+         width="0.96971166"
+         height="7"
+         x="13.030289"
+         y="-12"
+         transform="rotate(90)" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5699-7"
+         width="0.98789489"
+         height="8"
+         x="5"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.500188;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect5701-5"
+         width="0.98789489"
+         height="8"
+         x="12"
+         y="6" />
+      <rect
+         
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:0.436236;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         id="rect3163-3"
+         width="0.98789489"
+         height="8"
+         x="6"
+         y="-13"
+         transform="rotate(90)" />
+    </g>
+    <rect
+       
style="opacity:1;fill:#babdb6;fill-opacity:1;stroke:none;stroke-width:1.06106;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="rect5705"
+       width="0.98789489"
+       height="36"
+       x="26"
+       y="6" />
+    <rect
+       
style="opacity:0.51000001;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0.69999992;stroke-opacity:1"
+       id="rect6964"
+       width="41.01918"
+       height="37.762966"
+       x="3.5331275"
+       y="4.8124871"
+       rx="1"
+       ry="1" />
+    <path
+       
style="opacity:0.22222224;fill:none;fill-opacity:1;stroke:url(#linearGradient3183);stroke-width:0.99999994;stroke-miterlimit:4;stroke-opacity:1"
+       d="M 3.6211583,11.692977 L 3.4483942,4.5246062 L 44.634269,4.5246062 L 44.517741,11.188483"
+       id="rect3175" />
+  </g>
+</svg>
diff --git a/data/org.gnome.evolution.calendar.gschema.xml.in 
b/data/org.gnome.evolution.calendar.gschema.xml.in
index 0703e95aac..1c9724eb09 100644
--- a/data/org.gnome.evolution.calendar.gschema.xml.in
+++ b/data/org.gnome.evolution.calendar.gschema.xml.in
@@ -341,7 +341,7 @@
     </key>
     <key name="show-week-numbers" type="b">
       <default>false</default>
-      <_summary>Show week numbers in Day View, Work Week View, and Date Navigator</_summary>
+      <_summary>Show week numbers in Day View, Work Week View, Year view and Date Navigator</_summary>
       <_description>Whether to show week numbers in various places in the Calendar</_description>
     </key>
     <key name="tag-vpane-position" type="d">
@@ -475,6 +475,35 @@
       <default>false</default>
       <_summary>Whether to use markdown editor for the description in the component editor.</_summary>
     </key>
+    <key name="year-show-day-names" type="b">
+      <default>true</default>
+      <_summary>Show week day names in the Year View</_summary>
+    </key>
+    <key name="year-show-preview" type="b">
+      <default>true</default>
+      <_summary>Show the preview pane in the Year View</_summary>
+      <_description>If “true”, show the preview pane in the Year View</_description>
+    </key>
+    <key name="year-hpane-position" type="i">
+      <default>400</default>
+      <_summary>Year view horizontal pane position</_summary>
+      <_description>Position of the horizontal pane, between the year calendar and the list of events for 
the selected day in the year view, in pixels</_description>
+    </key>
+    <key name="year-layout" type="i">
+      <default>0</default>
+      <_summary>Layout style for the Year View</_summary>
+      <_description>The layout style determines where to place the preview pane. “0” (Horizontal View) 
places the preview pane below the calendar.  “1” (Vertical View) places the preview pane next to the 
calendar.</_description>
+    </key>
+    <key name="year-hpreview-position" type="i">
+      <default>400</default>
+      <_summary>Year view horizontal preview position</_summary>
+      <_description>Position of the horizontal event preview for the year view, in pixels</_description>
+    </key>
+    <key name="year-vpreview-position" type="i">
+      <default>400</default>
+      <_summary>Year view vertical preview position</_summary>
+      <_description>Position of the vertical event preview for the year view, in pixels</_description>
+    </key>
 
     <!-- The following keys are deprecated. -->
 
diff --git a/data/ui/evolution-calendars.ui b/data/ui/evolution-calendars.ui
index 7663df94e2..fa68e3b84d 100644
--- a/data/ui/evolution-calendars.ui
+++ b/data/ui/evolution-calendars.ui
@@ -31,6 +31,14 @@
           <menuitem action='calendar-show-tag-vpane'/>
         </placeholder>
       </menu>
+      <placeholder name='view-custom-menus'>
+        <menu action='calendar-preview-menu'>
+          <menuitem action='calendar-preview'/>
+          <separator/>
+          <menuitem action='calendar-preview-horizontal'/>
+          <menuitem action='calendar-preview-vertical'/>
+        </menu>
+      </placeholder>
     </menu>
     <placeholder name='custom-menus'>
       <menu action='calendar-actions-menu'>
@@ -60,8 +68,8 @@
     <toolitem action='calendar-view-day'/>
     <toolitem action='calendar-view-workweek'/>
     <toolitem action='calendar-view-week'/>
-
     <toolitem action='calendar-view-month'/>
+    <toolitem action='calendar-view-year'/>
     <toolitem action='calendar-view-list'/>
   </toolbar>
   <toolbar name='close-toolbar'>
@@ -97,6 +105,7 @@
       <menuitem action='calendar-view-workweek'/>
       <menuitem action='calendar-view-week'/>
       <menuitem action='calendar-view-month'/>
+      <menuitem action='calendar-view-year'/>
       <menuitem action='calendar-view-list'/>
     </menu>
     <menuitem action='calendar-popup-go-today'/>
diff --git a/data/views/calendar/galview.xml b/data/views/calendar/galview.xml
index dfd0850458..f5506f54ad 100644
--- a/data/views/calendar/galview.xml
+++ b/data/views/calendar/galview.xml
@@ -8,6 +8,8 @@
     type="week_view" accelerator="&lt;Control&gt;k"/>
   <GalView id="Month_View" _title="_Month View" filename="Month_View.galview"
     type="month_view" accelerator="&lt;Control&gt;m"/>
+  <GalView id="Year_View" _title="_Year View" filename="Year_View.galview"
+    type="year_view" accelerator="&lt;Control&gt;i"/>
   <GalView id="List_View" _title="_List View" filename="List_View.galview"
     type="etable" accelerator="&lt;Control&gt;l"/>
 </GalViewCollection>
diff --git a/docs/reference/evolution-util/evolution-util-docs.sgml.in 
b/docs/reference/evolution-util/evolution-util-docs.sgml.in
index 7f7b3bb5ab..46c1ed59ad 100644
--- a/docs/reference/evolution-util/evolution-util-docs.sgml.in
+++ b/docs/reference/evolution-util/evolution-util-docs.sgml.in
@@ -284,6 +284,7 @@
     <xi:include href="xml/e-menu-tool-action.xml"/>
     <xi:include href="xml/e-menu-tool-button.xml"/>
     <xi:include href="xml/e-mktemp.xml"/>
+    <xi:include href="xml/e-month-widget.xml"/>
     <xi:include href="xml/e-name-selector-dialog.xml"/>
     <xi:include href="xml/e-name-selector-entry.xml"/>
     <xi:include href="xml/e-name-selector-list.xml"/>
@@ -327,6 +328,10 @@
     <title>Index</title>
     <xi:include href="xml/api-index-full.xml"><xi:fallback /></xi:include>
   </index>
+  <index id="api-index-3-46" role="3.46">
+    <title>Index of new symbols in 3.46</title>
+    <xi:include href="xml/api-index-3.46.xml"><xi:fallback /></xi:include>
+  </index>
   <index id="api-index-3-44" role="3.44">
     <title>Index of new symbols in 3.44</title>
     <xi:include href="xml/api-index-3.44.xml"><xi:fallback /></xi:include>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index b003bb2dad..c3b5e1b29a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -110,6 +110,7 @@ src/calendar/gui/e-timezone-entry.c
 src/calendar/gui/e-to-do-pane.c
 src/calendar/gui/e-week-view.c
 src/calendar/gui/e-week-view-main-item.c
+src/calendar/gui/e-year-view.c
 src/calendar/gui/itip-utils.c
 src/calendar/gui/memotypes.xml.in
 src/calendar/gui/misc.c
diff --git a/src/calendar/gui/CMakeLists.txt b/src/calendar/gui/CMakeLists.txt
index 61168406de..a6241f9aa5 100644
--- a/src/calendar/gui/CMakeLists.txt
+++ b/src/calendar/gui/CMakeLists.txt
@@ -83,6 +83,7 @@ set(SOURCES
        e-week-view-titles-item.c
        e-weekday-chooser.c
        e-timezone-entry.c
+       e-year-view.c
        itip-utils.c
        misc.c
        print.c
@@ -161,6 +162,7 @@ set(HEADERS
        e-week-view.h
        e-weekday-chooser.h
        e-timezone-entry.h
+       e-year-view.h
        itip-utils.h
        misc.h
        print.h
diff --git a/src/calendar/gui/calendar-view.c b/src/calendar/gui/calendar-view.c
index 6571239ba9..b36bac0caf 100644
--- a/src/calendar/gui/calendar-view.c
+++ b/src/calendar/gui/calendar-view.c
@@ -37,6 +37,11 @@ G_DEFINE_TYPE (
        gal_view_calendar_month,
        GAL_TYPE_VIEW)
 
+G_DEFINE_TYPE (
+       GalViewCalendarYear,
+       gal_view_calendar_year,
+       GAL_TYPE_VIEW)
+
 static void
 gal_view_calendar_day_class_init (GalViewClass *class)
 {
@@ -81,3 +86,13 @@ gal_view_calendar_month_init (GalView *view)
 {
 }
 
+static void
+gal_view_calendar_year_class_init (GalViewClass *class)
+{
+       class->type_code = "year_view";
+}
+
+static void
+gal_view_calendar_year_init (GalView *view)
+{
+}
diff --git a/src/calendar/gui/calendar-view.h b/src/calendar/gui/calendar-view.h
index 14de9aabef..30907e4f6b 100644
--- a/src/calendar/gui/calendar-view.h
+++ b/src/calendar/gui/calendar-view.h
@@ -29,6 +29,8 @@
        (gal_view_calendar_week_get_type ())
 #define GAL_TYPE_VIEW_CALENDAR_MONTH \
        (gal_view_calendar_month_get_type ())
+#define GAL_TYPE_VIEW_CALENDAR_YEAR \
+       (gal_view_calendar_year_get_type ())
 
 G_BEGIN_DECLS
 
@@ -44,10 +46,14 @@ typedef struct _GalViewClass GalViewCalendarWeekClass;
 typedef struct _GalView GalViewCalendarMonth;
 typedef struct _GalViewClass GalViewCalendarMonthClass;
 
+typedef struct _GalView GalViewCalendarYear;
+typedef struct _GalViewClass GalViewCalendarYearClass;
+
 GType          gal_view_calendar_day_get_type          (void) G_GNUC_CONST;
 GType          gal_view_calendar_work_week_get_type    (void) G_GNUC_CONST;
 GType          gal_view_calendar_week_get_type         (void) G_GNUC_CONST;
 GType          gal_view_calendar_month_get_type        (void) G_GNUC_CONST;
+GType          gal_view_calendar_year_get_type         (void) G_GNUC_CONST;
 
 G_END_DECLS
 
diff --git a/src/calendar/gui/comp-util.c b/src/calendar/gui/comp-util.c
index f307008c85..ce688e7111 100644
--- a/src/calendar/gui/comp-util.c
+++ b/src/calendar/gui/comp-util.c
@@ -1853,3 +1853,499 @@ cal_comp_util_format_itt (ICalTime *itt,
        tm = e_cal_util_icaltime_to_tm (itt);
        e_datetime_format_format_tm_inline ("calendar", "table", i_cal_time_is_date (itt) ? DTFormatKindDate 
: DTFormatKindDateTime, &tm, buffer, buffer_size);
 }
+
+ICalTime *
+cal_comp_util_date_time_to_zone (ECalComponentDateTime *dt,
+                                ECalClient *client,
+                                ICalTimezone *default_zone)
+{
+       ICalTime *itt;
+       ICalTimezone *zone = NULL;
+       const gchar *tzid;
+
+       if (!dt)
+               return NULL;
+
+       itt = i_cal_time_clone (e_cal_component_datetime_get_value (dt));
+       tzid = e_cal_component_datetime_get_tzid (dt);
+
+       if (tzid && *tzid) {
+               if (!e_cal_client_get_timezone_sync (client, tzid, &zone, NULL, NULL))
+                       zone = NULL;
+       } else if (i_cal_time_is_utc (itt)) {
+               zone = i_cal_timezone_get_utc_timezone ();
+       }
+
+       if (zone) {
+               i_cal_time_convert_timezone (itt, zone, default_zone);
+               i_cal_time_set_timezone (itt, default_zone);
+       }
+
+       return itt;
+}
+
+gchar *
+cal_comp_util_dup_attendees_status_info (ECalComponent *comp,
+                                        ECalClient *cal_client,
+                                        ESourceRegistry *registry)
+{
+       struct _values {
+               ICalParameterPartstat status;
+               const gchar *caption;
+               gint count;
+       } values[] = {
+               { I_CAL_PARTSTAT_ACCEPTED,    N_("Accepted"),     0 },
+               { I_CAL_PARTSTAT_DECLINED,    N_("Declined"),     0 },
+               { I_CAL_PARTSTAT_TENTATIVE,   N_("Tentative"),    0 },
+               { I_CAL_PARTSTAT_DELEGATED,   N_("Delegated"),    0 },
+               { I_CAL_PARTSTAT_NEEDSACTION, N_("Needs action"), 0 },
+               { I_CAL_PARTSTAT_NONE,        N_("Other"),        0 },
+               { I_CAL_PARTSTAT_X,           NULL,              -1 }
+       };
+       GSList *attendees = NULL, *link;
+       gboolean have = FALSE;
+       gchar *res = NULL;
+       gint ii;
+
+       g_return_val_if_fail (E_IS_CAL_CLIENT (cal_client), NULL);
+
+       if (registry) {
+               g_return_val_if_fail (E_IS_SOURCE_REGISTRY (registry), NULL);
+               g_object_ref (registry);
+       } else {
+               GError *error = NULL;
+
+               registry = e_source_registry_new_sync (NULL, &error);
+               if (!registry)
+                       g_warning ("%s: Failed to create source registry: %s", G_STRFUNC, error ? 
error->message : "Unknown error");
+               g_clear_error (&error);
+       }
+
+       if (!comp || !e_cal_component_has_attendees (comp) ||
+           !itip_organizer_is_user_ex (registry, comp, cal_client, TRUE)) {
+               g_clear_object (&registry);
+               return NULL;
+       }
+
+       attendees = e_cal_component_get_attendees (comp);
+
+       for (link = attendees; link; link = g_slist_next (link)) {
+               ECalComponentAttendee *att = link->data;
+
+               if (att && e_cal_component_attendee_get_cutype (att) == I_CAL_CUTYPE_INDIVIDUAL &&
+                   (e_cal_component_attendee_get_role (att) == I_CAL_ROLE_CHAIR ||
+                    e_cal_component_attendee_get_role (att) == I_CAL_ROLE_REQPARTICIPANT ||
+                    e_cal_component_attendee_get_role (att) == I_CAL_ROLE_OPTPARTICIPANT)) {
+                       have = TRUE;
+
+                       for (ii = 0; values[ii].count != -1; ii++) {
+                               if (e_cal_component_attendee_get_partstat (att) == values[ii].status || 
values[ii].status == I_CAL_PARTSTAT_NONE) {
+                                       values[ii].count++;
+                                       break;
+                               }
+                       }
+               }
+       }
+
+       if (have) {
+               GString *str = g_string_new ("");
+
+               for (ii = 0; values[ii].count != -1; ii++) {
+                       if (values[ii].count > 0) {
+                               if (str->str && *str->str)
+                                       g_string_append (str, "   ");
+
+                               g_string_append_printf (str, "%s: %d", _(values[ii].caption), 
values[ii].count);
+                       }
+               }
+
+               g_string_prepend (str, ": ");
+
+               /* Translators: 'Status' here means the state of the attendees, the resulting string will be 
in a form:
+                * Status: Accepted: X   Declined: Y   ... */
+               g_string_prepend (str, _("Status"));
+
+               res = g_string_free (str, FALSE);
+       }
+
+       g_slist_free_full (attendees, e_cal_component_attendee_free);
+
+       g_clear_object (&registry);
+
+       return res;
+}
+
+gchar *
+cal_comp_util_describe (ECalComponent *comp,
+                       ECalClient *client,
+                       ICalTimezone *default_zone,
+                       ECalCompUtilDescribeFlags flags)
+{
+       gboolean use_markup = (flags & E_CAL_COMP_UTIL_DESCRIBE_FLAG_USE_MARKUP) != 0;
+       ECalComponentDateTime *dtstart = NULL, *dtend = NULL;
+       ICalTime *itt_start, *itt_end;
+       ICalComponent *icalcomp;
+       gchar *summary;
+       const gchar *location;
+       gchar *timediff = NULL, *tmp;
+       gchar timestr[255];
+       GString *markup;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+       g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
+
+       timestr[0] = 0;
+       markup = g_string_sized_new (256);
+       icalcomp = e_cal_component_get_icalcomponent (comp);
+       summary = e_calendar_view_dup_component_summary (icalcomp);
+       location = i_cal_component_get_location (icalcomp);
+
+       if (location && !*location)
+               location = NULL;
+
+       if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_EVENT) {
+               dtstart = e_cal_component_get_dtstart (comp);
+               dtend = e_cal_component_get_dtend (comp);
+       } else if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_TODO) {
+               dtstart = e_cal_component_get_dtstart (comp);
+       } else if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_JOURNAL) {
+               dtstart = e_cal_component_get_dtstart (comp);
+       }
+
+       itt_start = cal_comp_util_date_time_to_zone (dtstart, client, default_zone);
+       itt_end = cal_comp_util_date_time_to_zone (dtend, client, default_zone);
+
+       if ((flags & E_CAL_COMP_UTIL_DESCRIBE_FLAG_ONLY_TIME) != 0 &&
+           (itt_start && (!itt_end || i_cal_time_compare_date_only (itt_start, itt_end) == 0))) {
+               if ((flags & E_CAL_COMP_UTIL_DESCRIBE_FLAG_24HOUR_FORMAT) != 0) {
+                       g_snprintf (timestr, sizeof (timestr), "%d:%02d", i_cal_time_get_hour (itt_start), 
i_cal_time_get_minute (itt_start));
+               } else {
+                       gint hour = i_cal_time_get_hour (itt_start);
+                       const gchar *suffix;
+
+                       if (hour < 12) {
+                               /* String to use in 12-hour time format for times in the morning. */
+                               suffix = _("am");
+                       } else {
+                               hour -= 12;
+                               /* String to use in 12-hour time format for times in the afternoon. */
+                               suffix = _("pm");
+                       }
+
+                       if (hour == 0)
+                               hour = 12;
+
+                       if (!i_cal_time_get_minute (itt_start))
+                               g_snprintf (timestr, sizeof (timestr), "%d %s", hour, suffix);
+                       else
+                               g_snprintf (timestr, sizeof (timestr), "%d:%02d %s", hour, 
i_cal_time_get_minute (itt_start), suffix);
+               }
+       } else if (itt_start) {
+               cal_comp_util_format_itt (itt_start, timestr, sizeof (timestr) - 1);
+       }
+
+       if (itt_start && itt_end) {
+               gint64 start, end;
+
+               start = i_cal_time_as_timet (itt_start);
+               end = i_cal_time_as_timet (itt_end);
+
+               if (start < end)
+                       timediff = e_cal_util_seconds_to_string (end - start);
+       }
+
+       if (!summary || !*summary)
+               g_clear_pointer (&summary, g_free);
+
+       if (use_markup) {
+               tmp = g_markup_printf_escaped ("<b>%s</b>", summary ? summary : _( "No Summary"));
+               g_string_append (markup, tmp);
+               g_free (tmp);
+       } else {
+               g_string_append (markup, summary ? summary : _( "No Summary"));
+       }
+
+       if (*timestr) {
+               GSList *parts = NULL, *link;
+               const gchar *use_timestr = timestr;
+               const gchar *use_timediff = timediff;
+               const gchar *use_location = location;
+               gchar *escaped_timestr = NULL;
+               gchar *escaped_timediff = NULL;
+               gchar *escaped_location = NULL;
+
+               g_string_append_c (markup, '\n');
+
+               if (use_markup) {
+                       escaped_timestr = g_markup_escape_text (timestr, -1);
+                       use_timestr = escaped_timestr;
+
+                       if (timediff && *timediff) {
+                               escaped_timediff = g_markup_escape_text (timediff, -1);
+                               use_timediff = escaped_timediff;
+                       }
+
+                       if (location) {
+                               escaped_location = g_markup_escape_text (location, -1);
+                               use_location = escaped_location;
+                       }
+               }
+
+               if (timediff && *timediff) {
+                       if (use_location) {
+                               parts = g_slist_prepend (parts, (gpointer) use_timestr);
+                               parts = g_slist_prepend (parts, (gpointer) " (");
+                               parts = g_slist_prepend (parts, (gpointer) use_timediff);
+                               parts = g_slist_prepend (parts, (gpointer) ") ");
+                               parts = g_slist_prepend (parts, (gpointer) use_location);
+                       } else {
+                               parts = g_slist_prepend (parts, (gpointer) use_timestr);
+                               parts = g_slist_prepend (parts, (gpointer) " (");
+                               parts = g_slist_prepend (parts, (gpointer) use_timediff);
+                               parts = g_slist_prepend (parts, (gpointer) ")");
+                       }
+               } else if (use_location) {
+                               parts = g_slist_prepend (parts, (gpointer) use_timestr);
+                               parts = g_slist_prepend (parts, (gpointer) " ");
+                               parts = g_slist_prepend (parts, (gpointer) use_location);
+               } else {
+                       parts = g_slist_prepend (parts, (gpointer) use_timestr);
+               }
+
+               if (!(flags & E_CAL_COMP_UTIL_DESCRIBE_FLAG_RTL))
+                       parts = g_slist_reverse (parts);
+
+               if (use_markup)
+                       g_string_append (markup, "<small>");
+               for (link = parts; link; link = g_slist_next (link)) {
+                       g_string_append (markup, (const gchar *) link->data);
+               }
+               if (use_markup)
+                       g_string_append (markup, "</small>");
+
+               g_slist_free (parts);
+               g_free (escaped_timestr);
+               g_free (escaped_timediff);
+               g_free (escaped_location);
+       } else if (location) {
+               g_string_append_c (markup, '\n');
+
+               if (use_markup) {
+                       tmp = g_markup_printf_escaped ("%s", location);
+
+                       g_string_append (markup, "<small>");
+                       g_string_append (markup, tmp);
+                       g_string_append (markup, "</small>");
+
+                       g_free (tmp);
+               } else {
+                       g_string_append (markup, location);
+               }
+       }
+
+       g_free (timediff);
+       g_free (summary);
+
+       e_cal_component_datetime_free (dtstart);
+       e_cal_component_datetime_free (dtend);
+       g_clear_object (&itt_start);
+       g_clear_object (&itt_end);
+
+       return g_string_free (markup, FALSE);
+}
+
+gchar *
+cal_comp_util_dup_tooltip (ECalComponent *comp,
+                          ECalClient *client,
+                          ESourceRegistry *registry,
+                          ICalTimezone *default_zone)
+{
+       ECalComponentOrganizer *organizer;
+       ECalComponentDateTime *dtstart, *dtend;
+       ICalComponent *icalcomp;
+       ICalTimezone *zone;
+       GString *tooltip;
+       const gchar *description;
+       gchar *tmp;
+
+       g_return_val_if_fail (E_IS_CAL_COMPONENT (comp), NULL);
+       g_return_val_if_fail (E_IS_CAL_CLIENT (client), NULL);
+
+       icalcomp = e_cal_component_get_icalcomponent (comp);
+       tooltip = g_string_sized_new (256);
+
+       tmp = e_calendar_view_dup_component_summary (icalcomp);
+       e_util_markup_append_escaped (tooltip, "<b>%s</b>", tmp && *tmp ? tmp : _("No Summary"));
+       g_clear_pointer (&tmp, g_free);
+
+       organizer = e_cal_component_get_organizer (comp);
+       if (organizer && e_cal_component_organizer_get_cn (organizer)) {
+               const gchar *email;
+
+               email = itip_strip_mailto (e_cal_component_organizer_get_value (organizer));
+
+               if (email) {
+                       /* Translators: It will display "Organizer: NameOfTheUser <email ofuser com>" */
+                       tmp = g_strdup_printf (_("Organizer: %s <%s>"), e_cal_component_organizer_get_cn 
(organizer), email);
+               } else {
+                       /* Translators: It will display "Organizer: NameOfTheUser" */
+                       tmp = g_strdup_printf (_("Organizer: %s"), e_cal_component_organizer_get_cn 
(organizer));
+               }
+
+               g_string_append_c (tooltip, '\n');
+               e_util_markup_append_escaped_text (tooltip, tmp);
+               g_clear_pointer (&tmp, g_free);
+       }
+
+       e_cal_component_organizer_free (organizer);
+
+       tmp = e_cal_component_get_location (comp);
+
+       if (tmp && *tmp) {
+               g_string_append_c (tooltip, '\n');
+               /* Translators: It will display "Location: PlaceOfTheMeeting" */
+               e_util_markup_append_escaped (tooltip, _("Location: %s"), tmp);
+       }
+
+       g_clear_pointer (&tmp, g_free);
+
+       dtstart = e_cal_component_get_dtstart (comp);
+       dtend = e_cal_component_get_dtend (comp);
+
+       if (dtstart && e_cal_component_datetime_get_tzid (dtstart)) {
+               zone = i_cal_component_get_timezone (icalcomp, e_cal_component_datetime_get_tzid (dtstart));
+               if (!zone &&
+                   !e_cal_client_get_timezone_sync (client, e_cal_component_datetime_get_tzid (dtstart), 
&zone, NULL, NULL))
+                       zone = NULL;
+
+               if (!zone)
+                       zone = default_zone;
+
+       } else {
+               zone = default_zone;
+       }
+
+       if (dtstart && e_cal_component_datetime_get_value (dtstart)) {
+               struct tm tmp_tm;
+               time_t t_start, t_end;
+               gchar *tmp1;
+
+               t_start = i_cal_time_as_timet_with_zone (e_cal_component_datetime_get_value (dtstart), zone);
+
+               if (dtend && e_cal_component_datetime_get_value (dtend)) {
+                       ICalTimezone *end_zone = default_zone;
+
+                       if (e_cal_component_datetime_get_tzid (dtend)) {
+                               end_zone = i_cal_component_get_timezone (e_cal_component_get_icalcomponent 
(comp), e_cal_component_datetime_get_tzid (dtend));
+                               if (!end_zone &&
+                                   !e_cal_client_get_timezone_sync (client, 
e_cal_component_datetime_get_tzid (dtend), &end_zone, NULL, NULL))
+                                       end_zone = NULL;
+
+                               if (!end_zone)
+                                       end_zone = default_zone;
+                       }
+
+                       t_end = i_cal_time_as_timet_with_zone (e_cal_component_datetime_get_value (dtend), 
end_zone);
+               } else {
+                       t_end = t_start;
+               }
+
+               tmp_tm = e_cal_util_icaltime_to_tm_with_zone (e_cal_component_datetime_get_value (dtstart), 
zone, default_zone);
+               tmp1 = e_datetime_format_format_tm ("calendar", "table", i_cal_time_is_date 
(e_cal_component_datetime_get_value (dtstart)) ?
+                       DTFormatKindDate : DTFormatKindDateTime, &tmp_tm);
+
+               g_string_append_c (tooltip, '\n');
+
+               if (t_end > t_start) {
+                       tmp = e_cal_util_seconds_to_string (t_end - t_start);
+                       /* Translators: It will display "Time: ActualStartDateAndTime (DurationOfTheMeeting)" 
*/
+                       e_util_markup_append_escaped (tooltip, _("Time: %s (%s)"), tmp1, tmp);
+                       g_clear_pointer (&tmp, g_free);
+               } else {
+                       /* Translators: It will display "Time: ActualStartDateAndTime" */
+                       e_util_markup_append_escaped (tooltip, _("Time: %s"), tmp1);
+               }
+
+               g_clear_pointer (&tmp1, g_free);
+
+               if (zone && !cal_comp_util_compare_event_timezones (comp, client, default_zone)) {
+                       tmp_tm = e_cal_util_icaltime_to_tm_with_zone (e_cal_component_datetime_get_value 
(dtstart), zone, zone);
+                       tmp1 = e_datetime_format_format_tm ("calendar", "table", i_cal_time_is_date 
(e_cal_component_datetime_get_value (dtstart)) ?
+                               DTFormatKindDate : DTFormatKindDateTime, &tmp_tm);
+                       e_util_markup_append_escaped (tooltip, "\n\t[ %s %s ]", tmp1, 
i_cal_timezone_get_display_name (zone));
+                       g_clear_pointer (&tmp1, g_free);
+               }
+       }
+
+       e_cal_component_datetime_free (dtstart);
+       e_cal_component_datetime_free (dtend);
+
+       if (e_cal_component_get_vtype (comp) == E_CAL_COMPONENT_TODO) {
+               ECalComponentDateTime *due;
+
+               due = e_cal_component_get_due (comp);
+
+               if (due) {
+                       ICalTime *itt;
+
+                       itt = cal_comp_util_date_time_to_zone (due, client, default_zone);
+                       if (itt) {
+                               gchar timestr[255] = { 0, };
+
+                               cal_comp_util_format_itt (itt, timestr, sizeof (timestr) - 1);
+
+                               if (*timestr) {
+                                       g_string_append_c (tooltip, '\n');
+                                       /* Translators: It's for a task due date, it will display "Due: 
DateAndTime" */
+                                       e_util_markup_append_escaped (tooltip, _("Due: %s"), timestr);
+                               }
+
+                               g_clear_object (&itt);
+                       }
+               }
+
+               e_cal_component_datetime_free (due);
+       }
+
+       tmp = cal_comp_util_dup_attendees_status_info (comp, client, registry);
+       if (tmp) {
+               g_string_append_c (tooltip, '\n');
+               e_util_markup_append_escaped_text (tooltip, tmp);
+               g_clear_pointer (&tmp, g_free);
+       }
+
+       tmp = cal_comp_util_get_attendee_comments (icalcomp);
+       if (tmp) {
+               g_string_append_c (tooltip, '\n');
+               e_util_markup_append_escaped_text (tooltip, tmp);
+               g_clear_pointer (&tmp, g_free);
+       }
+
+       description = i_cal_component_get_description (icalcomp);
+       if (description && *description && g_utf8_validate (description, -1, NULL) &&
+           !g_str_equal (description, "\r") &&
+           !g_str_equal (description, "\n") &&
+           !g_str_equal (description, "\r\n")) {
+               #define MAX_TOOLTIP_DESCRIPTION_LEN 1024
+               glong len;
+
+               len = g_utf8_strlen (description, -1);
+               if (len > MAX_TOOLTIP_DESCRIPTION_LEN) {
+                       GString *str;
+                       const gchar *end;
+
+                       end = g_utf8_offset_to_pointer (description, MAX_TOOLTIP_DESCRIPTION_LEN);
+                       str = g_string_new_len (description, end - description);
+                       g_string_append (str, _("…"));
+
+                       tmp = g_string_free (str, FALSE);
+               }
+
+               g_string_append_c (tooltip, '\n');
+               g_string_append_c (tooltip, '\n');
+               e_util_markup_append_escaped_text (tooltip, tmp ? tmp : description);
+               g_clear_pointer (&tmp, g_free);
+       }
+
+       return g_string_free (tooltip, FALSE);
+}
diff --git a/src/calendar/gui/comp-util.h b/src/calendar/gui/comp-util.h
index 97902e4d51..3eab0c4309 100644
--- a/src/calendar/gui/comp-util.h
+++ b/src/calendar/gui/comp-util.h
@@ -175,5 +175,42 @@ void               cal_comp_util_maybe_ensure_allday_timezone_properties
 void           cal_comp_util_format_itt        (ICalTime *itt,
                                                 gchar *buffer,
                                                 gint buffer_size);
+ICalTime *     cal_comp_util_date_time_to_zone (ECalComponentDateTime *dt,
+                                                ECalClient *client,
+                                                ICalTimezone *default_zone);
+gchar *                cal_comp_util_dup_attendees_status_info
+                                               (ECalComponent *comp,
+                                                ECalClient *cal_client,
+                                                ESourceRegistry *registry);
+
+typedef enum _ECalCompUtilDescribeFlags {
+       E_CAL_COMP_UTIL_DESCRIBE_FLAG_NONE              = 0,
+       E_CAL_COMP_UTIL_DESCRIBE_FLAG_RTL               = 1 << 0,
+       E_CAL_COMP_UTIL_DESCRIBE_FLAG_USE_MARKUP        = 1 << 1,
+       E_CAL_COMP_UTIL_DESCRIBE_FLAG_ONLY_TIME         = 1 << 2,
+       E_CAL_COMP_UTIL_DESCRIBE_FLAG_24HOUR_FORMAT     = 1 << 3
+} ECalCompUtilDescribeFlags;
+
+/**
+ * ECalCompUtilDescribeFlags:
+ * @E_CAL_COMP_UTIL_DESCRIBE_FLAG_NONE: no special flag set
+ * @E_CAL_COMP_UTIL_DESCRIBE_FLAG_RTL: set to order text in right-to-left direction
+ * @E_CAL_COMP_UTIL_DESCRIBE_FLAG_USE_MARKUP: use markup in the output texts
+ * @E_CAL_COMP_UTIL_DESCRIBE_FLAG_ONLY_TIME: show only time, instead of date and time, for times
+ * @E_CAL_COMP_UTIL_DESCRIBE_FLAG_24HOUR_FORMAT: use 24-hour format for ONLY_TIME values
+ *
+ * Flags to use for cal_comp_util_describe().
+ *
+ * Since: 3.46
+ **/
+
+gchar *                cal_comp_util_describe          (ECalComponent *comp,
+                                                ECalClient *client,
+                                                ICalTimezone *default_zone,
+                                                ECalCompUtilDescribeFlags flags);
+gchar *                cal_comp_util_dup_tooltip       (ECalComponent *comp,
+                                                ECalClient *client,
+                                                ESourceRegistry *registry,
+                                                ICalTimezone *default_zone);
 
 #endif
diff --git a/src/calendar/gui/e-cal-list-view.c b/src/calendar/gui/e-cal-list-view.c
index 3536376ecd..ea341fac1c 100644
--- a/src/calendar/gui/e-cal-list-view.c
+++ b/src/calendar/gui/e-cal-list-view.c
@@ -42,9 +42,6 @@
 struct _ECalListViewPrivate {
        /* The main display table */
        ETable *table;
-
-       /* The last ECalendarViewEvent we returned from e_cal_list_view_get_selected_events(), to be freed */
-       ECalendarViewEvent *cursor_event;
 };
 
 enum {
@@ -61,7 +58,7 @@ static const gchar *icon_names[] = {
 
 static void      e_cal_list_view_dispose                (GObject *object);
 
-static GList    *e_cal_list_view_get_selected_events    (ECalendarView *cal_view);
+static GSList    *e_cal_list_view_get_selected_events    (ECalendarView *cal_view);
 static gboolean  e_cal_list_view_get_selected_time_range (ECalendarView *cal_view, time_t *start_time, 
time_t *end_time);
 static gboolean  e_cal_list_view_get_visible_time_range (ECalendarView *cal_view, time_t *start_time,
                                                         time_t *end_time);
@@ -161,7 +158,6 @@ e_cal_list_view_init (ECalListView *cal_list_view)
        cal_list_view->priv = G_TYPE_INSTANCE_GET_PRIVATE (cal_list_view, E_TYPE_CAL_LIST_VIEW, 
ECalListViewPrivate);
 
        cal_list_view->priv->table = NULL;
-       cal_list_view->priv->cursor_event = NULL;
 }
 
 /* Returns the current time, for the ECellDateEdit items. */
@@ -426,8 +422,6 @@ e_cal_list_view_dispose (GObject *object)
 
        cal_list_view = E_CAL_LIST_VIEW (object);
 
-       g_clear_pointer (&cal_list_view->priv->cursor_event, g_free);
-
        if (cal_list_view->priv->table) {
                gtk_widget_destroy (GTK_WIDGET (cal_list_view->priv->table));
                cal_list_view->priv->table = NULL;
@@ -557,19 +551,16 @@ e_cal_list_view_get_selected_time_range (ECalendarView *cal_view,
                                          time_t *start_time,
                                          time_t *end_time)
 {
-       GList *selected;
+       GSList *selected;
        ICalTimezone *zone;
 
        selected = e_calendar_view_get_selected_events (cal_view);
        if (selected) {
-               ECalendarViewEvent *event = (ECalendarViewEvent *) selected->data;
+               ECalendarViewSelectionData *sel_data = selected->data;
                ECalComponent *comp;
 
-               if (!is_comp_data_valid (event))
-                       return FALSE;
-
                comp = e_cal_component_new ();
-               e_cal_component_set_icalcomponent (comp, i_cal_component_clone (event->comp_data->icalcomp));
+               e_cal_component_set_icalcomponent (comp, i_cal_component_clone (sel_data->icalcomp));
                if (start_time) {
                        ECalComponentDateTime *dt;
 
@@ -608,7 +599,7 @@ e_cal_list_view_get_selected_time_range (ECalendarView *cal_view,
                }
 
                g_object_unref (comp);
-               g_list_free (selected);
+               g_slist_free_full (selected, e_calendar_view_selection_data_free);
 
                return TRUE;
        }
@@ -616,29 +607,26 @@ e_cal_list_view_get_selected_time_range (ECalendarView *cal_view,
        return FALSE;
 }
 
-static GList *
+static GSList *
 e_cal_list_view_get_selected_events (ECalendarView *cal_view)
 {
-       GList *event_list = NULL;
-       gint   cursor_row;
-
-       g_clear_pointer (&E_CAL_LIST_VIEW (cal_view)->priv->cursor_event, g_free);
+       GSList *selection = NULL;
+       gint cursor_row;
 
-       cursor_row = e_table_get_cursor_row (
-               E_CAL_LIST_VIEW (cal_view)->priv->table);
+       cursor_row = e_table_get_cursor_row (E_CAL_LIST_VIEW (cal_view)->priv->table);
 
        if (cursor_row >= 0) {
-               ECalendarViewEvent *event;
-
-               event = E_CAL_LIST_VIEW (cal_view)->priv->cursor_event = g_new0 (ECalendarViewEvent, 1);
-               event->comp_data =
-                       e_cal_model_get_component_at (
-                               e_calendar_view_get_model (cal_view),
-                               cursor_row);
-               event_list = g_list_prepend (event_list, event);
+               ECalModelComponent *comp_data;
+
+               comp_data = e_cal_model_get_component_at (e_calendar_view_get_model (cal_view), cursor_row);
+
+               if (comp_data) {
+                       selection = g_slist_prepend (selection,
+                               e_calendar_view_selection_data_new (comp_data->client, comp_data->icalcomp));
+               }
        }
 
-       return event_list;
+       return selection;
 }
 
 static gboolean
diff --git a/src/calendar/gui/e-cal-model.c b/src/calendar/gui/e-cal-model.c
index c4b79d3a5e..99f10685bb 100644
--- a/src/calendar/gui/e-cal-model.c
+++ b/src/calendar/gui/e-cal-model.c
@@ -3821,78 +3821,9 @@ e_cal_model_get_attendees_status_info (ECalModel *model,
                                        ECalComponent *comp,
                                        ECalClient *cal_client)
 {
-       struct _values {
-               ICalParameterPartstat status;
-               const gchar *caption;
-               gint count;
-       } values[] = {
-               { I_CAL_PARTSTAT_ACCEPTED,    N_("Accepted"),     0 },
-               { I_CAL_PARTSTAT_DECLINED,    N_("Declined"),     0 },
-               { I_CAL_PARTSTAT_TENTATIVE,   N_("Tentative"),    0 },
-               { I_CAL_PARTSTAT_DELEGATED,   N_("Delegated"),    0 },
-               { I_CAL_PARTSTAT_NEEDSACTION, N_("Needs action"), 0 },
-               { I_CAL_PARTSTAT_NONE,        N_("Other"),        0 },
-               { I_CAL_PARTSTAT_X,           NULL,              -1 }
-       };
-
-       ESourceRegistry *registry;
-       GSList *attendees = NULL, *a;
-       gboolean have = FALSE;
-       gchar *res = NULL;
-       gint i;
-
        g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);
 
-       registry = e_cal_model_get_registry (model);
-
-       if (!comp || !e_cal_component_has_attendees (comp) ||
-           !itip_organizer_is_user_ex (registry, comp, cal_client, TRUE))
-               return NULL;
-
-       attendees = e_cal_component_get_attendees (comp);
-
-       for (a = attendees; a; a = a->next) {
-               ECalComponentAttendee *att = a->data;
-
-               if (att && e_cal_component_attendee_get_cutype (att) == I_CAL_CUTYPE_INDIVIDUAL &&
-                   (e_cal_component_attendee_get_role (att) == I_CAL_ROLE_CHAIR ||
-                    e_cal_component_attendee_get_role (att) == I_CAL_ROLE_REQPARTICIPANT ||
-                    e_cal_component_attendee_get_role (att) == I_CAL_ROLE_OPTPARTICIPANT)) {
-                       have = TRUE;
-
-                       for (i = 0; values[i].count != -1; i++) {
-                               if (e_cal_component_attendee_get_partstat (att) == values[i].status || 
values[i].status == I_CAL_PARTSTAT_NONE) {
-                                       values[i].count++;
-                                       break;
-                               }
-                       }
-               }
-       }
-
-       if (have) {
-               GString *str = g_string_new ("");
-
-               for (i = 0; values[i].count != -1; i++) {
-                       if (values[i].count > 0) {
-                               if (str->str && *str->str)
-                                       g_string_append (str, "   ");
-
-                               g_string_append_printf (str, "%s: %d", _(values[i].caption), values[i].count);
-                       }
-               }
-
-               g_string_prepend (str, ": ");
-
-               /* To Translators: 'Status' here means the state of the attendees, the resulting string will 
be in a form:
-                * Status: Accepted: X   Declined: Y   ... */
-               g_string_prepend (str, _("Status"));
-
-               res = g_string_free (str, FALSE);
-       }
-
-       g_slist_free_full (attendees, e_cal_component_attendee_free);
-
-       return res;
+       return cal_comp_util_dup_attendees_status_info (comp, cal_client, e_cal_model_get_registry (model));
 }
 
 /**
diff --git a/src/calendar/gui/e-calendar-view.c b/src/calendar/gui/e-calendar-view.c
index 35b2b6d0d3..f81e7f2418 100644
--- a/src/calendar/gui/e-calendar-view.c
+++ b/src/calendar/gui/e-calendar-view.c
@@ -54,7 +54,7 @@ struct _ECalendarViewPrivate {
        ECalModel *model;
 
        gint time_divisions;
-       GSList *selected_cut_list;
+       GSList *selected_cut_list; /* ECalendarViewSelectionData * */
 
        GtkTargetList *copy_target_list;
        GtkTargetList *paste_target_list;
@@ -95,6 +95,31 @@ G_DEFINE_ABSTRACT_TYPE_WITH_CODE (
        G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL)
        G_IMPLEMENT_INTERFACE (E_TYPE_SELECTABLE, calendar_view_selectable_init));
 
+ECalendarViewSelectionData *
+e_calendar_view_selection_data_new (ECalClient *client,
+                                   ICalComponent *icalcomp)
+{
+       ECalendarViewSelectionData *sel_data;
+
+       sel_data = g_slice_new0 (ECalendarViewSelectionData);
+       sel_data->client = g_object_ref (client);
+       sel_data->icalcomp = g_object_ref (icalcomp);
+
+       return sel_data;
+}
+
+void
+e_calendar_view_selection_data_free (gpointer ptr)
+{
+       ECalendarViewSelectionData *sel_data = ptr;
+
+       if (sel_data) {
+               g_clear_object (&sel_data->client);
+               g_clear_object (&sel_data->icalcomp);
+               g_slice_free (ECalendarViewSelectionData, sel_data);
+       }
+}
+
 static void
 calendar_view_add_retract_data (ECalComponent *comp,
                                 const gchar *retract_comment,
@@ -155,7 +180,7 @@ calendar_view_check_for_retract (ECalComponent *comp,
 
 static void
 calendar_view_delete_event (ECalendarView *cal_view,
-                            ECalendarViewEvent *event,
+                            ECalendarViewSelectionData *sel_data,
                            gboolean only_occurrence,
                            ECalObjModType mod)
 {
@@ -165,22 +190,28 @@ calendar_view_delete_event (ECalendarView *cal_view,
        ESourceRegistry *registry;
        ECalClient *client;
        ICalComponent *icalcomp;
+       ICalTime *itt_start = NULL, *itt_end = NULL;
        time_t instance_start;
-       gboolean delete = TRUE;
-
-       if (!is_comp_data_valid (event))
-               return;
+       gboolean do_delete = TRUE;
 
        model = e_calendar_view_get_model (cal_view);
        registry = e_cal_model_get_registry (model);
 
        comp = e_cal_component_new ();
-       e_cal_component_set_icalcomponent (comp, i_cal_component_clone (event->comp_data->icalcomp));
+       e_cal_component_set_icalcomponent (comp, i_cal_component_clone (sel_data->icalcomp));
        vtype = e_cal_component_get_vtype (comp);
 
-       /* Remember structure values, because the 'event' can be freed while the question dialog is opened */
-       instance_start = event->comp_data->instance_start;
-       client = g_object_ref (event->comp_data->client);
+       cal_comp_get_instance_times (sel_data->client, sel_data->icalcomp,
+               e_cal_model_get_timezone (model),
+               &itt_start, &itt_end, NULL);
+
+       instance_start = itt_start ? i_cal_time_as_timet_with_zone (itt_start,
+               i_cal_time_get_timezone (itt_start)) : 0;
+
+       g_clear_object (&itt_start);
+       g_clear_object (&itt_end);
+
+       client = g_object_ref (sel_data->client);
        icalcomp = e_cal_component_get_icalcomponent (comp);
 
        /*FIXME remove it once the we don't set the recurrence id for all the generated instances */
@@ -192,7 +223,7 @@ calendar_view_delete_event (ECalendarView *cal_view,
                gchar *retract_comment = NULL;
                gboolean retract = FALSE;
 
-               delete = e_cal_dialogs_prompt_retract (GTK_WIDGET (cal_view), comp, &retract_comment, 
&retract);
+               do_delete = e_cal_dialogs_prompt_retract (GTK_WIDGET (cal_view), comp, &retract_comment, 
&retract);
                if (retract) {
                        ICalComponent *icomp;
 
@@ -203,10 +234,10 @@ calendar_view_delete_event (ECalendarView *cal_view,
                        e_cal_ops_send_component (model, client, icomp);
                }
        } else if (e_cal_model_get_confirm_delete (model))
-               delete = e_cal_dialogs_delete_component (
+               do_delete = e_cal_dialogs_delete_component (
                        comp, FALSE, 1, vtype, GTK_WIDGET (cal_view));
 
-       if (delete) {
+       if (do_delete) {
                const gchar *uid;
                gchar *rid;
 
@@ -247,6 +278,7 @@ calendar_view_delete_event (ECalendarView *cal_view,
 
                uid = e_cal_component_get_uid (comp);
                if (!uid || !*uid) {
+                       g_clear_object (&client);
                        g_object_unref (comp);
                        g_free (rid);
                        return;
@@ -403,8 +435,7 @@ calendar_view_dispose (GObject *object)
        g_clear_pointer (&priv->paste_target_list, gtk_target_list_unref);
 
        if (priv->selected_cut_list) {
-               g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL);
-               g_slist_free (priv->selected_cut_list);
+               g_slist_free_full (priv->selected_cut_list, e_calendar_view_selection_data_free);
                priv->selected_cut_list = NULL;
        }
 
@@ -446,7 +477,7 @@ calendar_view_update_actions (ESelectable *selectable,
        ECalendarView *view;
        GtkAction *action;
        GtkTargetList *target_list;
-       GList *list, *iter;
+       GSList *selected, *link;
        gboolean can_paste = FALSE;
        gboolean sources_are_editable = TRUE;
        gboolean recurring = FALSE;
@@ -459,19 +490,16 @@ calendar_view_update_actions (ESelectable *selectable,
        view = E_CALENDAR_VIEW (selectable);
        is_editing = e_calendar_view_is_editing (view);
 
-       list = e_calendar_view_get_selected_events (view);
-       n_selected = g_list_length (list);
+       selected = e_calendar_view_get_selected_events (view);
+       n_selected = g_slist_length (selected);
 
-       for (iter = list; iter != NULL; iter = iter->next) {
-               ECalendarViewEvent *event = iter->data;
+       for (link = selected; link; link = g_slist_next (link)) {
+               ECalendarViewSelectionData *sel_data = link->data;
                ECalClient *client;
                ICalComponent *icomp;
 
-               if (event == NULL || event->comp_data == NULL)
-                       continue;
-
-               client = event->comp_data->client;
-               icomp = event->comp_data->icalcomp;
+               client = sel_data->client;
+               icomp = sel_data->icalcomp;
 
                sources_are_editable = sources_are_editable && !e_client_is_readonly (E_CLIENT (client));
 
@@ -480,7 +508,7 @@ calendar_view_update_actions (ESelectable *selectable,
                        e_cal_util_component_has_recurrences (icomp);
        }
 
-       g_list_free (list);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 
        target_list = e_selectable_get_paste_target_list (selectable);
        for (ii = 0; ii < n_clipboard_targets && !can_paste; ii++)
@@ -517,24 +545,21 @@ calendar_view_cut_clipboard (ESelectable *selectable)
 {
        ECalendarView *cal_view;
        ECalendarViewPrivate *priv;
-       GList *selected, *l;
+       GSList *selected;
 
        cal_view = E_CALENDAR_VIEW (selectable);
        priv = cal_view->priv;
 
+       g_slist_free_full (priv->selected_cut_list, e_calendar_view_selection_data_free);
+       priv->selected_cut_list = NULL;
+
        selected = e_calendar_view_get_selected_events (cal_view);
        if (!selected)
                return;
 
        e_selectable_copy_clipboard (selectable);
 
-       for (l = selected; l != NULL; l = g_list_next (l)) {
-               ECalendarViewEvent *event = (ECalendarViewEvent *) l->data;
-
-               priv->selected_cut_list = g_slist_prepend (priv->selected_cut_list, g_object_ref 
(event->comp_data));
-       }
-
-       g_list_free (selected);
+       priv->selected_cut_list = selected;
 }
 
 static void
@@ -605,10 +630,9 @@ calendar_view_copy_clipboard (ESelectable *selectable)
 {
        ECalendarView *cal_view;
        ECalendarViewPrivate *priv;
-       GList *selected, *l;
+       GSList *selected, *link;
        gchar *comp_str;
        ICalComponent *vcal_comp;
-       ECalendarViewEvent *event;
        GtkClipboard *clipboard;
 
        cal_view = E_CALENDAR_VIEW (selectable);
@@ -619,32 +643,24 @@ calendar_view_copy_clipboard (ESelectable *selectable)
                return;
 
        if (priv->selected_cut_list) {
-               g_slist_foreach (priv->selected_cut_list, (GFunc) g_object_unref, NULL);
-               g_slist_free (priv->selected_cut_list);
+               g_slist_free_full (priv->selected_cut_list, e_calendar_view_selection_data_free);
                priv->selected_cut_list = NULL;
        }
 
        /* create top-level VCALENDAR component and add VTIMEZONE's */
        vcal_comp = e_cal_util_new_top_level ();
-       for (l = selected; l != NULL; l = l->next) {
-               event = (ECalendarViewEvent *) l->data;
-
-               if (event && is_comp_data_valid (event)) {
-                       e_cal_util_add_timezones_from_component (vcal_comp, event->comp_data->icalcomp);
+       for (link = selected; link; link = g_slist_next (link)) {
+               ECalendarViewSelectionData *sel_data = link->data;
 
-                       add_related_timezones (vcal_comp, event->comp_data->icalcomp, 
event->comp_data->client);
-               }
+               e_cal_util_add_timezones_from_component (vcal_comp, sel_data->icalcomp);
+               add_related_timezones (vcal_comp, sel_data->icalcomp, sel_data->client);
        }
 
-       for (l = selected; l != NULL; l = l->next) {
+       for (link = selected; link; link = g_slist_next (link)) {
+               ECalendarViewSelectionData *sel_data = link->data;
                ICalComponent *new_icomp;
 
-               event = (ECalendarViewEvent *) l->data;
-
-               if (!is_comp_data_valid (event))
-                       continue;
-
-               new_icomp = i_cal_component_clone (event->comp_data->icalcomp);
+               new_icomp = i_cal_component_clone (sel_data->icalcomp);
 
                /* do not remove RECURRENCE-IDs from copied objects */
                i_cal_component_take_component (vcal_comp, new_icomp);
@@ -660,7 +676,7 @@ calendar_view_copy_clipboard (ESelectable *selectable)
        /* free memory */
        g_object_unref (vcal_comp);
        g_free (comp_str);
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -807,7 +823,7 @@ e_calendar_view_add_event_sync (ECalModel *model,
 
 typedef struct {
        ECalendarView *cal_view;
-       GSList *selected_cut_list; /* ECalModelComponent * */
+       GSList *selected_cut_list; /* ECalendarViewSelectionData * */
        GSList *copied_uids; /* gchar * */
        gchar *ical_str;
        time_t selection_start;
@@ -834,27 +850,27 @@ paste_clipboard_data_free (gpointer ptr)
                        registry = e_cal_model_get_registry (model);
 
                        for (link = pcd->selected_cut_list; link != NULL; link = g_slist_next (link)) {
-                               ECalModelComponent *comp_data = (ECalModelComponent *) link->data;
+                               ECalendarViewSelectionData *sel_data = link->data;
                                ECalComponent *comp;
                                const gchar *uid;
                                GSList *found = NULL;
 
                                /* Remove them one by one after ensuring it has been copied to the 
destination successfully */
-                               found = g_slist_find_custom (pcd->copied_uids, i_cal_component_get_uid 
(comp_data->icalcomp), (GCompareFunc) strcmp);
+                               found = g_slist_find_custom (pcd->copied_uids, i_cal_component_get_uid 
(sel_data->icalcomp), (GCompareFunc) strcmp);
                                if (!found)
                                        continue;
 
                                g_free (found->data);
                                pcd->copied_uids = g_slist_delete_link (pcd->copied_uids, found);
 
-                               comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone 
(comp_data->icalcomp));
+                               comp = e_cal_component_new_from_icalcomponent (i_cal_component_clone 
(sel_data->icalcomp));
 
                                if (itip_has_any_attendees (comp) &&
-                                   (itip_organizer_is_user (registry, comp, comp_data->client) ||
-                                   itip_sentby_is_user (registry, comp, comp_data->client))
-                                   && e_cal_dialogs_cancel_component ((GtkWindow *) pcd->top_level, 
comp_data->client, comp, TRUE))
+                                   (itip_organizer_is_user (registry, comp, sel_data->client) ||
+                                   itip_sentby_is_user (registry, comp, sel_data->client))
+                                   && e_cal_dialogs_cancel_component ((GtkWindow *) pcd->top_level, 
sel_data->client, comp, TRUE))
                                        itip_send_component_with_model (model, I_CAL_METHOD_CANCEL,
-                                               comp, comp_data->client, NULL, NULL, NULL,
+                                               comp, sel_data->client, NULL, NULL, NULL,
                                                E_ITIP_SEND_COMPONENT_FLAG_STRIP_ALARMS | 
E_ITIP_SEND_COMPONENT_FLAG_ENSURE_MASTER_OBJECT);
 
                                uid = e_cal_component_get_uid (comp);
@@ -863,10 +879,10 @@ paste_clipboard_data_free (gpointer ptr)
 
                                        /* when cutting detached instances, only cut that instance */
                                        rid = e_cal_component_get_recurid_as_string (comp);
-                                       e_cal_ops_remove_component (model, comp_data->client, uid, rid, 
E_CAL_OBJ_MOD_THIS, TRUE);
+                                       e_cal_ops_remove_component (model, sel_data->client, uid, rid, 
E_CAL_OBJ_MOD_THIS, TRUE);
                                        g_free (rid);
                                } else {
-                                       e_cal_ops_remove_component (model, comp_data->client, uid, NULL, 
E_CAL_OBJ_MOD_ALL, FALSE);
+                                       e_cal_ops_remove_component (model, sel_data->client, uid, NULL, 
E_CAL_OBJ_MOD_ALL, FALSE);
                                }
 
                                g_object_unref (comp);
@@ -883,7 +899,7 @@ paste_clipboard_data_free (gpointer ptr)
                g_clear_object (&pcd->cal_view);
                g_clear_object (&pcd->top_level);
                g_clear_object (&pcd->client);
-               g_slist_free_full (pcd->selected_cut_list, g_object_unref);
+               g_slist_free_full (pcd->selected_cut_list, e_calendar_view_selection_data_free);
                g_slist_free_full (pcd->copied_uids, g_free);
                g_free (pcd->ical_str);
                g_slice_free (PasteClipboardData, pcd);
@@ -1054,12 +1070,7 @@ calendar_view_paste_clipboard (ESelectable *selectable)
 
        /* Paste text into an event being edited. */
        if (gtk_clipboard_wait_is_text_available (clipboard)) {
-               ECalendarViewClass *class;
-
-               class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
-               g_return_if_fail (class->paste_text != NULL);
-
-               class->paste_text (cal_view);
+               e_calendar_view_paste_text (cal_view);
 
        /* Paste iCalendar data into the view. */
        } else if (e_clipboard_wait_is_calendar_available (clipboard)) {
@@ -1112,23 +1123,19 @@ static void
 calendar_view_delete_selection (ESelectable *selectable)
 {
        ECalendarView *cal_view;
-       GList *selected, *iter;
+       GSList *selected, *link;
 
        cal_view = E_CALENDAR_VIEW (selectable);
 
        selected = e_calendar_view_get_selected_events (cal_view);
 
-       for (iter = selected; iter != NULL; iter = iter->next) {
-               ECalendarViewEvent *event = iter->data;
-
-               /* XXX Why would this ever be NULL? */
-               if (event == NULL)
-                       continue;
+       for (link = selected; link; link = g_slist_next (link)) {
+               ECalendarViewSelectionData *sel_data = link->data;
 
-               calendar_view_delete_event (cal_view, event, FALSE, E_CAL_OBJ_MOD_ALL);
+               calendar_view_delete_event (cal_view, sel_data, FALSE, E_CAL_OBJ_MOD_ALL);
        }
 
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static gchar *
@@ -1485,7 +1492,9 @@ e_calendar_view_set_time_divisions (ECalendarView *cal_view,
        g_object_notify (G_OBJECT (cal_view), "time-divisions");
 }
 
-GList *
+/* (transfer full) (element-type ECalendarViewSelectionData):
+   free with g_slist_free_full (selection, e_calendar_view_selection_data_free); */
+GSList *
 e_calendar_view_get_selected_events (ECalendarView *cal_view)
 {
        ECalendarViewClass *class;
@@ -1577,12 +1586,25 @@ e_calendar_view_update_query (ECalendarView *cal_view)
        class->update_query (cal_view);
 }
 
+void
+e_calendar_view_paste_text (ECalendarView *cal_view)
+{
+       ECalendarViewClass *class;
+
+       g_return_if_fail (E_IS_CALENDAR_VIEW (cal_view));
+
+       class = E_CALENDAR_VIEW_GET_CLASS (cal_view);
+       g_return_if_fail (class->paste_text != NULL);
+
+       class->paste_text (cal_view);
+}
+
 void
 e_calendar_view_delete_selected_occurrence (ECalendarView *cal_view,
                                            ECalObjModType mod)
 {
-       ECalendarViewEvent *event;
-       GList *selected;
+       ECalendarViewSelectionData *sel_data;
+       GSList *selected;
 
        g_return_if_fail (mod == E_CAL_OBJ_MOD_THIS || mod == E_CAL_OBJ_MOD_THIS_AND_FUTURE);
 
@@ -1590,26 +1612,23 @@ e_calendar_view_delete_selected_occurrence (ECalendarView *cal_view,
        if (!selected)
                return;
 
-       event = (ECalendarViewEvent *) selected->data;
-       if (is_comp_data_valid (event)) {
-               calendar_view_delete_event (cal_view, event, TRUE, mod);
-       }
+       sel_data = selected->data;
+       calendar_view_delete_event (cal_view, sel_data, TRUE, mod);
 
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 void
 e_calendar_view_open_event (ECalendarView *cal_view)
 {
-       GList *selected;
+       GSList *selected;
 
        selected = e_calendar_view_get_selected_events (cal_view);
        if (selected) {
-               ECalendarViewEvent *event = (ECalendarViewEvent *) selected->data;
-               if (event && is_comp_data_valid (event))
-                       e_calendar_view_edit_appointment (cal_view, event->comp_data->client, 
event->comp_data->icalcomp, EDIT_EVENT_AUTODETECT);
+               ECalendarViewSelectionData *sel_data = selected->data;
+               e_calendar_view_edit_appointment (cal_view, sel_data->client, sel_data->icalcomp, 
EDIT_EVENT_AUTODETECT);
 
-               g_list_free (selected);
+               g_slist_free_full (selected, e_calendar_view_selection_data_free);
        }
 }
 
diff --git a/src/calendar/gui/e-calendar-view.h b/src/calendar/gui/e-calendar-view.h
index ba9cfaac53..434d221b79 100644
--- a/src/calendar/gui/e-calendar-view.h
+++ b/src/calendar/gui/e-calendar-view.h
@@ -122,6 +122,16 @@ typedef struct {
        gint event_num;
 } ECalendarViewEventData;
 
+typedef struct _ECalendarViewSelectionData {
+       ECalClient *client;
+       ICalComponent *icalcomp;
+} ECalendarViewSelectionData;
+
+ECalendarViewSelectionData *
+               e_calendar_view_selection_data_new      (ECalClient *client,
+                                                        ICalComponent *icalcomp);
+void           e_calendar_view_selection_data_free     (gpointer ptr);
+
 typedef enum {
        EDIT_EVENT_AUTODETECT,
        EDIT_EVENT_FORCE_MEETING,
@@ -163,7 +173,7 @@ struct _ECalendarViewClass {
                                                 gint64 exact_date);
 
        /* Virtual methods */
-       GList *         (*get_selected_events)  (ECalendarView *cal_view);
+       GSList *        (*get_selected_events)  (ECalendarView *cal_view); /* ECalendarViewSelectionData * */
        gboolean        (*get_selected_time_range)
                                                (ECalendarView *cal_view,
                                                 time_t *start_time,
@@ -203,7 +213,7 @@ GtkTargetList *     e_calendar_view_get_copy_target_list
 GtkTargetList *        e_calendar_view_get_paste_target_list
                                                (ECalendarView *cal_view);
 
-GList *                e_calendar_view_get_selected_events
+GSList *       e_calendar_view_get_selected_events /* ECalendarViewSelectionData * */
                                                (ECalendarView *cal_view);
 gboolean       e_calendar_view_get_selected_time_range
                                                (ECalendarView *cal_view,
@@ -224,6 +234,7 @@ void                e_calendar_view_precalc_visible_time_range
                                                 time_t *out_start_time,
                                                 time_t *out_end_time);
 void           e_calendar_view_update_query    (ECalendarView *cal_view);
+void           e_calendar_view_paste_text      (ECalendarView *cal_view);
 
 void           e_calendar_view_delete_selected_occurrence
                                                (ECalendarView *cal_view,
diff --git a/src/calendar/gui/e-day-view.c b/src/calendar/gui/e-day-view.c
index d772b2175f..6b05dbe4e5 100644
--- a/src/calendar/gui/e-day-view.c
+++ b/src/calendar/gui/e-day-view.c
@@ -1783,11 +1783,11 @@ day_view_popup_menu (GtkWidget *widget)
 }
 
 /* Returns the currently-selected event, or NULL if none */
-static GList *
+static GSList *
 day_view_get_selected_events (ECalendarView *cal_view)
 {
        EDayViewEvent *event = NULL;
-       GList *list = NULL;
+       GSList *selection = NULL;
        EDayView *day_view = (EDayView *) cal_view;
 
        g_return_val_if_fail (E_IS_DAY_VIEW (day_view), NULL);
@@ -1826,10 +1826,12 @@ day_view_get_selected_events (ECalendarView *cal_view)
                }
        }
 
-       if (event)
-               list = g_list_append (list, event);
+       if (event && event->comp_data) {
+               selection = g_slist_prepend (selection,
+                       e_calendar_view_selection_data_new (event->comp_data->client, 
event->comp_data->icalcomp));
+       }
 
-       return list;
+       return selection;
 }
 
 /* This sets the selected time range. If the start_time & end_time are not equal
diff --git a/src/calendar/gui/e-week-view.c b/src/calendar/gui/e-week-view.c
index a9146172ed..9018d00ded 100644
--- a/src/calendar/gui/e-week-view.c
+++ b/src/calendar/gui/e-week-view.c
@@ -1380,11 +1380,11 @@ week_view_popup_menu (GtkWidget *widget)
        return TRUE;
 }
 
-static GList *
+static GSList *
 week_view_get_selected_events (ECalendarView *cal_view)
 {
        EWeekViewEvent *event = NULL;
-       GList *list = NULL;
+       GSList *selection = NULL;
        EWeekView *week_view = (EWeekView *) cal_view;
 
        g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), NULL);
@@ -1406,10 +1406,12 @@ week_view_get_selected_events (ECalendarView *cal_view)
                                        week_view->popup_event_num);
        }
 
-       if (event)
-               list = g_list_prepend (list, event);
+       if (event && event->comp_data) {
+               selection = g_slist_prepend (selection,
+                       e_calendar_view_selection_data_new (event->comp_data->client, 
event->comp_data->icalcomp));
+       }
 
-       return list;
+       return selection;
 }
 
 static gboolean
diff --git a/src/calendar/gui/e-year-view.c b/src/calendar/gui/e-year-view.c
new file mode 100644
index 0000000000..99edb8738e
--- /dev/null
+++ b/src/calendar/gui/e-year-view.c
@@ -0,0 +1,2014 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-config.h"
+
+#include <e-util/e-util.h>
+
+#include "comp-util.h"
+#include "e-cal-component-preview.h"
+#include "e-cal-ops.h"
+#include "e-calendar-view.h"
+#include "itip-utils.h"
+
+#include "e-year-view.h"
+
+/* #define WITH_PREV_NEXT_BUTTONS 1 */
+
+typedef struct _ComponentData {
+       ECalClient *client;
+       ECalComponent *comp;
+       gchar *uid;
+       gchar *rid;
+
+       guint day_from; /* day of year the comp is used at from, inclusive */
+       guint day_to; /* day of year the comp is used at to, inclusive */
+
+       guint date_mark; /* YYYYMMDD */
+       guint time_mark; /* HHMMSS */
+} ComponentData;
+
+typedef struct _DayData {
+       guint n_total; /* includes n_italic */
+       guint n_italic;
+       GSList *comps_data; /* ComponentData * */
+} DayData;
+
+struct _EYearViewPrivate {
+       ESourceRegistry *registry;
+       GHashTable *client_colors; /* ESource * ~> GdkRGBA * */
+       GtkCssProvider *css_provider;
+       GtkWidget *hpaned;
+       GtkWidget *preview_paned;
+       GtkButton *prev_year_button1;
+       GtkButton *prev_year_button2;
+       GtkLabel *current_year_label;
+       GtkButton *next_year_button1;
+       GtkButton *next_year_button2;
+       GtkTreeView *tree_view;
+       GtkListStore *list_store;
+       ECalComponentPreview *preview;
+       ECalDataModel *data_model;
+       EMonthWidget *months[12];
+       DayData days[367];
+       GHashTable *comps; /* ComponentData * ~> ComponentData * (itself, just for easier lookup) */
+       gboolean clearing_comps;
+       gboolean preview_visible;
+       gboolean use_24hour_format;
+       guint current_day;
+       guint current_month;
+       guint current_year;
+};
+
+enum {
+       PROP_0,
+       PROP_PREVIEW_VISIBLE,
+       PROP_USE_24HOUR_FORMAT,
+       LAST_PROP,
+       PROP_IS_EDITING /* override property as the last */
+};
+
+static void year_view_cal_data_model_subscriber_init (ECalDataModelSubscriberInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (EYearView, e_year_view, E_TYPE_CALENDAR_VIEW,
+       G_ADD_PRIVATE (EYearView)
+       G_IMPLEMENT_INTERFACE (E_TYPE_CAL_DATA_MODEL_SUBSCRIBER, year_view_cal_data_model_subscriber_init))
+
+static GParamSpec *obj_props[LAST_PROP] = { NULL, };
+
+enum {
+       COLUMN_BGCOLOR = 0,
+       COLUMN_FGCOLOR,
+       COLUMN_HAS_ICON_NAME,
+       COLUMN_ICON_NAME,
+       COLUMN_SUMMARY,
+       COLUMN_TOOLTIP,
+       COLUMN_SORTKEY,
+       COLUMN_COMPONENT_DATA,
+       N_COLUMNS
+};
+
+static ComponentData *
+component_data_new (ECalClient *client,
+                   ECalComponent *comp)
+{
+       ComponentData *cd;
+       ECalComponentId *id;
+
+       id = e_cal_component_get_id (comp);
+
+       cd = g_new0 (ComponentData, 1);
+       cd->client = g_object_ref (client);
+       cd->comp = g_object_ref (comp);
+       cd->uid = id ? g_strdup (e_cal_component_id_get_uid (id)) : NULL;
+       cd->rid = id ? g_strdup (e_cal_component_id_get_rid (id)) : NULL;
+
+       e_cal_component_id_free (id);
+
+       return cd;
+}
+
+static void
+component_data_free (gpointer ptr)
+{
+       ComponentData *cd = ptr;
+
+       if (cd) {
+               g_clear_object (&cd->client);
+               g_clear_object (&cd->comp);
+               g_free (cd->uid);
+               g_free (cd->rid);
+               g_free (cd);
+       }
+}
+
+static guint
+component_data_hash (gconstpointer ptr)
+{
+       const ComponentData *cd = ptr;
+
+       if (!cd)
+               return 0;
+
+       return g_direct_hash (cd->client) ^
+               (cd->uid ? g_str_hash (cd->uid) : 0) ^
+               (cd->rid ? g_str_hash (cd->rid) : 0);
+}
+
+static gboolean
+component_data_equal (gconstpointer ptr1,
+                     gconstpointer ptr2)
+{
+       const ComponentData *cd1 = ptr1, *cd2 = ptr2;
+
+       if (!cd1 || !cd2)
+               return cd1 == cd2;
+
+       return cd1->client == cd2->client &&
+               g_strcmp0 (cd1->uid, cd2->uid) == 0 &&
+               g_strcmp0 (cd1->rid, cd2->rid) == 0;
+}
+
+static void
+year_view_calc_component_data (EYearView *self,
+                              ComponentData *cd,
+                              guint *out_day_from,
+                              guint *out_day_to,
+                              guint *out_date_mark,
+                              guint *out_time_mark)
+{
+       ECalComponentDateTime *dtstart, *dtend = NULL, *dt;
+       guint day_from = 0;
+       guint day_to = 0;
+       guint date_mark = 0;
+       guint time_mark = 0;
+
+       dtstart = e_cal_component_get_dtstart (cd->comp);
+
+       if (e_cal_component_get_vtype (cd->comp) == E_CAL_COMPONENT_TODO) {
+               if (!dtstart)
+                       dtstart = e_cal_component_get_due (cd->comp);
+       } else {
+               dtend = e_cal_component_get_dtend (cd->comp);
+       }
+
+       dt = dtstart ? dtstart : dtend;
+
+       if (dt) {
+               ICalTimezone *zone;
+               ICalTime *itt;
+
+               zone = e_cal_data_model_get_timezone (self->priv->data_model);
+               itt = cal_comp_util_date_time_to_zone (dt, cd->client, zone);
+
+               if (itt) {
+                       if (i_cal_time_get_year (itt) < self->priv->current_year) {
+                               i_cal_time_set_date (itt, self->priv->current_year, 1, 1);
+
+                               if (!i_cal_time_is_date (itt))
+                                       i_cal_time_set_time (itt, 0, 0, 0);
+                       } else if (i_cal_time_get_year (itt) > self->priv->current_year) {
+                               i_cal_time_set_date (itt, self->priv->current_year, 12, 31);
+
+                               if (!i_cal_time_is_date (itt))
+                                       i_cal_time_set_time (itt, 23, 59, 59);
+                       }
+
+                       day_from = i_cal_time_day_of_year (itt);
+                       day_to = day_from;
+                       date_mark = (i_cal_time_get_year (itt) * 10000) +
+                               (i_cal_time_get_month (itt) * 100) +
+                               i_cal_time_get_day (itt);
+
+                       if (!i_cal_time_is_date (itt)) {
+                               time_mark = (i_cal_time_get_hour (itt) * 10000) +
+                                       (i_cal_time_get_minute (itt) * 100) +
+                                       i_cal_time_get_second (itt);
+                       }
+
+                       g_object_unref (itt);
+               }
+
+               if (dtend && dt != dtend) {
+                       itt = cal_comp_util_date_time_to_zone (dtend, cd->client, zone);
+
+                       if (itt) {
+                               guint end_date_mark, end_time_mark = 0;
+
+                               if (i_cal_time_get_year (itt) < self->priv->current_year) {
+                                       i_cal_time_set_date (itt, self->priv->current_year, 1, 1);
+
+                                       if (!i_cal_time_is_date (itt))
+                                               i_cal_time_set_time (itt, 0, 0, 0);
+                               } else if (i_cal_time_get_year (itt) > self->priv->current_year) {
+                                       i_cal_time_set_date (itt, self->priv->current_year, 12, 31);
+
+                                       if (!i_cal_time_is_date (itt))
+                                               i_cal_time_set_time (itt, 23, 59, 59);
+                               }
+
+                               end_date_mark = (i_cal_time_get_year (itt) * 10000) +
+                                       (i_cal_time_get_month (itt) * 100) +
+                                       i_cal_time_get_day (itt);
+
+                               if (!i_cal_time_is_date (itt)) {
+                                       end_time_mark = (i_cal_time_get_hour (itt) * 10000) +
+                                               (i_cal_time_get_minute (itt) * 100) +
+                                               i_cal_time_get_second (itt);
+                               }
+
+                               if (end_date_mark > date_mark || (end_date_mark == date_mark && end_time_mark 
time_mark)) {
+                                       /* The end time is excluded */
+                                       i_cal_time_adjust (itt, i_cal_time_is_date (itt) ? -1 : 0, 0, 0, 
i_cal_time_is_date (itt) ? 0 : -1);
+                               }
+
+                               day_to = i_cal_time_day_of_year (itt);
+
+                               /* This should not happen */
+                               if (day_to < day_from)
+                                       day_to = day_from;
+
+                               g_object_unref (itt);
+                       }
+               }
+       }
+
+       e_cal_component_datetime_free (dtstart);
+       e_cal_component_datetime_free (dtend);
+
+       *out_day_from = day_from;
+       *out_day_to = day_to;
+       *out_date_mark = date_mark;
+       *out_time_mark = time_mark;
+}
+
+static void
+year_view_clear_comps (EYearView *self)
+{
+       guint ii;
+
+       for (ii = 0; ii < 367; ii++) {
+               g_slist_free (self->priv->days[ii].comps_data);
+
+               self->priv->days[ii].n_total = 0;
+               self->priv->days[ii].n_italic = 0;
+               self->priv->days[ii].comps_data = NULL;
+       }
+
+       g_hash_table_remove_all (self->priv->comps);
+}
+
+static void
+year_view_update_data_model (EYearView *self)
+{
+       time_t range_start, range_end;
+       ICalTimezone *default_zone;
+       GDate dt;
+
+       self->priv->clearing_comps = TRUE;
+       year_view_clear_comps (self);
+       e_cal_data_model_unsubscribe (self->priv->data_model, E_CAL_DATA_MODEL_SUBSCRIBER (self));
+       self->priv->clearing_comps = FALSE;
+
+       default_zone = e_cal_data_model_get_timezone (self->priv->data_model);
+
+       g_date_clear (&dt, 1);
+       g_date_set_dmy (&dt, 1, 1, self->priv->current_year);
+       range_start = time_day_begin_with_zone (cal_comp_gdate_to_timet (&dt, default_zone), default_zone);
+       g_date_set_dmy (&dt, 31, 12, self->priv->current_year);
+       range_end = time_day_end_with_zone (cal_comp_gdate_to_timet (&dt, default_zone), default_zone);
+
+       e_cal_data_model_subscribe (self->priv->data_model,
+               E_CAL_DATA_MODEL_SUBSCRIBER (self),
+               range_start, range_end);
+}
+
+static void
+year_view_get_comp_colors (EYearView *self,
+                          ECalClient *client,
+                          ECalComponent *comp,
+                          GdkRGBA *out_bgcolor,
+                          gboolean *out_bgcolor_set,
+                          GdkRGBA *out_fgcolor,
+                          gboolean *out_fgcolor_set)
+{
+       GdkRGBA *bgcolor = NULL, fgcolor = { 1.0, 1.0, 1.0, 1.0 };
+       GdkRGBA stack_bgcolor;
+       ICalProperty *prop;
+
+       g_return_if_fail (out_bgcolor);
+       g_return_if_fail (out_bgcolor_set);
+       g_return_if_fail (out_fgcolor);
+       g_return_if_fail (out_fgcolor_set);
+
+       *out_bgcolor_set = FALSE;
+       *out_fgcolor_set = FALSE;
+
+       g_return_if_fail (E_IS_CAL_CLIENT (client));
+       g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+       prop = i_cal_component_get_first_property (e_cal_component_get_icalcomponent (comp), 
I_CAL_COLOR_PROPERTY);
+       if (prop) {
+               const gchar *color_spec;
+
+               color_spec = i_cal_property_get_color (prop);
+               if (color_spec && gdk_rgba_parse (&stack_bgcolor, color_spec)) {
+                       bgcolor = &stack_bgcolor;
+               }
+
+               g_clear_object (&prop);
+       }
+
+       if (!bgcolor) {
+               ESource *source = e_client_get_source (E_CLIENT (client));
+
+               bgcolor = g_hash_table_lookup (self->priv->client_colors, source);
+
+               if (!bgcolor && !g_hash_table_contains (self->priv->client_colors, source)) {
+                       ESourceSelectable *selectable = NULL;
+
+                       if (e_cal_client_get_source_type (client) == E_CAL_CLIENT_SOURCE_TYPE_EVENTS) {
+                               selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR);
+                       } else if (e_cal_client_get_source_type (client) == E_CAL_CLIENT_SOURCE_TYPE_TASKS) {
+                               selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
+                       }
+
+                       if (selectable) {
+                               GdkRGBA rgba;
+                               gchar *color_spec;
+
+                               color_spec = e_source_selectable_dup_color (selectable);
+                               if (color_spec && gdk_rgba_parse (&rgba, color_spec)) {
+                                       bgcolor = gdk_rgba_copy (&rgba);
+                                       g_hash_table_insert (self->priv->client_colors, source, bgcolor);
+                               } else {
+                                       g_hash_table_insert (self->priv->client_colors, source, NULL);
+                               }
+
+                               g_free (color_spec);
+                       } else {
+                               g_hash_table_insert (self->priv->client_colors, source, NULL);
+                       }
+               }
+       }
+
+       if (bgcolor)
+               fgcolor = e_utils_get_text_color_for_background (bgcolor);
+
+       *out_bgcolor_set = bgcolor != NULL;
+       if (bgcolor)
+               *out_bgcolor = *bgcolor;
+
+       *out_fgcolor_set = *out_bgcolor_set;
+       *out_fgcolor = fgcolor;
+}
+
+static guint
+year_view_get_describe_flags (EYearView *self)
+{
+       return (GTK_TEXT_DIR_RTL == gtk_widget_get_direction (GTK_WIDGET (self)) ? 
E_CAL_COMP_UTIL_DESCRIBE_FLAG_RTL : 0) |
+               E_CAL_COMP_UTIL_DESCRIBE_FLAG_USE_MARKUP |
+               E_CAL_COMP_UTIL_DESCRIBE_FLAG_ONLY_TIME |
+               (self->priv->use_24hour_format ? E_CAL_COMP_UTIL_DESCRIBE_FLAG_24HOUR_FORMAT : 0);
+}
+
+static const gchar *
+year_view_get_component_icon_name (EYearView *self,
+                                  ComponentData *cd)
+{
+       const gchar *icon_name;
+       gboolean is_task = e_cal_component_get_vtype (cd->comp) == E_CAL_COMPONENT_TODO;
+
+       if (is_task && e_cal_component_has_recurrences (cd->comp)) {
+               icon_name = "stock_task-recurring";
+       } else if (e_cal_component_has_attendees (cd->comp)) {
+               if (is_task) {
+                       ESourceRegistry *registry = self->priv->registry;
+
+                       icon_name = "stock_task-assigned";
+
+                       if (itip_organizer_is_user (registry, cd->comp, cd->client)) {
+                               icon_name = "stock_task-assigned-to";
+                       } else {
+                               GSList *attendees = NULL, *link;
+
+                               attendees = e_cal_component_get_attendees (cd->comp);
+                               for (link = attendees; link; link = g_slist_next (link)) {
+                                       ECalComponentAttendee *ca = link->data;
+                                       const gchar *text;
+
+                                       text = itip_strip_mailto (e_cal_component_attendee_get_value (ca));
+                                       if (itip_address_is_user (registry, text)) {
+                                               if (e_cal_component_attendee_get_delegatedto (ca))
+                                                       icon_name = "stock_task-assigned-to";
+                                               break;
+                                       }
+                               }
+
+                               g_slist_free_full (attendees, e_cal_component_attendee_free);
+                       }
+               } else
+                       icon_name = "stock_people";
+       } else {
+               if (is_task)
+                       icon_name = "stock_task";
+               else
+                       icon_name = "appointment-new";
+       }
+
+       return icon_name;
+}
+
+static void
+year_view_add_to_list_store (EYearView *self,
+                            ComponentData *cd)
+{
+       GtkTreeIter iter;
+       GdkRGBA bgcolor, fgcolor;
+       ICalTimezone *default_zone;
+       gboolean bgcolor_set = FALSE, fgcolor_set = FALSE;
+       gchar *summary, *tooltip, *sort_key;
+
+       year_view_get_comp_colors (self, cd->client, cd->comp, &bgcolor, &bgcolor_set, &fgcolor, 
&fgcolor_set);
+
+       default_zone = e_cal_data_model_get_timezone (self->priv->data_model);
+       summary = cal_comp_util_describe (cd->comp, cd->client, default_zone, year_view_get_describe_flags 
(self));
+       tooltip = cal_comp_util_dup_tooltip (cd->comp, cd->client, self->priv->registry, default_zone);
+       sort_key = g_strdup_printf ("%08u%06u-%s-%s-%s", cd->date_mark, cd->time_mark,
+               i_cal_component_get_summary (e_cal_component_get_icalcomponent (cd->comp)),
+               cd->uid ? cd->uid : "", cd->rid ? cd->rid : "");
+
+       gtk_list_store_append (self->priv->list_store, &iter);
+       gtk_list_store_set (self->priv->list_store, &iter,
+               COLUMN_BGCOLOR, bgcolor_set ? &bgcolor : NULL,
+               COLUMN_FGCOLOR, fgcolor_set ? &fgcolor : NULL,
+               COLUMN_HAS_ICON_NAME, TRUE,
+               COLUMN_ICON_NAME, year_view_get_component_icon_name (self, cd),
+               COLUMN_SUMMARY, summary,
+               COLUMN_TOOLTIP, tooltip,
+               COLUMN_SORTKEY, sort_key,
+               COLUMN_COMPONENT_DATA, cd,
+               -1);
+
+       g_free (summary);
+       g_free (tooltip);
+       g_free (sort_key);
+}
+
+static void
+year_view_update_tree_view (EYearView *self)
+{
+       ICalTimezone *zone;
+       GDate date;
+       GtkTreeViewColumn *column;
+       GSList *link;
+       gchar buffer[128] = { 0, };
+       guint day_of_year;
+
+       zone = e_cal_data_model_get_timezone (self->priv->data_model);
+
+       g_date_clear (&date, 1);
+       g_date_set_dmy (&date, self->priv->current_day, self->priv->current_month, self->priv->current_year);
+
+       e_datetime_format_format_inline ("calendar", "table", DTFormatKindDate, cal_comp_gdate_to_timet 
(&date, zone), buffer, sizeof (buffer));
+
+       column = gtk_tree_view_get_column (self->priv->tree_view, 0);
+       gtk_tree_view_column_set_title (column, buffer);
+
+       day_of_year = g_date_get_day_of_year (&date);
+       g_return_if_fail (day_of_year < sizeof (self->priv->days));
+
+       gtk_tree_view_set_model (self->priv->tree_view, NULL);
+
+       gtk_list_store_clear (self->priv->list_store);
+
+       for (link = self->priv->days[day_of_year].comps_data; link; link = g_slist_next (link)) {
+               ComponentData *cd = link->data;
+
+               year_view_add_to_list_store (self, cd);
+       }
+
+       gtk_tree_view_set_model (self->priv->tree_view, GTK_TREE_MODEL (self->priv->list_store));
+}
+
+static void
+year_view_set_year (EYearView *self,
+                   guint year,
+                   gint month,
+                   guint day)
+{
+       gchar buffer[128];
+       gint ii;
+
+       if (self->priv->current_year == year) {
+               if ((month && self->priv->current_month != month) ||
+                   (day && self->priv->current_day != day)) {
+                       e_month_widget_set_day_selected (self->priv->months[self->priv->current_month - 1], 
self->priv->current_day, FALSE);
+
+                       if (month)
+                               self->priv->current_month = month;
+                       if (day)
+                               self->priv->current_day = day;
+
+                       e_month_widget_set_day_selected (self->priv->months[self->priv->current_month - 1], 
self->priv->current_day, TRUE);
+
+                       year_view_update_tree_view (self);
+               }
+       } else {
+               self->priv->current_year = year;
+               if (month)
+                       self->priv->current_month = month;
+               if (day)
+                       self->priv->current_day = day;
+
+               g_snprintf (buffer, sizeof (buffer), "%d", self->priv->current_year - 2);
+               gtk_button_set_label (self->priv->prev_year_button2, buffer);
+
+               g_snprintf (buffer, sizeof (buffer), "%d", self->priv->current_year - 1);
+               gtk_button_set_label (self->priv->prev_year_button1, buffer);
+
+               g_snprintf (buffer, sizeof (buffer), "%d", self->priv->current_year);
+               gtk_label_set_label (self->priv->current_year_label, buffer);
+
+               g_snprintf (buffer, sizeof (buffer), "%d", self->priv->current_year + 1);
+               gtk_button_set_label (self->priv->next_year_button1, buffer);
+
+               g_snprintf (buffer, sizeof (buffer), "%d", self->priv->current_year + 2);
+               gtk_button_set_label (self->priv->next_year_button2, buffer);
+
+               for (ii = 0; ii < 12; ii++) {
+                       e_month_widget_clear_day_tooltips (self->priv->months[ii]);
+                       e_month_widget_clear_day_css_classes (self->priv->months[ii]);
+                       e_month_widget_set_month (self->priv->months[ii], ii + 1, self->priv->current_year);
+               }
+
+               e_month_widget_set_day_selected (self->priv->months[self->priv->current_month - 1], 
self->priv->current_day, TRUE);
+
+               year_view_update_data_model (self);
+               year_view_update_tree_view (self);
+       }
+}
+
+static GSList *
+year_view_get_selected_events (ECalendarView *cal_view)
+{
+       EYearView *self;
+       GtkTreeSelection *tree_selection;
+       GtkTreeModel *model = NULL;
+       GtkTreeIter iter;
+       GList *selected, *link;
+       GSList *selection = NULL;
+
+       g_return_val_if_fail (E_IS_YEAR_VIEW (cal_view), NULL);
+
+       self = E_YEAR_VIEW (cal_view);
+
+       tree_selection = gtk_tree_view_get_selection (self->priv->tree_view);
+       selected = gtk_tree_selection_get_selected_rows (tree_selection, &model);
+
+       for (link = selected; link; link = g_list_next (link)) {
+               if (gtk_tree_model_get_iter (model, &iter, selected->data)) {
+                       ComponentData *cd = NULL;
+
+                       gtk_tree_model_get (model, &iter,
+                               COLUMN_COMPONENT_DATA, &cd,
+                               -1);
+
+                       selection = g_slist_prepend (selection,
+                               e_calendar_view_selection_data_new (cd->client, 
e_cal_component_get_icalcomponent (cd->comp)));
+               }
+       }
+
+       g_list_free_full (selected, (GDestroyNotify) gtk_tree_path_free);
+
+       return selection;
+}
+
+static gboolean
+year_view_get_selected_time_range (ECalendarView *cal_view,
+                                  time_t *start_time,
+                                  time_t *end_time)
+{
+       EYearView *self;
+       ICalTimezone *zone;
+       GDate date;
+
+       g_return_val_if_fail (E_IS_YEAR_VIEW (cal_view), FALSE);
+
+       self = E_YEAR_VIEW (cal_view);
+       zone = e_cal_data_model_get_timezone (self->priv->data_model);
+
+       g_date_clear (&date, 1);
+       g_date_set_dmy (&date, self->priv->current_day, self->priv->current_month, self->priv->current_year);
+
+       *start_time = time_day_begin (cal_comp_gdate_to_timet (&date, zone));
+       *end_time = time_day_end (*start_time);
+
+       return TRUE;
+}
+
+static void
+year_view_set_selected_time_range (ECalendarView *cal_view,
+                                  time_t start_time,
+                                  time_t end_time)
+{
+       EYearView *self;
+       ICalTimezone *zone;
+       GDate date;
+
+       g_return_if_fail (E_IS_YEAR_VIEW (cal_view));
+
+       self = E_YEAR_VIEW (cal_view);
+       zone = e_cal_data_model_get_timezone (self->priv->data_model);
+
+       time_to_gdate_with_zone (&date, start_time, zone);
+
+       year_view_set_year (self, g_date_get_year (&date), g_date_get_month (&date), g_date_get_day (&date));
+}
+
+static time_t
+year_view_add_days_in_year (time_t tt,
+                           guint year)
+{
+       return time_add_day (tt, 31 + g_date_get_days_in_month (2, year) + 31 + 30 + 31 + 30 +
+               31 + 31 + 30 + 31 + 30 + 31);
+}
+
+static gboolean
+year_view_get_visible_time_range (ECalendarView *cal_view,
+                                 time_t *start_time,
+                                 time_t *end_time)
+{
+       EYearView *self;
+       ICalTimezone *zone;
+       GDate date;
+
+       g_return_val_if_fail (E_IS_YEAR_VIEW (cal_view), FALSE);
+
+       self = E_YEAR_VIEW (cal_view);
+       zone = e_cal_data_model_get_timezone (self->priv->data_model);
+
+       g_date_clear (&date, 1);
+       g_date_set_dmy (&date, self->priv->current_day, self->priv->current_month, self->priv->current_year);
+
+       *start_time = time_year_begin_with_zone (cal_comp_gdate_to_timet (&date, zone), zone);
+       *end_time = year_view_add_days_in_year (*start_time, self->priv->current_year);
+
+       return TRUE;
+}
+
+static void
+year_view_precalc_visible_time_range (ECalendarView *cal_view,
+                                     time_t in_start_time,
+                                     time_t in_end_time,
+                                     time_t *out_start_time,
+                                     time_t *out_end_time)
+{
+       EYearView *self;
+       ICalTimezone *zone;
+       ICalTime *itt;
+
+       g_return_if_fail (E_IS_YEAR_VIEW (cal_view));
+       g_return_if_fail (out_start_time != NULL);
+       g_return_if_fail (out_end_time != NULL);
+
+       self = E_YEAR_VIEW (cal_view);
+       zone = e_cal_data_model_get_timezone (self->priv->data_model);
+
+       itt = i_cal_time_new_from_timet_with_zone (in_start_time, FALSE, zone);
+
+       i_cal_time_set_date (itt, i_cal_time_get_year (itt), self->priv->current_month, 
self->priv->current_day);
+
+       *out_start_time = i_cal_time_as_timet_with_zone (itt, zone);
+       *out_end_time = *out_start_time + (24 * 3600);
+
+       g_clear_object (&itt);
+}
+
+static void
+year_view_paste_text (ECalendarView *cal_view)
+{
+       g_return_if_fail (E_IS_YEAR_VIEW (cal_view));
+
+       /* Do nothing, inline editing not allowed here */
+}
+
+static void
+year_view_month_widget_day_clicked_cb (EMonthWidget *month_widget,
+                                      GdkEventButton *event,
+                                      guint year,
+                                      gint /* GDateMonth */ month,
+                                      guint day,
+                                      gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       if (event->button == GDK_BUTTON_PRIMARY)
+               year_view_set_year (self, year, month, day);
+}
+
+static void
+year_view_prev_year_clicked_cb (GtkWidget *button,
+                               gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       year_view_set_year (self, self->priv->current_year - 1, 0, 0);
+}
+
+static void
+year_view_prev_year2_clicked_cb (GtkWidget *button,
+                                gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       year_view_set_year (self, self->priv->current_year - 2, 0, 0);
+}
+
+static void
+year_view_next_year_clicked_cb (GtkWidget *button,
+                               gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       year_view_set_year (self, self->priv->current_year + 1, 0, 0);
+}
+
+static void
+year_view_next_year2_clicked_cb (GtkWidget *button,
+                                gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       year_view_set_year (self, self->priv->current_year + 2, 0, 0);
+}
+
+static void
+year_view_update_colors (EYearView *self)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+
+       model = GTK_TREE_MODEL (self->priv->list_store);
+
+       if (!gtk_tree_model_get_iter_first (model, &iter))
+               return;
+
+       do {
+               ComponentData *cd = NULL;
+
+               gtk_tree_model_get (model, &iter,
+                       COLUMN_COMPONENT_DATA, &cd,
+                       -1);
+
+               if (cd) {
+                       GdkRGBA bgcolor, fgcolor;
+                       gboolean bgcolor_set = FALSE, fgcolor_set = FALSE;
+
+                       year_view_get_comp_colors (self, cd->client, cd->comp, &bgcolor, &bgcolor_set, 
&fgcolor, &fgcolor_set);
+
+                       gtk_list_store_set (self->priv->list_store, &iter,
+                               COLUMN_BGCOLOR, bgcolor_set ? &bgcolor : NULL,
+                               COLUMN_FGCOLOR, fgcolor_set ? &fgcolor : NULL,
+                               -1);
+               }
+       } while (gtk_tree_model_iter_next (model, &iter));
+}
+
+static void
+year_view_source_changed_cb (ESourceRegistry *registry,
+                            ESource *source,
+                            gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       if (g_hash_table_contains (self->priv->client_colors, source)) {
+               ESourceSelectable *selectable = NULL;
+
+               if (e_source_has_extension (source, E_SOURCE_EXTENSION_CALENDAR))
+                       selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR);
+               else if (e_source_has_extension (source, E_SOURCE_EXTENSION_TASK_LIST))
+                       selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_TASK_LIST);
+
+               if (selectable) {
+                       GdkRGBA rgba;
+                       gchar *color_spec;
+
+                       color_spec = e_source_selectable_dup_color (selectable);
+                       if (color_spec && gdk_rgba_parse (&rgba, color_spec)) {
+                               GdkRGBA *current_rgba;
+
+                               current_rgba = g_hash_table_lookup (self->priv->client_colors, source);
+                               if (!gdk_rgba_equal (current_rgba, &rgba)) {
+                                       g_hash_table_insert (self->priv->client_colors, source, gdk_rgba_copy 
(&rgba));
+                                       year_view_update_colors (self);
+                               }
+                       }
+
+                       g_free (color_spec);
+               }
+       }
+}
+
+static void
+year_view_source_removed_cb (ESourceRegistry *registry,
+                            ESource *source,
+                            gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       g_hash_table_remove (self->priv->client_colors, source);
+}
+
+static guint
+year_view_get_current_day_of_year (EYearView *self)
+{
+       GDate dt;
+
+       g_date_clear (&dt, 1);
+       g_date_set_dmy (&dt, self->priv->current_day, self->priv->current_month, self->priv->current_year);
+
+       return g_date_get_day_of_year (&dt);
+}
+
+static void
+year_view_add_to_view (EYearView *self,
+                      ComponentData *cd)
+{
+       ICalTime *itt;
+       gboolean is_italic;
+       guint day_of_year;
+       guint ii;
+
+       day_of_year = year_view_get_current_day_of_year (self);
+       is_italic = e_cal_component_get_transparency (cd->comp) == E_CAL_COMPONENT_TRANSP_TRANSPARENT;
+       itt = i_cal_time_new_from_day_of_year (cd->day_from, self->priv->current_year);
+
+       for (ii = cd->day_from; ii <= cd->day_to; ii++) {
+               gchar *tooltip;
+               guint month, day;
+
+               g_return_if_fail (ii < sizeof (self->priv->days));
+
+               month = i_cal_time_get_month (itt);
+               day = i_cal_time_get_day (itt);
+
+               self->priv->days[ii].comps_data = g_slist_prepend (self->priv->days[ii].comps_data, cd);
+               self->priv->days[ii].n_total++;
+               e_month_widget_add_day_css_class (self->priv->months[month - 1], day, 
E_MONTH_WIDGET_CSS_CLASS_UNDERLINE);
+               if (is_italic) {
+                       e_month_widget_add_day_css_class (self->priv->months[month - 1], day, 
E_MONTH_WIDGET_CSS_CLASS_ITALIC);
+                       self->priv->days[ii].n_italic++;
+               } else {
+                       e_month_widget_add_day_css_class (self->priv->months[month - 1], day, 
E_MONTH_WIDGET_CSS_CLASS_BOLD);
+               }
+
+               tooltip = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%u event", "%u events", 
self->priv->days[ii].n_total), self->priv->days[ii].n_total);
+
+               e_month_widget_set_day_tooltip_markup (self->priv->months[month - 1], day, tooltip);
+
+               g_free (tooltip);
+
+               if (ii == day_of_year)
+                       year_view_add_to_list_store (self, cd);
+
+               i_cal_time_adjust (itt, 1, 0, 0, 0);
+       }
+
+       g_clear_object (&itt);
+}
+
+static void
+year_view_remove_from_view (EYearView *self,
+                           ComponentData *cd)
+{
+       ICalTime *itt;
+       gboolean is_italic;
+       guint day_of_year;
+       guint ii;
+
+       day_of_year = year_view_get_current_day_of_year (self);
+       is_italic = e_cal_component_get_transparency (cd->comp) == E_CAL_COMPONENT_TRANSP_TRANSPARENT;
+       itt = i_cal_time_new_from_day_of_year (cd->day_from, self->priv->current_year);
+
+       for (ii = cd->day_from; ii <= cd->day_to; ii++) {
+               gchar *tooltip;
+               guint month, day;
+
+               g_return_if_fail (ii < sizeof (self->priv->days));
+
+               month = i_cal_time_get_month (itt);
+               day = i_cal_time_get_day (itt);
+
+               self->priv->days[ii].comps_data = g_slist_remove (self->priv->days[ii].comps_data, cd);
+               self->priv->days[ii].n_total--;
+               e_month_widget_remove_day_css_class (self->priv->months[month - 1], day, 
E_MONTH_WIDGET_CSS_CLASS_UNDERLINE);
+               if (is_italic) {
+                       self->priv->days[ii].n_italic--;
+                       if (!self->priv->days[ii].n_italic)
+                               e_month_widget_remove_day_css_class (self->priv->months[month - 1], day, 
E_MONTH_WIDGET_CSS_CLASS_ITALIC);
+               } else if (!self->priv->days[ii].n_total) {
+                       e_month_widget_remove_day_css_class (self->priv->months[month - 1], day, 
E_MONTH_WIDGET_CSS_CLASS_BOLD);
+               }
+
+               if (self->priv->days[ii].n_total > 0)
+                       tooltip = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "%u event", "%u events", 
self->priv->days[ii].n_total), self->priv->days[ii].n_total);
+               else
+                       tooltip = NULL;
+
+               e_month_widget_set_day_tooltip_markup (self->priv->months[month - 1], day, tooltip);
+
+               g_free (tooltip);
+
+               if (ii == day_of_year) {
+                       GtkTreeIter iter;
+                       GtkTreeModel *model = GTK_TREE_MODEL (self->priv->list_store);
+
+                       if (gtk_tree_model_get_iter_first (model, &iter)) {
+                               do {
+                                       ComponentData *comp_data = NULL;
+
+                                       gtk_tree_model_get (model, &iter,
+                                               COLUMN_COMPONENT_DATA, &comp_data,
+                                               -1);
+
+                                       if (comp_data == cd) {
+                                               gtk_list_store_remove (self->priv->list_store, &iter);
+                                               break;
+                                       }
+                               } while (gtk_tree_model_iter_next (model, &iter));
+                       }
+               }
+
+               i_cal_time_adjust (itt, 1, 0, 0, 0);
+       }
+
+       g_clear_object (&itt);
+}
+
+static void
+year_view_add_component (EYearView *self,
+                        ECalClient *client,
+                        ECalComponent *comp)
+{
+       ECalComponentId *id;
+       ComponentData *cd, tmp_cd = { 0, };
+       guint day_from = 0, day_to = 0, date_mark = 0, time_mark = 0;
+
+       g_return_if_fail (E_IS_CAL_CLIENT (client));
+       g_return_if_fail (E_IS_CAL_COMPONENT (comp));
+
+       id = e_cal_component_get_id (comp);
+       g_return_if_fail (id != NULL);
+
+       tmp_cd.client = client;
+       tmp_cd.comp = comp;
+       tmp_cd.uid = (gchar *) e_cal_component_id_get_uid (id);
+       tmp_cd.rid = (gchar *) e_cal_component_id_get_rid (id);
+
+       year_view_calc_component_data (self, &tmp_cd, &day_from, &day_to, &date_mark, &time_mark);
+
+       cd = g_hash_table_lookup (self->priv->comps, &tmp_cd);
+
+       e_cal_component_id_free (id);
+
+       /* The component was modified */
+       if (cd) {
+               if (day_from != cd->day_from || day_to != cd->day_to ||
+                   e_cal_component_get_transparency (comp) != e_cal_component_get_transparency (cd->comp)) {
+                       year_view_remove_from_view (self, cd);
+                       g_hash_table_remove (self->priv->comps, cd);
+                       cd = NULL;
+               } else {
+                       g_object_ref (comp);
+                       g_clear_object (&cd->comp);
+                       cd->comp = comp;
+               }
+       }
+
+       if (cd) {
+               guint day_of_year = year_view_get_current_day_of_year (self);
+
+               /* Updat the list view */
+               if (day_of_year >= day_from && day_of_year <= day_to) {
+                       GtkTreeModel *model;
+                       GtkTreeIter iter;
+
+                       model = GTK_TREE_MODEL (self->priv->list_store);
+
+                       if (gtk_tree_model_get_iter_first (model, &iter)) {
+                               ICalTimezone *default_zone = e_cal_data_model_get_timezone 
(self->priv->data_model);
+                               guint flags = year_view_get_describe_flags (self);
+
+                               do {
+                                       ComponentData *comp_data = NULL;
+
+                                       gtk_tree_model_get (model, &iter,
+                                               COLUMN_COMPONENT_DATA, &comp_data,
+                                               -1);
+
+                                       if (comp_data == cd) {
+                                               gchar *summary;
+                                               gchar *tooltip;
+
+                                               summary = cal_comp_util_describe (cd->comp, cd->client, 
default_zone, flags);
+                                               tooltip = cal_comp_util_dup_tooltip (cd->comp, cd->client, 
self->priv->registry, default_zone);
+
+                                               gtk_list_store_set (self->priv->list_store, &iter,
+                                                       COLUMN_SUMMARY, summary,
+                                                       COLUMN_TOOLTIP, tooltip,
+                                                       -1);
+
+                                               g_free (summary);
+                                               g_free (tooltip);
+
+                                               break;
+                                       }
+                               } while (gtk_tree_model_iter_next (model, &iter));
+                       }
+               }
+       } else {
+               cd = component_data_new (client, comp);
+               cd->day_from = day_from;
+               cd->day_to = day_to;
+               cd->date_mark = date_mark;
+               cd->time_mark = time_mark;
+
+               g_hash_table_insert (self->priv->comps, cd, cd);
+               year_view_add_to_view (self, cd);
+       }
+}
+
+static void
+year_view_data_subscriber_component_added (ECalDataModelSubscriber *subscriber,
+                                          ECalClient *client,
+                                          ECalComponent *comp)
+{
+       g_return_if_fail (E_IS_YEAR_VIEW (subscriber));
+
+       year_view_add_component (E_YEAR_VIEW (subscriber), client, comp);
+}
+
+static void
+year_view_data_subscriber_component_modified (ECalDataModelSubscriber *subscriber,
+                                             ECalClient *client,
+                                             ECalComponent *comp)
+{
+       g_return_if_fail (E_IS_YEAR_VIEW (subscriber));
+
+       year_view_add_component (E_YEAR_VIEW (subscriber), client, comp);
+}
+
+static void
+year_view_data_subscriber_component_removed (ECalDataModelSubscriber *subscriber,
+                                            ECalClient *client,
+                                            const gchar *uid,
+                                            const gchar *rid)
+{
+       EYearView *self;
+       ComponentData *cd, tmp_cd = { 0, };
+
+       g_return_if_fail (E_IS_YEAR_VIEW (subscriber));
+
+       self = E_YEAR_VIEW (subscriber);
+
+       if (self->priv->clearing_comps)
+               return;
+
+       tmp_cd.client = client;
+       tmp_cd.uid = (gchar *) uid;
+       tmp_cd.rid = (gchar *) (rid && *rid ? rid : NULL);
+
+       cd = g_hash_table_lookup (self->priv->comps, &tmp_cd);
+
+       if (cd) {
+               year_view_remove_from_view (self, cd);
+               g_hash_table_remove (self->priv->comps, cd);
+       }
+}
+
+static void
+year_view_data_subscriber_freeze (ECalDataModelSubscriber *subscriber)
+{
+       g_return_if_fail (E_IS_YEAR_VIEW (subscriber));
+}
+
+static void
+year_view_data_subscriber_thaw (ECalDataModelSubscriber *subscriber)
+{
+       g_return_if_fail (E_IS_YEAR_VIEW (subscriber));
+}
+
+static void
+year_view_selection_changed_cb (GtkTreeSelection *in_selection, /* can be NULL */
+                               gpointer user_data)
+{
+       EYearView *self = user_data;
+       GtkTreeSelection *selection;
+
+       if (!self->priv->preview_visible) {
+               g_signal_emit_by_name (self, "selection-changed");
+               return;
+       }
+
+       selection = gtk_tree_view_get_selection (self->priv->tree_view);
+
+       if (gtk_tree_selection_count_selected_rows (selection) == 1) {
+               GList *selected;
+               GtkTreeModel *model = NULL;
+               GtkTreeIter iter;
+
+               selected = gtk_tree_selection_get_selected_rows (selection, &model);
+
+               if (selected &&
+                   gtk_tree_model_get_iter (model, &iter, selected->data)) {
+                       ComponentData *cd = NULL;
+
+                       gtk_tree_model_get (model, &iter,
+                               COLUMN_COMPONENT_DATA, &cd,
+                               -1);
+
+                       e_cal_component_preview_display (self->priv->preview,
+                               cd->client, cd->comp, e_cal_data_model_get_timezone (self->priv->data_model),
+                               self->priv->use_24hour_format);
+               } else {
+                       e_cal_component_preview_clear (self->priv->preview);
+               }
+
+               g_list_free_full (selected, (GDestroyNotify) gtk_tree_path_free);
+       } else {
+               e_cal_component_preview_clear (self->priv->preview);
+       }
+
+       g_signal_emit_by_name (self, "selection-changed");
+}
+
+static void
+year_view_tree_view_popup_menu (EYearView *self,
+                               GdkEvent *button_event)
+{
+       e_calendar_view_popup_event (E_CALENDAR_VIEW (self), button_event);
+}
+
+static gboolean
+year_view_tree_view_popup_menu_cb (GtkWidget *tree_view,
+                                  gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       year_view_tree_view_popup_menu (self, NULL);
+
+       return TRUE;
+}
+
+static gboolean
+year_view_tree_view_button_press_event_cb (GtkWidget *widget,
+                                          GdkEvent *event,
+                                          gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       if (event->type == GDK_BUTTON_PRESS &&
+           gdk_event_triggers_context_menu (event)) {
+               GtkTreeSelection *selection;
+               GtkTreePath *path;
+
+               selection = gtk_tree_view_get_selection (self->priv->tree_view);
+               if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_SINGLE)
+                       gtk_tree_selection_unselect_all (selection);
+
+               if (gtk_tree_view_get_path_at_pos (self->priv->tree_view, event->button.x, event->button.y, 
&path, NULL, NULL, NULL)) {
+                       gtk_tree_selection_select_path (selection, path);
+                       gtk_tree_view_set_cursor (self->priv->tree_view, path, NULL, FALSE);
+
+                       gtk_tree_path_free (path);
+               }
+
+               year_view_tree_view_popup_menu (self, event);
+
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static void
+year_view_tree_view_row_activated_cb (GtkTreeView *tree_view,
+                                     GtkTreePath *path,
+                                     GtkTreeViewColumn *column,
+                                     gpointer user_data)
+{
+       GtkTreeModel *model;
+       GtkTreeIter iter;
+
+       model = gtk_tree_view_get_model (tree_view);
+
+       if (gtk_tree_model_get_iter (model, &iter, path)) {
+               ComponentData *cd = NULL;
+
+               gtk_tree_model_get (model, &iter,
+                       COLUMN_COMPONENT_DATA, &cd,
+                       -1);
+
+               if (cd) {
+                       e_cal_ops_open_component_in_editor_sync (NULL, cd->client,
+                               e_cal_component_get_icalcomponent (cd->comp), FALSE);
+               }
+       }
+}
+
+static void
+year_view_timezone_changed_cb (GObject *object,
+                              GParamSpec *param,
+                              gpointer user_data)
+{
+       EYearView *self = user_data;
+
+       self->priv->current_year--;
+
+       /* This updates everything */
+       year_view_set_year (self, self->priv->current_year + 1, 0, 0);
+}
+
+static GtkWidget *
+year_view_construct_year_widget (EYearView *self)
+{
+       GtkWidget *widget, *top_container, *container, *hbox;
+       GtkStyleContext *style_context;
+       GtkStyleProvider *style_provider;
+       ECalModel *model;
+       GSettings *settings;
+       GDate *date;
+       gint ii;
+
+       style_provider = GTK_STYLE_PROVIDER (self->priv->css_provider);
+
+       model = e_calendar_view_get_model (E_CALENDAR_VIEW (self));
+       settings = e_util_ref_settings ("org.gnome.evolution.calendar");
+
+       widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+
+       g_object_set (G_OBJECT (widget),
+               "hexpand", TRUE,
+               "halign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               NULL);
+
+       gtk_style_context_add_class (gtk_widget_get_style_context (widget), GTK_STYLE_CLASS_VIEW);
+
+       top_container = widget;
+       container = widget;
+
+       widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+
+       g_object_set (G_OBJECT (widget),
+               "hexpand", FALSE,
+               "halign", GTK_ALIGN_CENTER,
+               "vexpand", FALSE,
+               "valign", GTK_ALIGN_START,
+               "margin-top", 12,
+               "margin-bottom", 6,
+               NULL);
+
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+
+       hbox = widget;
+
+       #ifdef WITH_PREV_NEXT_BUTTONS
+       widget = gtk_button_new_from_icon_name ("go-previous-symbolic", GTK_ICON_SIZE_BUTTON);
+
+       g_object_set (G_OBJECT (widget),
+               "valign", GTK_ALIGN_BASELINE,
+               NULL);
+
+       gtk_style_context_add_class (gtk_widget_get_style_context (widget), GTK_STYLE_CLASS_FLAT);
+
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       g_signal_connect (widget, "clicked", G_CALLBACK (year_view_prev_year_clicked_cb), self);
+       #endif
+
+       widget = gtk_button_new ();
+       self->priv->prev_year_button2 = GTK_BUTTON (widget);
+
+       g_object_set (G_OBJECT (widget),
+               "valign", GTK_ALIGN_BASELINE,
+               NULL);
+
+       style_context = gtk_widget_get_style_context (widget);
+       gtk_style_context_add_provider (style_context, style_provider, 
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_FLAT);
+       gtk_style_context_add_class (style_context, "prev-year");
+
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       g_signal_connect (widget, "clicked", G_CALLBACK (year_view_prev_year2_clicked_cb), self);
+
+       widget = gtk_button_new ();
+       self->priv->prev_year_button1 = GTK_BUTTON (widget);
+
+       g_object_set (G_OBJECT (widget),
+               "valign", GTK_ALIGN_BASELINE,
+               NULL);
+
+       style_context = gtk_widget_get_style_context (widget);
+       gtk_style_context_add_provider (style_context, style_provider, 
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_FLAT);
+       gtk_style_context_add_class (style_context, "prev-year");
+
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       g_signal_connect (widget, "clicked", G_CALLBACK (year_view_prev_year_clicked_cb), self);
+
+       widget = gtk_label_new ("");
+       self->priv->current_year_label = GTK_LABEL (widget);
+
+       g_object_set (G_OBJECT (widget),
+               "valign", GTK_ALIGN_BASELINE,
+               NULL);
+
+       style_context = gtk_widget_get_style_context (widget);
+       gtk_style_context_add_provider (style_context, style_provider, 
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_style_context_add_class (style_context, "current-year");
+
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       widget = gtk_button_new ();
+       self->priv->next_year_button1 = GTK_BUTTON (widget);
+
+       g_object_set (G_OBJECT (widget),
+               "valign", GTK_ALIGN_BASELINE,
+               NULL);
+
+       style_context = gtk_widget_get_style_context (widget);
+       gtk_style_context_add_provider (style_context, style_provider, 
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_FLAT);
+       gtk_style_context_add_class (style_context, "next-year");
+
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       g_signal_connect (widget, "clicked", G_CALLBACK (year_view_next_year_clicked_cb), self);
+
+       widget = gtk_button_new ();
+       self->priv->next_year_button2 = GTK_BUTTON (widget);
+
+       g_object_set (G_OBJECT (widget),
+               "valign", GTK_ALIGN_BASELINE,
+               NULL);
+
+       style_context = gtk_widget_get_style_context (widget);
+       gtk_style_context_add_provider (style_context, style_provider, 
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_FLAT);
+       gtk_style_context_add_class (style_context, "next-year");
+
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       g_signal_connect (widget, "clicked", G_CALLBACK (year_view_next_year2_clicked_cb), self);
+
+       #ifdef WITH_PREV_NEXT_BUTTONS
+       widget = gtk_button_new_from_icon_name ("go-next-symbolic", GTK_ICON_SIZE_BUTTON);
+
+       g_object_set (G_OBJECT (widget),
+               "valign", GTK_ALIGN_BASELINE,
+               NULL);
+
+       gtk_style_context_add_class (gtk_widget_get_style_context (widget), GTK_STYLE_CLASS_FLAT);
+
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       g_signal_connect (widget, "clicked", G_CALLBACK (year_view_next_year_clicked_cb), self);
+       #endif
+
+       widget = gtk_scrolled_window_new (NULL, NULL);
+
+       g_object_set (G_OBJECT (widget),
+               "hexpand", TRUE,
+               "halign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               "min-content-width", 50,
+               "min-content-height", 50,
+               NULL);
+
+       style_context = gtk_widget_get_style_context (widget);
+       gtk_style_context_add_provider (style_context, style_provider, 
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_FLAT);
+       gtk_style_context_add_class (style_context, "calendar-window");
+
+       gtk_container_add (GTK_CONTAINER (top_container), widget);
+
+       container = widget;
+
+       widget = gtk_flow_box_new ();
+
+       g_object_set (G_OBJECT (widget),
+               "hexpand", TRUE,
+               "halign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "column-spacing", 12,
+               "row-spacing", 12,
+               "homogeneous", TRUE,
+               "min-children-per-line", 1,
+               "max-children-per-line", 6,
+               "selection-mode", GTK_SELECTION_NONE,
+               NULL);
+
+       style_context = gtk_widget_get_style_context (widget);
+       gtk_style_context_add_provider (style_context, style_provider, 
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_VIEW);
+       gtk_style_context_add_class (style_context, "calendar-flowbox");
+
+       gtk_container_add (GTK_CONTAINER (container), widget);
+       container = widget;
+
+       /* The date is used only for the month name */
+       date = g_date_new_dmy (1, 1, self->priv->current_year);
+
+       for (ii = 0; ii < 12; ii++) {
+               GtkFlowBoxChild *child;
+               GtkWidget *vbox;
+               gchar buffer[128];
+
+               g_date_strftime (buffer, sizeof (buffer), "%B", date);
+               g_date_add_months (date, 1);
+
+               vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+
+               widget = gtk_label_new (buffer);
+
+               g_object_set (G_OBJECT (widget),
+                       "halign", GTK_ALIGN_CENTER,
+                       "valign", GTK_ALIGN_CENTER,
+                       "xalign", 0.5,
+                       "yalign", 0.5,
+                       NULL);
+
+               gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+               widget = e_month_widget_new ();
+
+               g_object_set (G_OBJECT (widget),
+                       "halign", GTK_ALIGN_CENTER,
+                       "valign", GTK_ALIGN_CENTER,
+                       NULL);
+
+               gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+               self->priv->months[ii] = E_MONTH_WIDGET (widget);
+
+               g_signal_connect (widget, "day-clicked",
+                       G_CALLBACK (year_view_month_widget_day_clicked_cb), self);
+
+               e_binding_bind_property (model, "week-start-day", widget, "week-start-day", 
G_BINDING_SYNC_CREATE);
+               g_settings_bind (settings, "show-week-numbers", widget, "show-week-numbers", 
G_SETTINGS_BIND_GET);
+               g_settings_bind (settings, "year-show-day-names", widget, "show-day-names", 
G_SETTINGS_BIND_GET);
+
+               e_month_widget_set_month (E_MONTH_WIDGET (widget), ii + 1, self->priv->current_year);
+
+               gtk_container_add (GTK_CONTAINER (container), vbox);
+
+               child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (container), ii);
+
+               g_object_set (G_OBJECT (child),
+                       "halign", GTK_ALIGN_CENTER,
+                       "valign", GTK_ALIGN_START,
+                       NULL);
+       }
+
+       g_clear_object (&settings);
+       g_date_free (date);
+
+       return top_container;
+}
+
+static void
+year_view_set_property (GObject *object,
+                       guint property_id,
+                       const GValue *value,
+                       GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_PREVIEW_VISIBLE:
+                       e_year_view_set_preview_visible (
+                               E_YEAR_VIEW (object),
+                               g_value_get_boolean (value));
+                       return;
+
+               case PROP_USE_24HOUR_FORMAT:
+                       e_year_view_set_use_24hour_format (
+                               E_YEAR_VIEW (object),
+                               g_value_get_boolean (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+year_view_get_property (GObject *object,
+                       guint property_id,
+                       GValue *value,
+                       GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_IS_EDITING:
+                       g_value_set_boolean (value, FALSE);
+                       return;
+
+               case PROP_PREVIEW_VISIBLE:
+                       g_value_set_boolean (value,
+                               e_year_view_get_preview_visible (E_YEAR_VIEW (object)));
+                       return;
+
+               case PROP_USE_24HOUR_FORMAT:
+                       g_value_set_boolean (value,
+                               e_year_view_get_use_24hour_format (E_YEAR_VIEW (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+year_view_constructed (GObject *object)
+{
+       EYearView *self = E_YEAR_VIEW (object);
+       ECalModel *model;
+       GSettings *settings;
+       GtkWidget *widget;
+       GtkTreeViewColumn *column;
+       GtkTreeSelection *selection;
+       GtkCellRenderer *renderer;
+       GError *error = NULL;
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_year_view_parent_class)->constructed (object);
+
+       self->priv->registry = e_source_registry_new_sync (NULL, &error);
+
+       if (self->priv->registry) {
+               g_signal_connect_object (self->priv->registry, "source-changed",
+                       G_CALLBACK (year_view_source_changed_cb), self, 0);
+               g_signal_connect_object (self->priv->registry, "source-disabled",
+                       G_CALLBACK (year_view_source_removed_cb), self, 0);
+               g_signal_connect_object (self->priv->registry, "source-removed",
+                       G_CALLBACK (year_view_source_removed_cb), self, 0);
+       } else {
+               g_warning ("%s: Failed to create source registry: %s", G_STRFUNC, error ? error->message : 
"Unknown error");
+               g_clear_error (&error);
+       }
+
+       self->priv->css_provider = gtk_css_provider_new ();
+
+       if (!gtk_css_provider_load_from_data (self->priv->css_provider,
+               "EYearView .prev-year {"
+               "   font-size:90%;"
+               "}"
+               "EYearView .current-year {"
+               "   font-size:120%;"
+               "   font-weight:bold;"
+               "}"
+               "EYearView .next-year {"
+               "   font-size:90%;"
+               "}"
+               "EYearView .calendar-window {"
+               "   border-top: 1px solid @theme_bg_color;"
+               "}"
+               "EYearView .calendar-flowbox {"
+               "   padding-top: 12px;"
+               "   padding-bottom: 12px;"
+               "}",
+               -1, &error)) {
+               g_warning ("%s: Failed to parse CSS: %s", G_STRFUNC, error ? error->message : "Unknown 
error");
+               g_clear_error (&error);
+       }
+
+       model = e_calendar_view_get_model (E_CALENDAR_VIEW (self));
+       self->priv->data_model = g_object_ref (e_cal_model_get_data_model (model));
+
+       self->priv->preview_paned = e_paned_new (GTK_ORIENTATION_HORIZONTAL);
+
+       g_object_set (G_OBJECT (self->priv->preview_paned),
+               "halign", GTK_ALIGN_FILL,
+               "valign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "vexpand", TRUE,
+               NULL);
+
+       gtk_grid_attach (GTK_GRID (self), self->priv->preview_paned, 0, 0, 1, 1);
+
+       self->priv->hpaned = e_paned_new (GTK_ORIENTATION_HORIZONTAL);
+
+       g_object_set (G_OBJECT (self->priv->hpaned),
+               "halign", GTK_ALIGN_FILL,
+               "valign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "vexpand", TRUE,
+               NULL);
+
+       gtk_paned_pack1 (GTK_PANED (self->priv->preview_paned), self->priv->hpaned, TRUE, FALSE);
+
+       self->priv->preview = E_CAL_COMPONENT_PREVIEW (e_cal_component_preview_new ());
+       g_object_set (G_OBJECT (self->priv->preview),
+               "width-request", 50,
+               "height-request", 50,
+               NULL);
+       gtk_paned_pack2 (GTK_PANED (self->priv->preview_paned), GTK_WIDGET (self->priv->preview), FALSE, 
FALSE);
+
+       widget = gtk_scrolled_window_new (NULL, NULL);
+       g_object_set (G_OBJECT (widget),
+               "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               "min-content-width", 50,
+               "min-content-height", 50,
+               NULL);
+       gtk_paned_pack1 (GTK_PANED (self->priv->hpaned), widget, TRUE, FALSE);
+
+       gtk_container_add (GTK_CONTAINER (widget), year_view_construct_year_widget (self));
+
+       widget = gtk_scrolled_window_new (NULL, NULL);
+       g_object_set (G_OBJECT (widget),
+               "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               "min-content-width", 50,
+               "min-content-height", 50,
+               NULL);
+
+       gtk_paned_pack2 (GTK_PANED (self->priv->hpaned), widget, FALSE, FALSE);
+
+       self->priv->list_store = gtk_list_store_new (N_COLUMNS,
+               GDK_TYPE_RGBA,          /* COLUMN_BGCOLOR */
+               GDK_TYPE_RGBA,          /* COLUMN_FGCOLOR */
+               G_TYPE_BOOLEAN,         /* COLUMN_HAS_ICON_NAME */
+               G_TYPE_STRING,          /* COLUMN_ICON_NAME */
+               G_TYPE_STRING,          /* COLUMN_SUMMARY */
+               G_TYPE_STRING,          /* COLUMN_TOOLTIP */
+               G_TYPE_STRING,          /* COLUMN_SORTKEY */
+               G_TYPE_POINTER);        /* COLUMN_COMPONENT_DATA */
+
+       gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self->priv->list_store), COLUMN_SORTKEY, 
GTK_SORT_ASCENDING);
+
+       self->priv->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ());
+
+       g_object_set (G_OBJECT (self->priv->tree_view),
+               "halign", GTK_ALIGN_FILL,
+               "valign", GTK_ALIGN_FILL,
+               "hexpand", TRUE,
+               "vexpand", TRUE,
+               "fixed-height-mode", TRUE,
+               "headers-clickable", FALSE,
+               "headers-visible", TRUE,
+               "reorderable", FALSE,
+               "search-column", COLUMN_SUMMARY,
+               "tooltip-column", COLUMN_TOOLTIP,
+               "enable-grid-lines", GTK_TREE_VIEW_GRID_LINES_HORIZONTAL,
+               "model", self->priv->list_store,
+               NULL);
+
+       gtk_container_add (GTK_CONTAINER (widget), GTK_WIDGET (self->priv->tree_view));
+
+       column = gtk_tree_view_column_new ();
+
+       g_object_set (G_OBJECT (column),
+               "expand", TRUE,
+               "clickable", FALSE,
+               "resizable", FALSE,
+               "reorderable", FALSE,
+               "sizing", GTK_TREE_VIEW_COLUMN_FIXED,
+               "alignment", 0.5f,
+               NULL);
+
+       renderer = gtk_cell_renderer_pixbuf_new ();
+
+       gtk_tree_view_column_pack_start (column, renderer, FALSE);
+
+       gtk_tree_view_column_set_attributes (column, renderer,
+               "cell-background-rgba", COLUMN_BGCOLOR,
+               "icon-name", COLUMN_ICON_NAME,
+               "visible", COLUMN_HAS_ICON_NAME,
+               NULL);
+
+       renderer = gtk_cell_renderer_text_new ();
+
+       g_object_set (G_OBJECT (renderer),
+               "ellipsize", PANGO_ELLIPSIZE_END,
+               NULL);
+
+       gtk_tree_view_column_pack_start (column, renderer, TRUE);
+
+       gtk_tree_view_column_set_attributes (column, renderer,
+               "markup", COLUMN_SUMMARY,
+               "background-rgba", COLUMN_BGCOLOR,
+               "foreground-rgba", COLUMN_FGCOLOR,
+               NULL);
+
+       gtk_tree_view_append_column (self->priv->tree_view, column);
+
+       selection = gtk_tree_view_get_selection (self->priv->tree_view);
+
+       g_signal_connect_object (selection, "changed",
+               G_CALLBACK (year_view_selection_changed_cb), self, 0);
+
+       g_signal_connect_object (self->priv->tree_view, "popup-menu",
+               G_CALLBACK (year_view_tree_view_popup_menu_cb), self, 0);
+
+       g_signal_connect_object (self->priv->tree_view, "button-press-event",
+               G_CALLBACK (year_view_tree_view_button_press_event_cb), self, 0);
+
+       g_signal_connect_object (self->priv->tree_view, "row-activated",
+               G_CALLBACK (year_view_tree_view_row_activated_cb), self, 0);
+
+       g_signal_connect_object (self->priv->data_model, "notify::timezone",
+               G_CALLBACK (year_view_timezone_changed_cb), self, 0);
+
+       gtk_widget_show_all (self->priv->preview_paned);
+
+       settings = e_util_ref_settings ("org.gnome.evolution.calendar");
+
+       g_settings_bind (
+               settings, "year-hpane-position",
+               self->priv->hpaned, "hposition",
+               G_SETTINGS_BIND_DEFAULT);
+
+       g_settings_bind (
+               settings, "use-24hour-format",
+               self, "use-24hour-format",
+               G_SETTINGS_BIND_GET);
+
+       if (e_year_view_get_preview_orientation (self) == GTK_ORIENTATION_HORIZONTAL) {
+               g_settings_bind (
+                       settings, "year-hpreview-position",
+                       self->priv->preview_paned, "hposition",
+                       G_SETTINGS_BIND_DEFAULT);
+       } else {
+               g_settings_bind (
+                       settings, "year-vpreview-position",
+                       self->priv->preview_paned, "vposition",
+                       G_SETTINGS_BIND_DEFAULT);
+       }
+
+       g_object_unref (settings);
+
+       /* To update the top year buttons */
+       self->priv->current_year--;
+       year_view_set_year (self, self->priv->current_year + 1, 0, 0);
+}
+
+static void
+year_view_dispose (GObject *object)
+{
+       EYearView *self = E_YEAR_VIEW (object);
+
+       if (self->priv->data_model) {
+               self->priv->clearing_comps = TRUE;
+               year_view_clear_comps (self);
+               e_cal_data_model_unsubscribe (self->priv->data_model, E_CAL_DATA_MODEL_SUBSCRIBER (self));
+               self->priv->clearing_comps = FALSE;
+       }
+
+       g_clear_object (&self->priv->registry);
+       g_clear_object (&self->priv->list_store);
+       g_clear_object (&self->priv->data_model);
+       g_clear_object (&self->priv->css_provider);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_year_view_parent_class)->dispose (object);
+}
+
+static void
+year_view_finalize (GObject *object)
+{
+       EYearView *self = E_YEAR_VIEW (object);
+
+       year_view_clear_comps (self);
+
+       g_hash_table_destroy (self->priv->client_colors);
+       g_hash_table_destroy (self->priv->comps);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_year_view_parent_class)->finalize (object);
+}
+
+static void
+e_year_view_class_init (EYearViewClass *klass)
+{
+       GObjectClass *object_class;
+       ECalendarViewClass *view_class;
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->set_property = year_view_set_property;
+       object_class->get_property = year_view_get_property;
+       object_class->constructed = year_view_constructed;
+       object_class->dispose = year_view_dispose;
+       object_class->finalize = year_view_finalize;
+
+       gtk_widget_class_set_css_name (GTK_WIDGET_CLASS (klass), "EYearView");
+
+       view_class = E_CALENDAR_VIEW_CLASS (klass);
+       view_class->get_selected_events = year_view_get_selected_events;
+       view_class->get_selected_time_range = year_view_get_selected_time_range;
+       view_class->set_selected_time_range = year_view_set_selected_time_range;
+       view_class->get_visible_time_range = year_view_get_visible_time_range;
+       view_class->precalc_visible_time_range = year_view_precalc_visible_time_range;
+       view_class->paste_text = year_view_paste_text;
+
+       g_object_class_override_property (
+               object_class,
+               PROP_IS_EDITING,
+               "is-editing");
+
+       obj_props[PROP_PREVIEW_VISIBLE] =
+               g_param_spec_boolean ("preview-visible", NULL, NULL,
+                       TRUE,
+                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+       obj_props[PROP_USE_24HOUR_FORMAT] =
+               g_param_spec_boolean ("use-24hour-format", NULL, NULL,
+                       FALSE,
+                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+       g_object_class_install_properties (object_class, LAST_PROP, obj_props);
+}
+
+static void
+e_year_view_init (EYearView *self)
+{
+       self->priv = e_year_view_get_instance_private (self);
+       self->priv->preview_visible = TRUE;
+       self->priv->current_day = 1;
+       self->priv->current_month = 1;
+       self->priv->current_year = 2000;
+
+       self->priv->client_colors = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+               NULL, (GDestroyNotify) gdk_rgba_free);
+
+       self->priv->comps = g_hash_table_new_full (component_data_hash, component_data_equal,
+               component_data_free, NULL);
+}
+
+static void
+year_view_cal_data_model_subscriber_init (ECalDataModelSubscriberInterface *iface)
+{
+       iface->component_added = year_view_data_subscriber_component_added;
+       iface->component_modified = year_view_data_subscriber_component_modified;
+       iface->component_removed = year_view_data_subscriber_component_removed;
+       iface->freeze = year_view_data_subscriber_freeze;
+       iface->thaw = year_view_data_subscriber_thaw;
+}
+
+ECalendarView *
+e_year_view_new (ECalModel *model)
+{
+       g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL);
+
+       return g_object_new (E_TYPE_YEAR_VIEW, "model", model, NULL);
+}
+
+void
+e_year_view_set_preview_visible (EYearView *self,
+                                gboolean value)
+{
+       g_return_if_fail (E_IS_YEAR_VIEW (self));
+
+       if ((self->priv->preview_visible ? 1 : 0) == (value ? 1 : 0))
+               return;
+
+       self->priv->preview_visible = value;
+
+       gtk_widget_set_visible (GTK_WIDGET (self->priv->preview), self->priv->preview_visible);
+
+       if (self->priv->preview_visible)
+               year_view_selection_changed_cb (NULL, self);
+       else
+               e_cal_component_preview_clear (self->priv->preview);
+
+       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_PREVIEW_VISIBLE]);
+}
+
+gboolean
+e_year_view_get_preview_visible (EYearView *self)
+{
+       g_return_val_if_fail (E_IS_YEAR_VIEW (self), FALSE);
+
+       return self->priv->preview_visible;
+}
+
+void
+e_year_view_set_preview_orientation (EYearView *self,
+                                    GtkOrientation value)
+{
+       GSettings *settings;
+
+       g_return_if_fail (E_IS_YEAR_VIEW (self));
+
+       if (gtk_orientable_get_orientation (GTK_ORIENTABLE (self->priv->preview_paned)) == value)
+               return;
+
+       g_settings_unbind (self->priv->preview_paned, "hposition");
+       g_settings_unbind (self->priv->preview_paned, "vposition");
+
+       gtk_orientable_set_orientation (GTK_ORIENTABLE (self->priv->preview_paned), value);
+
+       settings = e_util_ref_settings ("org.gnome.evolution.calendar");
+
+       if (value == GTK_ORIENTATION_HORIZONTAL) {
+               g_settings_bind (
+                       settings, "year-hpreview-position",
+                       self->priv->preview_paned, "hposition",
+                       G_SETTINGS_BIND_DEFAULT);
+       } else {
+               g_settings_bind (
+                       settings, "year-vpreview-position",
+                       self->priv->preview_paned, "vposition",
+                       G_SETTINGS_BIND_DEFAULT);
+       }
+
+       g_clear_object (&settings);
+}
+
+GtkOrientation
+e_year_view_get_preview_orientation (EYearView *self)
+{
+       g_return_val_if_fail (E_IS_YEAR_VIEW (self), GTK_ORIENTATION_HORIZONTAL);
+
+       return gtk_orientable_get_orientation (GTK_ORIENTABLE (self->priv->preview_paned));
+}
+
+void
+e_year_view_set_use_24hour_format (EYearView *self,
+                                  gboolean value)
+{
+       GtkTreeIter iter;
+       GtkTreeModel *model;
+
+       g_return_if_fail (E_IS_YEAR_VIEW (self));
+
+       if ((self->priv->use_24hour_format ? 1 : 0) == (value ? 1 : 0))
+               return;
+
+       self->priv->use_24hour_format = value;
+
+       model = GTK_TREE_MODEL (self->priv->list_store);
+
+       if (gtk_tree_model_get_iter_first (model, &iter)) {
+               ICalTimezone *default_zone = e_cal_data_model_get_timezone (self->priv->data_model);
+               guint flags = year_view_get_describe_flags (self);
+
+               do {
+                       ComponentData *cd = NULL;
+
+                       gtk_tree_model_get (model, &iter,
+                               COLUMN_COMPONENT_DATA, &cd,
+                               -1);
+
+                       if (cd) {
+                               gchar *summary;
+
+                               summary = cal_comp_util_describe (cd->comp, cd->client, default_zone, flags);
+
+                               gtk_list_store_set (self->priv->list_store, &iter,
+                                       COLUMN_SUMMARY, summary,
+                                       -1);
+
+                               g_free (summary);
+                       }
+               } while (gtk_tree_model_iter_next (model, &iter));
+       }
+
+       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_USE_24HOUR_FORMAT]);
+}
+
+gboolean
+e_year_view_get_use_24hour_format (EYearView *self)
+{
+       g_return_val_if_fail (E_IS_YEAR_VIEW (self), FALSE);
+
+       return self->priv->use_24hour_format;
+}
diff --git a/src/calendar/gui/e-year-view.h b/src/calendar/gui/e-year-view.h
new file mode 100644
index 0000000000..896def3010
--- /dev/null
+++ b/src/calendar/gui/e-year-view.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_YEAR_VIEW_H
+#define E_YEAR_VIEW_H
+
+#include <calendar/gui/e-calendar-view.h>
+
+/* Standard GObject macros */
+#define E_TYPE_YEAR_VIEW \
+       (e_year_view_get_type ())
+#define E_YEAR_VIEW(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_YEAR_VIEW, EYearView))
+#define E_YEAR_VIEW_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_YEAR_VIEW, EYearViewClass))
+#define E_IS_YEAR_VIEW(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_YEAR_VIEW))
+#define E_IS_YEAR_VIEW_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_YEAR_VIEW))
+#define E_YEAR_VIEW_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_YEAR_VIEW, EYearViewClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EYearView EYearView;
+typedef struct _EYearViewClass EYearViewClass;
+typedef struct _EYearViewPrivate EYearViewPrivate;
+
+struct _EYearView {
+       ECalendarView parent;
+       EYearViewPrivate *priv;
+};
+
+struct _EYearViewClass {
+       ECalendarViewClass parent_class;
+};
+
+GType          e_year_view_get_type                    (void) G_GNUC_CONST;
+ECalendarView *        e_year_view_new                         (ECalModel *model);
+void           e_year_view_set_preview_visible         (EYearView *self,
+                                                        gboolean value);
+gboolean       e_year_view_get_preview_visible         (EYearView *self);
+void           e_year_view_set_preview_orientation     (EYearView *self,
+                                                        GtkOrientation value);
+GtkOrientation e_year_view_get_preview_orientation     (EYearView *self);
+void           e_year_view_set_use_24hour_format       (EYearView *self,
+                                                        gboolean value);
+gboolean       e_year_view_get_use_24hour_format       (EYearView *self);
+
+G_END_DECLS
+
+#endif /* E_YEAR_VIEW_H */
diff --git a/src/e-util/CMakeLists.txt b/src/e-util/CMakeLists.txt
index cc8c06c2b0..0c940f326f 100644
--- a/src/e-util/CMakeLists.txt
+++ b/src/e-util/CMakeLists.txt
@@ -175,6 +175,7 @@ set(SOURCES
        e-menu-tool-button.c
        e-misc-utils.c
        e-mktemp.c
+       e-month-widget.c
        e-name-selector-dialog.c
        e-name-selector-entry.c
        e-name-selector-list.c
@@ -451,6 +452,7 @@ set(HEADERS
        e-menu-tool-button.h
        e-misc-utils.h
        e-mktemp.h
+       e-month-widget.h
        e-name-selector-dialog.h
        e-name-selector-entry.h
        e-name-selector-list.h
@@ -823,6 +825,7 @@ add_private_programs_simple(
        test-html-editor
        test-mail-signatures
        test-markdown-editor
+       test-month-widget
        test-name-selector
        test-preferences-window
        test-proxy-preferences
diff --git a/src/e-util/e-misc-utils.c b/src/e-util/e-misc-utils.c
index f5b7d76758..5530756770 100644
--- a/src/e-util/e-misc-utils.c
+++ b/src/e-util/e-misc-utils.c
@@ -4571,6 +4571,33 @@ e_util_markup_append_escaped (GString *buffer,
        g_free (escaped);
 }
 
+/**
+ * e_util_markup_append_escaped_text:
+ * @buffer: a #GString buffer to append escaped text to
+ * @text: a text to escape and append to the buffer
+ *
+ * Markup-escapes @text and appends it to @buffer.
+ *
+ * Since: 3.46
+ **/
+void
+e_util_markup_append_escaped_text (GString *buffer,
+                                  const gchar *text)
+{
+       gchar *escaped;
+
+       g_return_if_fail (buffer != NULL);
+
+       if (!text || !*text)
+               return;
+
+       escaped = g_markup_escape_text (text, -1);
+
+       g_string_append (buffer, escaped);
+
+       g_free (escaped);
+}
+
 void
 e_util_enum_supported_locales (void)
 {
diff --git a/src/e-util/e-misc-utils.h b/src/e-util/e-misc-utils.h
index 85f73e8fa4..430934ad2d 100644
--- a/src/e-util/e-misc-utils.h
+++ b/src/e-util/e-misc-utils.h
@@ -330,6 +330,9 @@ gboolean    e_util_can_preview_filename     (const gchar *filename);
 void           e_util_markup_append_escaped    (GString *buffer,
                                                 const gchar *format,
                                                 ...) G_GNUC_PRINTF (2, 3);
+void           e_util_markup_append_escaped_text
+                                               (GString *buffer,
+                                                const gchar *text);
 
 typedef struct _ESupportedLocales {
        const gchar *code;      /* like 'en' */
diff --git a/src/e-util/e-month-widget.c b/src/e-util/e-month-widget.c
new file mode 100644
index 0000000000..75fc584670
--- /dev/null
+++ b/src/e-util/e-month-widget.c
@@ -0,0 +1,1290 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-config.h"
+
+#include <gtk/gtk.h>
+
+#include "e-month-widget.h"
+
+#define MAX_WEEKS 6
+
+#define CSS_CLASS_SELECTED "emw-selected"
+
+struct _EMonthWidgetPrivate {
+       GtkCssProvider *css_provider;
+       GtkGrid *grid;
+       GDateMonth month;
+       guint year;
+       GDateWeekday week_start_day;
+       gboolean show_week_numbers;
+       gboolean show_day_names;
+
+       gboolean calculating_min_day_size;
+       gint min_day_size; /* used for a square size */
+       guint button_press_day;
+};
+
+/* A "day label", whose minimum size is always square */
+
+#define E_TYPE_MONTH_WIDGET_DAY_LABEL (e_month_widget_day_label_get_type ())
+
+G_DECLARE_FINAL_TYPE (EMonthWidgetDayLabel, e_month_widget_day_label, E, MONTH_WIDGET_DAY_LABEL, GtkLabel)
+
+struct _EMonthWidgetDayLabel
+{
+       GtkLabel parent_instance;
+
+       EMonthWidget *month_widget;
+       guint day;
+};
+
+G_DEFINE_TYPE (EMonthWidgetDayLabel, e_month_widget_day_label, GTK_TYPE_LABEL)
+
+static GtkSizeRequestMode
+e_month_widget_day_label_get_request_mode (GtkWidget *widget)
+{
+       EMonthWidgetDayLabel *self = E_MONTH_WIDGET_DAY_LABEL (widget);
+
+       if (self->month_widget->priv->calculating_min_day_size)
+               return GTK_WIDGET_CLASS (e_month_widget_day_label_parent_class)->get_request_mode (widget);
+
+       return GTK_SIZE_REQUEST_CONSTANT_SIZE;
+}
+
+static void
+e_month_widget_day_label_get_preferred_height (GtkWidget *widget,
+                                              gint *minimum_height,
+                                              gint *natural_height)
+{
+       EMonthWidgetDayLabel *self = E_MONTH_WIDGET_DAY_LABEL (widget);
+
+       if (self->month_widget->priv->calculating_min_day_size) {
+               GTK_WIDGET_CLASS (e_month_widget_day_label_parent_class)->get_preferred_height (widget, 
minimum_height, natural_height);
+               return;
+       }
+
+       if (minimum_height)
+               *minimum_height = self->month_widget->priv->min_day_size;
+
+       if (natural_height)
+               *natural_height = self->month_widget->priv->min_day_size;
+}
+
+static void
+e_month_widget_day_label_get_preferred_width (GtkWidget *widget,
+                                             gint *minimum_width,
+                                             gint *natural_width)
+{
+       EMonthWidgetDayLabel *self = E_MONTH_WIDGET_DAY_LABEL (widget);
+
+       if (self->month_widget->priv->calculating_min_day_size) {
+               GTK_WIDGET_CLASS (e_month_widget_day_label_parent_class)->get_preferred_width (widget, 
minimum_width, natural_width);
+               return;
+       }
+
+       if (minimum_width)
+               *minimum_width = self->month_widget->priv->min_day_size;
+
+       if (natural_width)
+               *natural_width = self->month_widget->priv->min_day_size;
+}
+
+static void
+e_month_widget_day_label_class_init (EMonthWidgetDayLabelClass *klass)
+{
+       GtkWidgetClass *widget_class;
+
+       widget_class = GTK_WIDGET_CLASS (klass);
+       widget_class->get_request_mode = e_month_widget_day_label_get_request_mode;
+       widget_class->get_preferred_height = e_month_widget_day_label_get_preferred_height;
+       widget_class->get_preferred_width = e_month_widget_day_label_get_preferred_width;
+}
+
+static void
+e_month_widget_day_label_init (EMonthWidgetDayLabel *self)
+{
+}
+
+G_DEFINE_TYPE_WITH_PRIVATE (EMonthWidget, e_month_widget, GTK_TYPE_EVENT_BOX)
+
+enum {
+       PROP_0,
+       PROP_WEEK_START_DAY,
+       PROP_SHOW_WEEK_NUMBERS,
+       PROP_SHOW_DAY_NAMES,
+       LAST_PROP
+};
+
+enum {
+       CHANGED,
+       DAY_CLICKED,
+       LAST_SIGNAL
+};
+
+static GParamSpec *obj_props[LAST_PROP] = { NULL, };
+static guint signals[LAST_SIGNAL];
+
+static const gchar *
+get_digit_format (void)
+{
+#ifdef HAVE_GNU_GET_LIBC_VERSION
+#include <gnu/libc-version.h>
+
+       const gchar *libc_version = gnu_get_libc_version ();
+       gchar **split = g_strsplit (libc_version, ".", -1);
+       gint major = 0;
+       gint minor = 0;
+       gint revision = 0;
+
+       major = atoi (split[0]);
+       minor = atoi (split[1]);
+
+       if (g_strv_length (split) > 2)
+               revision = atoi (split[2]);
+       g_strfreev (split);
+
+       if (major > 2 || minor > 2 || (minor == 2 && revision > 2)) {
+               digit_fomat = "%Id";
+               return digit_fomat;
+       }
+#endif
+
+       return "%d";
+}
+
+static void
+e_month_widget_update (EMonthWidget *self)
+{
+       static const gchar *digit_format = NULL;
+       GDate *date, tmp_date;
+       GtkWidget *widget;
+       gchar buffer[128];
+       guint week_of_year, week_of_last_year = 0;
+       guint ii, jj, month_day, max_month_days;
+
+       if (!digit_format)
+               digit_format = get_digit_format ();
+
+       date = g_date_new_dmy (1, self->priv->month, self->priv->year);
+
+       if (self->priv->week_start_day == G_DATE_SUNDAY) {
+               week_of_year = g_date_get_sunday_week_of_year (date);
+               if (!week_of_year)
+                       week_of_last_year = g_date_get_sunday_weeks_in_year (self->priv->year - 1);
+       } else {
+               week_of_year = g_date_get_monday_week_of_year (date);
+               if (!week_of_year)
+                       week_of_last_year = g_date_get_monday_weeks_in_year (self->priv->year - 1);
+       }
+
+       /* Update week numbers */
+       for (ii = 0; ii < MAX_WEEKS; ii++) {
+               g_snprintf (buffer, sizeof (buffer), digit_format, !week_of_year ? week_of_last_year : 
week_of_year);
+
+               widget = gtk_grid_get_child_at (self->priv->grid, 0, ii + 1);
+               gtk_label_set_text (GTK_LABEL (widget), buffer);
+
+               week_of_year++;
+       }
+
+       /* Update day names */
+       tmp_date = *date;
+       if (g_date_get_weekday (&tmp_date) > self->priv->week_start_day) {
+               g_date_subtract_days (&tmp_date, g_date_get_weekday (&tmp_date) - self->priv->week_start_day);
+       } else if (g_date_get_weekday (&tmp_date) < self->priv->week_start_day) {
+               g_date_subtract_days (&tmp_date, 7 - (self->priv->week_start_day - g_date_get_weekday 
(&tmp_date)));
+       }
+
+       for (ii = 0; ii < 7; ii++) {
+               g_warn_if_fail (g_date_strftime (buffer, sizeof (buffer), "%a", &tmp_date));
+               g_date_add_days (&tmp_date, 1);
+
+               widget = gtk_grid_get_child_at (self->priv->grid, ii + 1, 0);
+               gtk_label_set_text (GTK_LABEL (widget), buffer);
+       }
+
+       g_date_subtract_days (&tmp_date, 7);
+
+       /* Update days and weeks */
+       month_day = 1;
+       max_month_days = g_date_get_days_in_month (self->priv->month, self->priv->year);
+
+       for (jj = 0; jj < MAX_WEEKS; jj++) {
+               for (ii = 0; ii < 7; ii++) {
+                       EMonthWidgetDayLabel *day_label;
+
+                       widget = gtk_grid_get_child_at (self->priv->grid, ii + 1, jj + 1);
+                       day_label = E_MONTH_WIDGET_DAY_LABEL (widget);
+
+                       if (jj == 0 && g_date_compare (&tmp_date, date) < 0) {
+                               g_date_add_days (&tmp_date, 1);
+                               gtk_widget_set_visible (widget, FALSE);
+                               day_label->day = 0;
+                       } else if (month_day <= max_month_days) {
+                               g_snprintf (buffer, sizeof (buffer), digit_format, month_day);
+                               gtk_label_set_text (GTK_LABEL (widget), buffer);
+                               gtk_widget_set_visible (widget, TRUE);
+                               day_label->day = month_day;
+                               month_day++;
+
+                               if (ii == 0 && self->priv->show_week_numbers) {
+                                       /* Show the week number */
+                                       widget = gtk_grid_get_child_at (self->priv->grid, 0, jj + 1);
+                                       gtk_widget_set_visible (widget, TRUE);
+                               }
+                       } else {
+                               gtk_widget_set_visible (widget, FALSE);
+                               day_label->day = 0;
+
+                               if (ii == 0 && self->priv->show_week_numbers) {
+                                       /* Hide the week number */
+                                       widget = gtk_grid_get_child_at (self->priv->grid, 0, jj + 1);
+                                       gtk_widget_set_visible (widget, FALSE);
+                               }
+                       }
+               }
+       }
+
+       g_date_free (date);
+}
+
+static GtkWidget *
+e_month_widget_get_day_widget (EMonthWidget *self,
+                              guint day)
+{
+       GtkWidget *widget;
+       guint row, col, first_day;
+
+       if (!day || day > g_date_get_days_in_month (self->priv->month, self->priv->year))
+               return NULL;
+
+       for (first_day = 0; first_day < 7; first_day++) {
+               widget = gtk_grid_get_child_at (self->priv->grid, first_day + 1, 1);
+               if (gtk_widget_get_visible (widget))
+                       break;
+       }
+
+       day--;
+
+       row = day / 7;
+       col = day % 7;
+
+       if (col + first_day >= 7)
+               row++;
+
+       col = (col + first_day) % 7;
+
+       widget = gtk_grid_get_child_at (self->priv->grid, col + 1, row + 1);
+       g_warn_if_fail (gtk_widget_get_visible (widget));
+
+       return widget;
+}
+
+static void
+e_month_widget_style_updated (GtkWidget *widget)
+{
+       static const gchar *digit_format = NULL;
+       EMonthWidget *self = E_MONTH_WIDGET (widget);
+       GtkWidget *label_widget;
+       GtkLabel *label;
+       GDate *date;
+       gchar buffer[128];
+       gchar *previous_value;
+       gboolean previous_visible;
+       gint max_day_name_width = 0;
+       gint max_week_num_height = 0;
+       gint max_day_num_width = 0;
+       gint max_day_num_height = 0;
+       gint value;
+       guint ii;
+
+       if (!digit_format)
+               digit_format = get_digit_format ();
+
+       self->priv->calculating_min_day_size = TRUE;
+
+       /* It does not matter what date it is, as it's used to get day names only */
+       date = g_date_new_dmy (1, 1, 2000);
+
+       /* Day name */
+       label_widget = gtk_grid_get_child_at (self->priv->grid, 1, 0);
+       label = GTK_LABEL (label_widget);
+       previous_value = g_strdup (gtk_label_get_text (label));
+       previous_visible = gtk_widget_get_visible (label_widget);
+       gtk_widget_set_visible (label_widget, TRUE);
+
+       for (ii = 0; ii < 7; ii++) {
+               g_warn_if_fail (g_date_strftime (buffer, sizeof (buffer), "%a", date));
+               g_date_add_days (date, 1);
+
+               gtk_label_set_text (label, buffer);
+
+               gtk_widget_get_preferred_width (label_widget, &value, NULL);
+               if (value > max_day_name_width)
+                       max_day_name_width = value;
+       }
+
+       gtk_widget_set_visible (label_widget, previous_visible);
+       gtk_label_set_text (label, previous_value);
+       g_free (previous_value);
+       g_date_free (date);
+
+       /* Week number */
+       label_widget = gtk_grid_get_child_at (self->priv->grid, 0, 1);
+       label = GTK_LABEL (label_widget);
+       previous_value = g_strdup (gtk_label_get_text (label));
+       previous_visible = gtk_widget_get_visible (label_widget);
+       gtk_widget_set_visible (label_widget, TRUE);
+
+       for (ii = 1; ii < 54; ii++) {
+               g_snprintf (buffer, sizeof (buffer), digit_format, ii);
+
+               gtk_label_set_text (label, buffer);
+
+               gtk_widget_get_preferred_height (label_widget, &value, NULL);
+               if (value > max_week_num_height)
+                       max_week_num_height = value;
+       }
+
+       gtk_widget_set_visible (label_widget, previous_visible);
+       gtk_label_set_text (label, previous_value);
+       g_free (previous_value);
+
+       /* Day number */
+       label_widget = gtk_grid_get_child_at (self->priv->grid, 1, 1);
+       label = GTK_LABEL (label_widget);
+       previous_value = g_strdup (gtk_label_get_text (label));
+       previous_visible = gtk_widget_get_visible (label_widget);
+       gtk_widget_set_visible (label_widget, TRUE);
+
+       for (ii = 1; ii < 32; ii++) {
+               g_snprintf (buffer, sizeof (buffer), digit_format, ii);
+
+               gtk_label_set_text (label, buffer);
+
+               gtk_widget_get_preferred_width (label_widget, &value, NULL);
+               if (value > max_day_num_width)
+                       max_day_num_width = value;
+
+               gtk_widget_get_preferred_height (label_widget, &value, NULL);
+               if (value > max_day_num_height)
+                       max_day_num_height = value;
+       }
+
+       gtk_widget_set_visible (label_widget, previous_visible);
+       gtk_label_set_text (label, previous_value);
+       g_free (previous_value);
+
+       self->priv->calculating_min_day_size = FALSE;
+
+       value = MAX (max_day_num_width, MAX (max_day_num_height, MAX (max_day_name_width, 
max_week_num_height)));
+
+       /* Padding 2 pixels on each side */
+       value += 4;
+
+       if (value != self->priv->min_day_size) {
+               self->priv->min_day_size = value;
+               gtk_widget_queue_resize (widget);
+       }
+}
+
+static void
+e_month_widget_show_all (GtkWidget *widget)
+{
+       EMonthWidget *self = E_MONTH_WIDGET (widget);
+
+       gtk_widget_show (widget);
+
+       if (self->priv->grid)
+               gtk_widget_show (GTK_WIDGET (self->priv->grid));
+}
+
+static gboolean
+e_month_widget_button_press_event_cb (GtkWidget *widget,
+                                     GdkEventButton *event,
+                                     gpointer user_data)
+{
+       EMonthWidget *self = E_MONTH_WIDGET (widget);
+
+       self->priv->button_press_day = event->type == GDK_BUTTON_PRESS ?
+               e_month_widget_get_day_at_position (self, event->x, event->y) : 0;
+
+       return FALSE;
+}
+
+static gboolean
+e_month_widget_button_release_event_cb (GtkWidget *widget,
+                                       GdkEventButton *event,
+                                       gpointer user_data)
+{
+       EMonthWidget *self = E_MONTH_WIDGET (widget);
+       guint day;
+
+       day = event->type == GDK_BUTTON_RELEASE ?
+               e_month_widget_get_day_at_position (self, event->x, event->y) : 0;
+
+       if (day && self->priv->button_press_day == day) {
+               g_signal_emit (self, signals[DAY_CLICKED], 0, event, self->priv->year, self->priv->month, 
day, NULL);
+       }
+
+       self->priv->button_press_day = 0;
+
+       return FALSE;
+}
+
+static void
+e_month_widget_set_property (GObject *object,
+                            guint property_id,
+                            const GValue *value,
+                            GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_WEEK_START_DAY:
+                       e_month_widget_set_week_start_day (
+                               E_MONTH_WIDGET (object),
+                               g_value_get_int (value));
+                       return;
+
+               case PROP_SHOW_WEEK_NUMBERS:
+                       e_month_widget_set_show_week_numbers (
+                               E_MONTH_WIDGET (object),
+                               g_value_get_boolean (value));
+                       return;
+
+               case PROP_SHOW_DAY_NAMES:
+                       e_month_widget_set_show_day_names (
+                               E_MONTH_WIDGET (object),
+                               g_value_get_boolean (value));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_month_widget_get_property (GObject *object,
+                            guint property_id,
+                            GValue *value,
+                            GParamSpec *pspec)
+{
+       switch (property_id) {
+               case PROP_WEEK_START_DAY:
+                       g_value_set_int (
+                               value, e_month_widget_get_week_start_day (
+                               E_MONTH_WIDGET (object)));
+                       return;
+
+               case PROP_SHOW_WEEK_NUMBERS:
+                       g_value_set_boolean (
+                               value, e_month_widget_get_show_week_numbers (
+                               E_MONTH_WIDGET (object)));
+                       return;
+
+               case PROP_SHOW_DAY_NAMES:
+                       g_value_set_boolean (
+                               value, e_month_widget_get_show_day_names (
+                               E_MONTH_WIDGET (object)));
+                       return;
+       }
+
+       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+e_month_widget_constructed (GObject *object)
+{
+       EMonthWidget *self = E_MONTH_WIDGET (object);
+       PangoAttrList *attrs_small, *attrs_tnum, *attrs_small_tnum;
+       GtkStyleProvider *style_provider;
+       GtkStyleContext *style_context;
+       guint ii, jj;
+       GError *error = NULL;
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_month_widget_parent_class)->constructed (object);
+
+       g_object_set (object,
+               "above-child", TRUE,
+               "visible-window", TRUE,
+               NULL);
+
+       self->priv->grid = GTK_GRID (gtk_grid_new ());
+
+       g_object_set (G_OBJECT (self->priv->grid),
+               "column-homogeneous", FALSE,
+               "column-spacing", 0,
+               "row-homogeneous", FALSE,
+               "row-spacing", 0,
+               "visible", TRUE,
+               NULL);
+
+       gtk_container_add (GTK_CONTAINER (self), GTK_WIDGET (self->priv->grid));
+
+       self->priv->css_provider = gtk_css_provider_new ();
+
+       if (!gtk_css_provider_load_from_data (self->priv->css_provider,
+               "EMonthWidget ." CSS_CLASS_SELECTED " {"
+               "   background-color:@theme_selected_bg_color;"
+               "   color:@theme_selected_fg_color;"
+               "   border-radius:4px;"
+               "   border-width:1px;"
+               "   border-color:darker(@theme_selected_bg_color);"
+               "   border-style:solid;"
+               "}"
+               "EMonthWidget .emw-day {"
+               "   padding:1px;"
+               "}"
+               "EMonthWidget ." E_MONTH_WIDGET_CSS_CLASS_BOLD " {"
+               "   font-weight:bold;"
+               "}"
+               "EMonthWidget ." E_MONTH_WIDGET_CSS_CLASS_ITALIC " {"
+               "   font-style:italic;"
+               "}"
+               "EMonthWidget ." E_MONTH_WIDGET_CSS_CLASS_UNDERLINE " {"
+               "   text-decoration:underline;"
+               "}"
+               "EMonthWidget ." E_MONTH_WIDGET_CSS_CLASS_HIGHLIGHT " {"
+               "   border-radius:4px;"
+               "   border-width:2px;"
+               "   border-color:darker(@theme_selected_bg_color);"
+               "   border-style:solid;"
+               "}",
+               -1, &error)) {
+               g_warning ("%s: Failed to parse CSS: %s", G_STRFUNC, error ? error->message : "Unknown 
error");
+               g_clear_error (&error);
+       }
+
+       style_provider = GTK_STYLE_PROVIDER (self->priv->css_provider);
+
+       style_context = gtk_widget_get_style_context (GTK_WIDGET (self->priv->grid));
+       gtk_style_context_add_provider (style_context, style_provider, 
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+       gtk_style_context_add_class (style_context, GTK_STYLE_CLASS_VIEW);
+
+       attrs_small = pango_attr_list_new ();
+       pango_attr_list_insert (attrs_small, pango_attr_scale_new (PANGO_SCALE_SMALL));
+
+       attrs_tnum = pango_attr_list_new ();
+       pango_attr_list_insert_before (attrs_tnum, pango_attr_font_features_new ("tnum=1"));
+
+       attrs_small_tnum = pango_attr_list_new ();
+       pango_attr_list_insert (attrs_small_tnum, pango_attr_scale_new (PANGO_SCALE_SMALL));
+       pango_attr_list_insert_before (attrs_small_tnum, pango_attr_font_features_new ("tnum=1"));
+
+       for (jj = 0; jj < MAX_WEEKS + 1; jj++) {
+               for (ii = 0; ii < 7 + 1; ii++) {
+                       GtkWidget *widget;
+                       PangoAttrList *attrs;
+
+                       if (!ii && !jj)
+                               continue;
+
+                       attrs = pango_attr_list_new ();
+
+                       if (ii == 0)
+                               attrs = attrs_small_tnum;
+                       else if (jj == 0)
+                               attrs = attrs_small;
+                       else
+                               attrs = attrs_tnum;
+
+                       if (ii != 0 && jj != 0) {
+                               EMonthWidgetDayLabel *day_label;
+
+                               day_label = g_object_new (E_TYPE_MONTH_WIDGET_DAY_LABEL, NULL);
+                               day_label->month_widget = self;
+
+                               widget = GTK_WIDGET (day_label);
+                       } else {
+                               widget = gtk_label_new ("");
+                       }
+
+                       g_object_set (G_OBJECT (widget),
+                               "halign", GTK_ALIGN_FILL,
+                               "valign", GTK_ALIGN_FILL,
+                               "hexpand", ii != 0 && jj != 0,
+                               "vexpand", ii != 0 && jj != 0,
+                               "xalign", 0.5,
+                               "yalign", 0.5,
+                               "attributes", attrs,
+                               "visible", FALSE,
+                               "sensitive", ii != 0 && jj != 0,
+                               NULL);
+
+                       style_context = gtk_widget_get_style_context (widget);
+                       gtk_style_context_add_provider (style_context, style_provider, 
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+                       if (ii == 0)
+                               gtk_style_context_add_class (style_context, "week-number");
+                       else if (jj == 0)
+                               gtk_style_context_add_class (style_context, "day-name");
+                       else
+                               gtk_style_context_add_class (style_context, "day-number");
+
+                       gtk_grid_attach (self->priv->grid, widget, ii, jj, 1, 1);
+               }
+       }
+
+       e_month_widget_update (self);
+
+       pango_attr_list_unref (attrs_small);
+       pango_attr_list_unref (attrs_tnum);
+       pango_attr_list_unref (attrs_small_tnum);
+
+       g_signal_connect (self, "button-press-event",
+               G_CALLBACK (e_month_widget_button_press_event_cb), NULL);
+
+       g_signal_connect (self, "button-release-event",
+               G_CALLBACK (e_month_widget_button_release_event_cb), NULL);
+}
+
+static void
+e_month_widget_finalize (GObject *object)
+{
+       EMonthWidget *self = E_MONTH_WIDGET (object);
+
+       g_clear_object (&self->priv->css_provider);
+
+       /* Chain up to parent's method. */
+       G_OBJECT_CLASS (e_month_widget_parent_class)->finalize (object);
+}
+
+static void
+e_month_widget_class_init (EMonthWidgetClass *klass)
+{
+       GObjectClass *object_class;
+       GtkWidgetClass *widget_class;
+
+       widget_class = GTK_WIDGET_CLASS (klass);
+       widget_class->style_updated = e_month_widget_style_updated;
+       widget_class->show_all = e_month_widget_show_all;
+
+       gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_CALENDAR);
+       gtk_widget_class_set_css_name (widget_class, "EMonthWidget");
+
+       object_class = G_OBJECT_CLASS (klass);
+       object_class->get_property = e_month_widget_get_property;
+       object_class->set_property = e_month_widget_set_property;
+       object_class->constructed = e_month_widget_constructed;
+       object_class->finalize = e_month_widget_finalize;
+
+       /**
+        * EMonthWidget:week-start-day:
+        *
+        * A day the week starts with.
+        *
+        * Since: 3.46
+        **/
+       obj_props[PROP_WEEK_START_DAY] =
+               g_param_spec_int ("week-start-day", NULL, NULL,
+                       0, 7, G_DATE_SUNDAY,
+                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+       /**
+        * EMonthWidget:show-week-numbers:
+        *
+        * Whether to show week numbers.
+        *
+        * Since: 3.46
+        **/
+       obj_props[PROP_SHOW_WEEK_NUMBERS] =
+               g_param_spec_boolean ("show-week-numbers", NULL, NULL,
+                       FALSE,
+                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+       /**
+        * EMonthWidget:show-day-names:
+        *
+        * Whether to show day names.
+        *
+        * Since: 3.46
+        **/
+       obj_props[PROP_SHOW_DAY_NAMES] =
+               g_param_spec_boolean ("show-day-names", NULL, NULL,
+                       FALSE,
+                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
+       g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props);
+
+       /**
+        * EMonthWidget::changed:
+        * @self: an #EMonthWidget, which sent the signal
+        *
+        * This signal is emitted when the shown date (month or year) changes.
+        *
+        * Since: 3.46
+        **/
+       signals[CHANGED] = g_signal_new (
+               "changed",
+               G_TYPE_FROM_CLASS (klass),
+               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+               G_STRUCT_OFFSET (EMonthWidgetClass, changed),
+               NULL, NULL, NULL,
+               G_TYPE_NONE, 0,
+               G_TYPE_NONE);
+
+       /**
+        * EMonthWidget::day-clicked:
+        * @self: an #EMonthWidget, which sent the signal
+        * @event: a #GdkButtonEvent causing this signal; it's always a button release event
+        * @year: the year of the clicked day
+        * @month: the month of the clicked day
+        * @day: the day of the clicked day
+        *
+        * This signal is emitted when a day is clicked. It's identified
+        * as a date split into @year, @month and @day.
+        *
+        * Since: 3.46
+        **/
+       signals[DAY_CLICKED] = g_signal_new (
+               "day-clicked",
+               G_TYPE_FROM_CLASS (klass),
+               G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+               G_STRUCT_OFFSET (EMonthWidgetClass, day_clicked),
+               NULL, NULL, NULL,
+               G_TYPE_NONE, 4,
+               GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE,
+               G_TYPE_UINT,
+               G_TYPE_INT,
+               G_TYPE_UINT);
+}
+
+static void
+e_month_widget_init (EMonthWidget *self)
+{
+       self->priv = e_month_widget_get_instance_private (self);
+       self->priv->month = 1;
+       self->priv->year = 2000;
+       self->priv->week_start_day = G_DATE_SUNDAY;
+       self->priv->show_week_numbers = FALSE;
+       self->priv->show_day_names = FALSE;
+}
+
+/**
+ * e_month_widget_new:
+ *
+ * Creates a new #EMonthWidget
+ *
+ * Returns: (transfer full): a new #EMonthWidget
+ *
+ * Since: 3.46
+ **/
+GtkWidget *
+e_month_widget_new (void)
+{
+       return g_object_new (E_TYPE_MONTH_WIDGET, NULL);
+}
+
+/**
+ * e_month_widget_set_month:
+ * @self: an #EMonthWidget
+ * @month: a month to show, as #GDateMonth
+ * @year: a year to show
+ *
+ * Sets the @month of the @year to be shown in the @self.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_set_month (EMonthWidget *self,
+                         GDateMonth month,
+                         guint year)
+{
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       if (self->priv->month == month &&
+           self->priv->year == year)
+               return;
+
+       self->priv->month = month;
+       self->priv->year = year;
+
+       e_month_widget_update (self);
+
+       g_signal_emit (self, signals[CHANGED], 0, NULL);
+}
+
+/**
+ * e_month_widget_get_month:
+ * @self: an #EMonthWidget
+ * @out_month: (out) (optioal): an output location to set the shown month to, as #GDateMonth, or %NULL
+ * @out_year: (out) (optional): an output location to set the shown year to, or %NULL
+ *
+ * Retrieve currently shown month and/or year in the @self.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_get_month (EMonthWidget *self,
+                         GDateMonth *out_month,
+                         guint *out_year)
+{
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       if (out_month)
+               *out_month = self->priv->month;
+       if (out_year)
+               *out_year = self->priv->year;
+}
+
+/**
+ * e_month_widget_set_week_start_day:
+ * @self: an #EMonthWidget
+ * @value: a #GDateWeekday
+ *
+ * Set which day of week the week starts on.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_set_week_start_day (EMonthWidget *self,
+                                  GDateWeekday value)
+{
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+       g_return_if_fail (value != G_DATE_BAD_WEEKDAY);
+
+       if (self->priv->week_start_day == value)
+               return;
+
+       self->priv->week_start_day = value;
+
+       e_month_widget_update (self);
+
+       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_WEEK_START_DAY]);
+}
+
+/**
+ * e_month_widget_get_week_start_day:
+ * @self: an #EMonthWidget
+ *
+ * Returns: which day the week starts with
+ *
+ * Since: 3.46
+ **/
+GDateWeekday
+e_month_widget_get_week_start_day (EMonthWidget *self)
+{
+       g_return_val_if_fail (E_IS_MONTH_WIDGET (self), G_DATE_BAD_WEEKDAY);
+
+       return self->priv->week_start_day;
+}
+
+/**
+ * e_month_widget_set_show_week_numbers:
+ * @self: an #EMonthWidget
+ * @value: whether to show week numbers
+ *
+ * Set whether to show the week numbers.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_set_show_week_numbers (EMonthWidget *self,
+                                     gboolean value)
+{
+       guint ii;
+
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       if ((self->priv->show_week_numbers ? 1 : 0) == (value ? 1 : 0))
+               return;
+
+       self->priv->show_week_numbers = value;
+
+       for (ii = 0; ii < MAX_WEEKS; ii++) {
+               GtkWidget *week_number;
+               gboolean should_show = self->priv->show_week_numbers;
+
+               week_number = gtk_grid_get_child_at (self->priv->grid, 0, ii + 1);
+
+               if (should_show) {
+                       guint jj;
+
+                       for (jj = 0; jj < 7; jj++) {
+                               GtkWidget *day_widget;
+
+                               day_widget = gtk_grid_get_child_at (self->priv->grid, jj + 1, ii + 1);
+
+                               if (gtk_widget_get_visible (day_widget))
+                                       break;
+                       }
+
+                       /* Found a shown day in the week row */
+                       should_show = jj < 7;
+               }
+
+               gtk_widget_set_visible (week_number, should_show);
+       }
+
+       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SHOW_WEEK_NUMBERS]);
+}
+
+/**
+ * e_month_widget_get_show_week_numbers:
+ * @self: an #EMonthWidget
+ *
+ * Returns: whether week numbers are shown
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_month_widget_get_show_week_numbers (EMonthWidget *self)
+{
+       g_return_val_if_fail (E_IS_MONTH_WIDGET (self), FALSE);
+
+       return self->priv->show_week_numbers;
+}
+
+/**
+ * e_month_widget_set_show_day_names:
+ * @self: an #EMonthWidget
+ * @value: whether to show day names
+ *
+ * Set whether to show day names above the month days.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_set_show_day_names (EMonthWidget *self,
+                                  gboolean value)
+{
+       guint ii;
+
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       if ((self->priv->show_day_names ? 1 : 0) == (value ? 1 : 0))
+               return;
+
+       self->priv->show_day_names = value;
+
+       for (ii = 0; ii < 7; ii++) {
+               GtkWidget *day_name;
+
+               day_name = gtk_grid_get_child_at (self->priv->grid, ii + 1, 0);
+
+               gtk_widget_set_visible (day_name, self->priv->show_day_names);
+       }
+
+       g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_SHOW_DAY_NAMES]);
+}
+
+/**
+ * e_month_widget_get_show_day_names:
+ * @self: an #EMonthWidget
+ *
+ * Returns: whether day names are shown.
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_month_widget_get_show_day_names (EMonthWidget *self)
+{
+       g_return_val_if_fail (E_IS_MONTH_WIDGET (self), FALSE);
+
+       return self->priv->show_day_names;
+}
+
+/**
+ * e_month_widget_set_day_selected:
+ * @self: an #EMonthWidget
+ * @day: a day of month
+ * @selected: whether to select the day
+ *
+ * Sets the @day as @selected. There can be selected more
+ * than one day.
+ *
+ * Using the @day out of range for the current month and year
+ * leads to no change being done.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_set_day_selected (EMonthWidget *self,
+                                guint day,
+                                gboolean selected)
+{
+       GtkWidget *day_widget;
+
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       day_widget = e_month_widget_get_day_widget (self, day);
+
+       if (day_widget) {
+               GtkStyleContext *style_context;
+
+               style_context = gtk_widget_get_style_context (day_widget);
+
+               if (selected)
+                       gtk_style_context_add_class (style_context, CSS_CLASS_SELECTED);
+               else
+                       gtk_style_context_remove_class (style_context, CSS_CLASS_SELECTED);
+       }
+}
+
+/**
+ * e_month_widget_get_day_selected:
+ * @self: an #EMonthWidget
+ * @day: a day of month
+ *
+ * Returns whether the @day is selected. Using the @day out of range
+ * for the current month and year always returns %FALSE.
+ *
+ * Returns: whether the @day is selected
+ *
+ * Since: 3.46
+ **/
+gboolean
+e_month_widget_get_day_selected (EMonthWidget *self,
+                                guint day)
+{
+       GtkWidget *day_widget;
+
+       g_return_val_if_fail (E_IS_MONTH_WIDGET (self), FALSE);
+
+       day_widget = e_month_widget_get_day_widget (self, day);
+
+       if (day_widget) {
+               GtkStyleContext *style_context;
+
+               style_context = gtk_widget_get_style_context (day_widget);
+
+               return gtk_style_context_has_class (style_context, CSS_CLASS_SELECTED);
+       }
+
+       return FALSE;
+}
+
+/**
+ * e_month_widget_set_day_tooltip_markup:
+ * @self: an #EMonthWidget
+ * @day: a day of month
+ * @tooltip_markup: (nullable): a tooltip to set, or %NULL to unset
+ *
+ * Sets a tooltip @tooltip_markup for the @day. The @tooltip_markup
+ * is expected to be markup.
+ *
+ * The function does nothing when the @day is out of range.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_set_day_tooltip_markup (EMonthWidget *self,
+                                      guint day,
+                                      const gchar *tooltip_markup)
+{
+       GtkWidget *day_widget;
+
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       day_widget = e_month_widget_get_day_widget (self, day);
+
+       if (day_widget)
+               gtk_widget_set_tooltip_markup (day_widget, tooltip_markup);
+}
+
+/**
+ * e_month_widget_get_day_tooltip_markup:
+ * @self: an #EMonthWidget
+ * @day: a day of month
+ *
+ * Returns a tooltip markup for the @day, previously set by e_month_widget_set_day_tooltip_markup(),
+ * or %NULL when none is set.
+ *
+ * The function returns %NULL when the @day is out of range.
+ *
+ * Returns: (transfer none) (nullable): a tooltip markup for the day, or %NULL
+ *
+ * Since: 3.46
+ **/
+const gchar *
+e_month_widget_get_day_tooltip_markup (EMonthWidget *self,
+                                      guint day)
+{
+       GtkWidget *day_widget;
+
+       g_return_val_if_fail (E_IS_MONTH_WIDGET (self), NULL);
+
+       day_widget = e_month_widget_get_day_widget (self, day);
+
+       if (day_widget)
+               return gtk_widget_get_tooltip_markup (day_widget);
+
+       return NULL;
+}
+
+/**
+ * e_month_widget_clear_day_tooltips:
+ * @self: an #EMonthWidget
+ *
+ * Clear tooltips for all days of the month.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_clear_day_tooltips (EMonthWidget *self)
+{
+       gint ii, jj;
+
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       for (ii = 0; ii < 7; ii++) {
+               for (jj = 0; jj < MAX_WEEKS; jj++) {
+                       GtkWidget *widget;
+
+                       widget = gtk_grid_get_child_at (self->priv->grid, ii + 1, jj + 1);
+
+                       gtk_widget_set_tooltip_markup (widget, NULL);
+               }
+       }
+}
+
+/**
+ * e_month_widget_add_day_css_class:
+ * @self: an #EMonthWidget
+ * @day: a day of month
+ * @name: a CSS class name to add
+ *
+ * Add the CSS class @name for the @day.
+ *
+ * The function does nothing when the @day is out of range.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_add_day_css_class (EMonthWidget *self,
+                                 guint day,
+                                 const gchar *name)
+{
+       GtkWidget *day_widget;
+
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       day_widget = e_month_widget_get_day_widget (self, day);
+
+       if (day_widget) {
+               GtkStyleContext *style_context;
+
+               style_context = gtk_widget_get_style_context (day_widget);
+               gtk_style_context_add_class (style_context, name);
+       }
+}
+
+/**
+ * e_month_widget_remove_day_css_class:
+ * @self: an #EMonthWidget
+ * @day: a day of month
+ * @name: a CSS class name to remove
+ *
+ * Add the CSS class @name for the @day.
+ *
+ * The function does nothing when the @day is out of range.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_remove_day_css_class (EMonthWidget *self,
+                                    guint day,
+                                    const gchar *name)
+{
+       GtkWidget *day_widget;
+
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       day_widget = e_month_widget_get_day_widget (self, day);
+
+       if (day_widget) {
+               GtkStyleContext *style_context;
+
+               style_context = gtk_widget_get_style_context (day_widget);
+               gtk_style_context_remove_class (style_context, name);
+       }
+}
+
+/**
+ * e_month_widget_clear_day_css_classes:
+ * @self: an #EMonthWidget
+ *
+ * Clear CSS classes for all days of the month. Those considered are @E_MONTH_WIDGET_CSS_CLASS_BOLD,
+ * @E_MONTH_WIDGET_CSS_CLASS_ITALIC, @E_MONTH_WIDGET_CSS_CLASS_UNDERLINE
+ * and @E_MONTH_WIDGET_CSS_CLASS_HIGHLIGHT. The function also removes
+ * selected state from the days, if set.
+ *
+ * Since: 3.46
+ **/
+void
+e_month_widget_clear_day_css_classes (EMonthWidget *self)
+{
+       gint ii, jj;
+
+       g_return_if_fail (E_IS_MONTH_WIDGET (self));
+
+       for (ii = 0; ii < 7; ii++) {
+               for (jj = 0; jj < MAX_WEEKS; jj++) {
+                       GtkWidget *day_widget;
+                       GtkStyleContext *style_context;
+
+                       day_widget = gtk_grid_get_child_at (self->priv->grid, ii + 1, jj + 1);
+                       style_context = gtk_widget_get_style_context (day_widget);
+
+                       gtk_style_context_remove_class (style_context, E_MONTH_WIDGET_CSS_CLASS_BOLD);
+                       gtk_style_context_remove_class (style_context, E_MONTH_WIDGET_CSS_CLASS_ITALIC);
+                       gtk_style_context_remove_class (style_context, E_MONTH_WIDGET_CSS_CLASS_UNDERLINE);
+                       gtk_style_context_remove_class (style_context, E_MONTH_WIDGET_CSS_CLASS_HIGHLIGHT);
+                       gtk_style_context_remove_class (style_context, CSS_CLASS_SELECTED);
+               }
+       }
+}
+
+/**
+ * e_month_widget_get_day_at_position:
+ * @self: an #EMonthWidget
+ * @x_win: window x coordinate
+ * @y_win: window y coordinate
+ *
+ * Returns the day of month above which the @x_win, @y_win is. The position
+ * is in the @self widget coordinates. A value 0 is returned when the position
+ * doesn't point into any day.
+ *
+ * Returns: the day of month the @x_win, @y_win points to, or 0 if not any day
+ *
+ * Since: 3.46
+ **/
+guint
+e_month_widget_get_day_at_position (EMonthWidget *self,
+                                   gdouble x_win,
+                                   gdouble y_win)
+{
+       GtkAllocation allocation;
+       GtkWidget *day_widget;
+       gint ii, jj;
+
+       g_return_val_if_fail (E_IS_MONTH_WIDGET (self), 0);
+
+       gtk_widget_get_allocation (GTK_WIDGET (self), &allocation);
+
+       if (x_win < 0 || x_win >= allocation.width ||
+           y_win < 0 || y_win >= allocation.height)
+               return 0;
+
+       for (jj = 0; jj < MAX_WEEKS; jj++) {
+               for (ii = 0; ii < 7; ii++) {
+                       day_widget = gtk_grid_get_child_at (self->priv->grid, ii + 1, jj + 1);
+
+                       if (gtk_widget_is_visible (day_widget)) {
+                               gtk_widget_get_allocation (day_widget, &allocation);
+
+                               if (x_win >= allocation.x && x_win < allocation.x + allocation.width &&
+                                   y_win >= allocation.y && y_win < allocation.y + allocation.height) {
+                                       EMonthWidgetDayLabel *day_label = E_MONTH_WIDGET_DAY_LABEL 
(day_widget);
+
+                                       return day_label->day;
+                               }
+                       }
+               }
+       }
+
+       return 0;
+}
diff --git a/src/e-util/e-month-widget.h b/src/e-util/e-month-widget.h
new file mode 100644
index 0000000000..9cecd8312b
--- /dev/null
+++ b/src/e-util/e-month-widget.h
@@ -0,0 +1,106 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#if !defined (__E_UTIL_H_INSIDE__) && !defined (LIBEUTIL_COMPILATION)
+#error "Only <e-util/e-util.h> should be included directly."
+#endif
+
+#ifndef E_MONTH_WIDGET_H
+#define E_MONTH_WIDGET_H
+
+#include <gtk/gtk.h>
+
+/* Standard GObject macros */
+#define E_TYPE_MONTH_WIDGET \
+       (e_month_widget_get_type ())
+#define E_MONTH_WIDGET(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_MONTH_WIDGET, EMonthWidget))
+#define E_MONTH_WIDGET_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_MONTH_WIDGET, EMonthWidgetClass))
+#define E_IS_MONTH_WIDGET(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_MONTH_WIDGET))
+#define E_IS_MONTH_WIDGET_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_MONTH_WIDGET))
+#define E_MONTH_WIDGET_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_MONTH_WIDGET, EMonthWidgetClass))
+
+#define E_MONTH_WIDGET_CSS_CLASS_BOLD "emw-bold"
+#define E_MONTH_WIDGET_CSS_CLASS_ITALIC "emw-italic"
+#define E_MONTH_WIDGET_CSS_CLASS_UNDERLINE "emw-underline"
+#define E_MONTH_WIDGET_CSS_CLASS_HIGHLIGHT "emw-highlight"
+
+G_BEGIN_DECLS
+
+typedef struct _EMonthWidget EMonthWidget;
+typedef struct _EMonthWidgetClass EMonthWidgetClass;
+typedef struct _EMonthWidgetPrivate EMonthWidgetPrivate;
+
+struct _EMonthWidget {
+       GtkEventBox parent;
+       EMonthWidgetPrivate *priv;
+};
+
+struct _EMonthWidgetClass {
+       GtkEventBoxClass parent_class;
+
+       void    (* changed)             (EMonthWidget *self);
+       void    (* day_clicked)         (EMonthWidget *self,
+                                        GdkEventButton *event,
+                                        guint year,
+                                        gint /* GDateMonth */ month,
+                                        guint day);
+
+       /* Padding for future expansion */
+       gpointer padding[12];
+};
+
+GType          e_month_widget_get_type                 (void) G_GNUC_CONST;
+GtkWidget *    e_month_widget_new                      (void);
+void           e_month_widget_set_month                (EMonthWidget *self,
+                                                        GDateMonth month,
+                                                        guint year);
+void           e_month_widget_get_month                (EMonthWidget *self,
+                                                        GDateMonth *out_month,
+                                                        guint *out_year);
+void           e_month_widget_set_week_start_day       (EMonthWidget *self,
+                                                        GDateWeekday value);
+GDateWeekday   e_month_widget_get_week_start_day       (EMonthWidget *self);
+void           e_month_widget_set_show_week_numbers    (EMonthWidget *self,
+                                                        gboolean value);
+gboolean       e_month_widget_get_show_week_numbers    (EMonthWidget *self);
+void           e_month_widget_set_show_day_names       (EMonthWidget *self,
+                                                        gboolean value);
+gboolean       e_month_widget_get_show_day_names       (EMonthWidget *self);
+void           e_month_widget_set_day_selected         (EMonthWidget *self,
+                                                        guint day,
+                                                        gboolean selected);
+gboolean       e_month_widget_get_day_selected         (EMonthWidget *self,
+                                                        guint day);
+void           e_month_widget_set_day_tooltip_markup   (EMonthWidget *self,
+                                                        guint day,
+                                                        const gchar *tooltip_markup);
+const gchar *  e_month_widget_get_day_tooltip_markup   (EMonthWidget *self,
+                                                        guint day);
+void           e_month_widget_clear_day_tooltips       (EMonthWidget *self);
+void           e_month_widget_add_day_css_class        (EMonthWidget *self,
+                                                        guint day,
+                                                        const gchar *name);
+void           e_month_widget_remove_day_css_class     (EMonthWidget *self,
+                                                        guint day,
+                                                        const gchar *name);
+void           e_month_widget_clear_day_css_classes    (EMonthWidget *self);
+guint          e_month_widget_get_day_at_position      (EMonthWidget *self,
+                                                        gdouble x_win,
+                                                        gdouble y_win);
+
+G_END_DECLS
+
+#endif /* E_MONTH_WIDGET_H */
diff --git a/src/e-util/e-util.h b/src/e-util/e-util.h
index 6801baf41c..c27dfa3110 100644
--- a/src/e-util/e-util.h
+++ b/src/e-util/e-util.h
@@ -158,6 +158,7 @@
 #include <e-util/e-menu-tool-button.h>
 #include <e-util/e-misc-utils.h>
 #include <e-util/e-mktemp.h>
+#include <e-util/e-month-widget.h>
 #include <e-util/e-name-selector-dialog.h>
 #include <e-util/e-name-selector-entry.h>
 #include <e-util/e-name-selector-list.h>
diff --git a/src/e-util/test-month-widget.c b/src/e-util/test-month-widget.c
new file mode 100644
index 0000000000..beba857dd6
--- /dev/null
+++ b/src/e-util/test-month-widget.c
@@ -0,0 +1,438 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include <e-util/e-util.h>
+
+static gboolean
+window_delete_event_cb (GtkWidget *widget,
+                       GdkEvent *event,
+                       gpointer user_data)
+{
+       gtk_main_quit ();
+
+       return FALSE;
+}
+
+static void
+month_changed_cb (GtkSpinButton *spin_button,
+                 EMonthWidget *month_widget)
+{
+       GDateMonth month;
+       guint year = 0;
+
+       month = gtk_spin_button_get_value_as_int (spin_button);
+       e_month_widget_get_month (month_widget, NULL, &year);
+       e_month_widget_set_month (month_widget, month, year);
+}
+
+static void
+year_changed_cb (GtkSpinButton *spin_button,
+                EMonthWidget *month_widget)
+{
+       GDateMonth month = G_DATE_JANUARY;
+       guint year;
+
+       year = gtk_spin_button_get_value_as_int (spin_button);
+       e_month_widget_get_month (month_widget, &month, NULL);
+       e_month_widget_set_month (month_widget, month, year);
+}
+
+static void
+week_start_day_changed_cb (GtkComboBox *combo,
+                          EMonthWidget *month_widget)
+{
+       const gchar *id = gtk_combo_box_get_active_id (combo);
+       e_month_widget_set_week_start_day (month_widget, g_ascii_strtoll (id, NULL, 10));
+}
+
+static gboolean
+month_widget_montion_notify_event_cb (EMonthWidget *month_widget,
+                                     GdkEvent *event,
+                                     guint *p_selected_day)
+{
+       gdouble x_win = -1, y_win = -1;
+
+       if (gdk_event_get_coords (event, &x_win, &y_win)) {
+               guint select_day;
+
+               select_day = e_month_widget_get_day_at_position (month_widget, x_win, y_win);
+
+               if (select_day == *p_selected_day)
+                       return FALSE;
+
+               if (*p_selected_day)
+                       e_month_widget_set_day_selected (month_widget, *p_selected_day, FALSE);
+
+               if (select_day)
+                       e_month_widget_set_day_selected (month_widget, select_day, TRUE);
+
+               *p_selected_day = select_day;
+       } else if (p_selected_day) {
+               e_month_widget_set_day_selected (month_widget, *p_selected_day, FALSE);
+               *p_selected_day = 0;
+       }
+
+       return FALSE;
+}
+
+static void
+bold_toggled_cb (GtkToggleButton *toggle_button,
+                guint *p_set_mark)
+{
+       *p_set_mark = (*p_set_mark & ~1) |
+               (gtk_toggle_button_get_active (toggle_button) ? 1 : 0);
+}
+
+static void
+italic_toggled_cb (GtkToggleButton *toggle_button,
+                  guint *p_set_mark)
+{
+       *p_set_mark = (*p_set_mark & ~2) |
+               (gtk_toggle_button_get_active (toggle_button) ? 2 : 0);
+}
+
+static void
+underline_toggled_cb (GtkToggleButton *toggle_button,
+                     guint *p_set_mark)
+{
+       *p_set_mark = (*p_set_mark & ~4) |
+               (gtk_toggle_button_get_active (toggle_button) ? 4 : 0);
+}
+
+
+static void
+highlight_toggled_cb (GtkToggleButton *toggle_button,
+                     guint *p_set_mark)
+{
+       *p_set_mark = (*p_set_mark & ~8) |
+               (gtk_toggle_button_get_active (toggle_button) ? 8 : 0);
+}
+
+static void
+clear_marks_clicked_cb (GtkToggleButton *toggle_button,
+                       EMonthWidget *month_widget)
+{
+       e_month_widget_clear_day_css_classes (month_widget);
+}
+
+static void
+month_widget_day_clicked_cb (EMonthWidget *widget,
+                            GdkEventButton *event,
+                            guint year,
+                            gint month,
+                            guint day,
+                            guint *p_set_mark)
+{
+       void (* func) (EMonthWidget *widget, guint day, const gchar *name);
+       gchar buff[128];
+
+       if (event->button == GDK_BUTTON_PRIMARY)
+               func = e_month_widget_add_day_css_class;
+       else if (event->button == GDK_BUTTON_SECONDARY)
+               func = e_month_widget_remove_day_css_class;
+       else
+               return;
+
+       if ((*p_set_mark) & 1)
+               func (widget, day, E_MONTH_WIDGET_CSS_CLASS_BOLD);
+       if ((*p_set_mark) & 2)
+               func (widget, day, E_MONTH_WIDGET_CSS_CLASS_ITALIC);
+       if ((*p_set_mark) & 4)
+               func (widget, day, E_MONTH_WIDGET_CSS_CLASS_UNDERLINE);
+       if ((*p_set_mark) & 8)
+               func (widget, day, E_MONTH_WIDGET_CSS_CLASS_HIGHLIGHT);
+
+       g_snprintf (buff, sizeof (buff), "Last clicked day: %04u-%02d-%02u", year, month, day);
+       gtk_widget_set_tooltip_text (GTK_WIDGET (widget), buff);
+}
+
+static void
+sync_year_on_change_cb (EMonthWidget *src_month_widget,
+                       EMonthWidget *des_month_widget)
+{
+       GDateMonth month = G_DATE_BAD_MONTH;
+       guint year = 0, cur_year = 0;
+
+       e_month_widget_get_month (src_month_widget, NULL, &year);
+       e_month_widget_get_month (des_month_widget, &month, &cur_year);
+
+       if (cur_year != year) {
+               e_month_widget_set_month (des_month_widget, month, year);
+               e_month_widget_clear_day_css_classes (des_month_widget);
+       }
+}
+
+static gint
+on_idle_create_widget (ESourceRegistry *registry)
+{
+       GDate *date;
+       GtkWidget *window, *notebook;
+       GtkWidget *widget, *container, *hbox, *month_widget;
+       static guint selected_day = 0;
+       static guint set_mark = 0;
+       guint year = 0;
+       gint ii;
+
+       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+       gtk_window_set_default_size (GTK_WINDOW (window), 300, 400);
+
+       g_signal_connect (
+               window, "delete-event",
+               G_CALLBACK (window_delete_event_cb), NULL);
+
+       notebook = gtk_notebook_new ();
+       gtk_notebook_set_show_border (GTK_NOTEBOOK (notebook), FALSE);
+       gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (notebook));
+
+       widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+
+       g_object_set (G_OBJECT (widget),
+               "hexpand", TRUE,
+               "halign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               NULL);
+
+       container = widget;
+
+       gtk_notebook_append_page (GTK_NOTEBOOK (notebook), container, gtk_label_new ("Month"));
+
+       month_widget = e_month_widget_new ();
+
+       g_signal_connect (month_widget, "motion-notify-event",
+               G_CALLBACK (month_widget_montion_notify_event_cb), &selected_day);
+
+       g_signal_connect (month_widget, "day-clicked",
+               G_CALLBACK (month_widget_day_clicked_cb), &set_mark);
+
+       hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+       gtk_box_pack_start (GTK_BOX (container), hbox, FALSE, FALSE, 0);
+
+       widget = gtk_label_new ("Month:");
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       widget = gtk_spin_button_new_with_range (1, 12, 1);
+       gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), 3);
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       g_signal_connect (widget, "value-changed",
+               G_CALLBACK (month_changed_cb), month_widget);
+
+       hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+       gtk_box_pack_start (GTK_BOX (container), hbox, FALSE, FALSE, 0);
+
+       widget = gtk_label_new ("Year:");
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       widget = gtk_spin_button_new_with_range (1, 3000, 1);
+       gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), 2022);
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       e_month_widget_set_month (E_MONTH_WIDGET (month_widget), 3, 2022);
+
+       g_signal_connect (widget, "value-changed",
+               G_CALLBACK (year_changed_cb), month_widget);
+
+       hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+       gtk_box_pack_start (GTK_BOX (container), hbox, FALSE, FALSE, 0);
+
+       widget = gtk_label_new ("Week start day:");
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       widget = gtk_combo_box_text_new ();
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+
+       gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "1", "Mo");
+       gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "2", "Tu");
+       gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "3", "We");
+       gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "4", "Th");
+       gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "5", "Fr");
+       gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "6", "Sa");
+       gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (widget), "7", "Su");
+
+       gtk_combo_box_set_active_id (GTK_COMBO_BOX (widget), "1");
+       e_month_widget_set_week_start_day (E_MONTH_WIDGET (month_widget), G_DATE_MONDAY);
+
+       g_signal_connect (widget, "changed", G_CALLBACK (week_start_day_changed_cb), month_widget);
+
+       widget = gtk_check_button_new_with_label ("Show week numbers");
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+
+       e_binding_bind_property (widget, "active", month_widget, "show-week-numbers", G_BINDING_SYNC_CREATE);
+
+       widget = gtk_check_button_new_with_label ("Show day names");
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+
+       e_binding_bind_property (widget, "active", month_widget, "show-day-names", G_BINDING_SYNC_CREATE);
+
+       widget = gtk_label_new ("Click to left-set/right-unset mark");
+       g_object_set (G_OBJECT (widget),
+               "halign", GTK_ALIGN_START,
+               NULL);
+       gtk_box_pack_start (GTK_BOX (container), widget, FALSE, FALSE, 0);
+
+       hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+       gtk_box_pack_start (GTK_BOX (container), hbox, FALSE, FALSE, 0);
+
+       widget = gtk_check_button_new_with_label ("B");
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+       g_signal_connect (widget, "toggled", G_CALLBACK (bold_toggled_cb), &set_mark);
+
+       widget = gtk_check_button_new_with_label ("I");
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+       g_signal_connect (widget, "toggled", G_CALLBACK (italic_toggled_cb), &set_mark);
+
+       widget = gtk_check_button_new_with_label ("U");
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+       g_signal_connect (widget, "toggled", G_CALLBACK (underline_toggled_cb), &set_mark);
+
+       widget = gtk_check_button_new_with_label ("H");
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+       g_signal_connect (widget, "toggled", G_CALLBACK (highlight_toggled_cb), &set_mark);
+
+       widget = gtk_button_new_with_label ("Clear Marks");
+       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
+       g_signal_connect (widget, "clicked", G_CALLBACK (clear_marks_clicked_cb), month_widget);
+
+       gtk_box_pack_start (GTK_BOX (container), month_widget, TRUE, TRUE, 0);
+
+       widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+
+       g_object_set (G_OBJECT (widget),
+               "hexpand", TRUE,
+               "halign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               NULL);
+
+       container = widget;
+
+       gtk_notebook_append_page (GTK_NOTEBOOK (notebook), container, gtk_label_new ("Year"));
+
+       widget = gtk_scrolled_window_new (NULL, NULL);
+
+       g_object_set (G_OBJECT (widget),
+               "hexpand", TRUE,
+               "halign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "hscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               "vscrollbar-policy", GTK_POLICY_AUTOMATIC,
+               NULL);
+
+       gtk_box_pack_start (GTK_BOX (container), widget, TRUE, TRUE, 0);
+       container = widget;
+
+       widget = gtk_flow_box_new ();
+
+       g_object_set (G_OBJECT (widget),
+               "hexpand", TRUE,
+               "halign", GTK_ALIGN_FILL,
+               "vexpand", TRUE,
+               "valign", GTK_ALIGN_FILL,
+               "column-spacing", 12,
+               "row-spacing", 12,
+               "homogeneous", TRUE,
+               "min-children-per-line", 1,
+               "max-children-per-line", 6,
+               "selection-mode", GTK_SELECTION_NONE,
+               NULL);
+
+       gtk_style_context_add_class (gtk_widget_get_style_context (widget), GTK_STYLE_CLASS_VIEW);
+
+       gtk_container_add (GTK_CONTAINER (container), widget);
+       container = widget;
+
+       e_month_widget_get_month (E_MONTH_WIDGET (month_widget), NULL, &year);
+
+       date = g_date_new_dmy (1, 1, 2022);
+
+       for (ii = 0; ii < 12; ii++) {
+               GtkFlowBoxChild *child;
+               GtkWidget *vbox;
+               gchar buffer[128];
+
+               g_date_strftime (buffer, sizeof (buffer), "%B", date);
+               g_date_add_months (date, 1);
+
+               vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
+
+               widget = gtk_label_new (buffer);
+
+               g_object_set (G_OBJECT (widget),
+                       "halign", GTK_ALIGN_CENTER,
+                       "valign", GTK_ALIGN_CENTER,
+                       "xalign", 0.5,
+                       "yalign", 0.5,
+                       NULL);
+
+               gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+               widget = e_month_widget_new ();
+
+               g_object_set (G_OBJECT (widget),
+                       "halign", GTK_ALIGN_CENTER,
+                       "valign", GTK_ALIGN_CENTER,
+                       NULL);
+
+               gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
+
+               g_signal_connect (month_widget, "changed",
+                       G_CALLBACK (sync_year_on_change_cb), widget);
+
+               g_signal_connect (widget, "day-clicked",
+                       G_CALLBACK (month_widget_day_clicked_cb), &set_mark);
+
+               e_binding_bind_property (month_widget, "week-start-day", widget, "week-start-day", 
G_BINDING_SYNC_CREATE);
+               e_binding_bind_property (month_widget, "show-week-numbers", widget, "show-week-numbers", 
G_BINDING_SYNC_CREATE);
+               e_binding_bind_property (month_widget, "show-day-names", widget, "show-day-names", 
G_BINDING_SYNC_CREATE);
+
+               e_month_widget_set_month (E_MONTH_WIDGET (widget), ii + 1, year);
+
+               gtk_container_add (GTK_CONTAINER (container), vbox);
+
+               child = gtk_flow_box_get_child_at_index (GTK_FLOW_BOX (container), ii);
+
+               g_object_set (G_OBJECT (child),
+                       "halign", GTK_ALIGN_CENTER,
+                       "valign", GTK_ALIGN_START,
+                       NULL);
+       }
+
+       g_date_free (date);
+
+       gtk_widget_show_all (window);
+
+       return FALSE;
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       ESourceRegistry *registry;
+       GError *local_error = NULL;
+
+       gtk_init (&argc, &argv);
+
+       registry = e_source_registry_new_sync (NULL, &local_error);
+
+       if (local_error != NULL) {
+               g_error (
+                       "Failed to load ESource registry: %s",
+                       local_error->message);
+               g_return_val_if_reached (-1);
+       }
+
+       g_idle_add ((GSourceFunc) on_idle_create_widget, registry);
+
+       gtk_main ();
+
+       g_object_unref (registry);
+       e_misc_util_free_global_memory ();
+
+       return 0;
+}
diff --git a/src/modules/calendar/e-cal-shell-content.c b/src/modules/calendar/e-cal-shell-content.c
index abcdee08e1..e8d159e80c 100644
--- a/src/modules/calendar/e-cal-shell-content.c
+++ b/src/modules/calendar/e-cal-shell-content.c
@@ -32,6 +32,7 @@
 #include "calendar/gui/e-day-view.h"
 #include "calendar/gui/e-month-view.h"
 #include "calendar/gui/e-week-view.h"
+#include "calendar/gui/e-year-view.h"
 #include "calendar/gui/itip-utils.h"
 #include "calendar/gui/tag-calendar.h"
 
@@ -431,6 +432,9 @@ cal_shell_content_datepicker_selection_changed_cb (ECalendarItem *calitem,
        g_return_if_fail (E_IS_CAL_SHELL_CONTENT (cal_shell_content));
        g_return_if_fail (E_IS_CALENDAR_ITEM (calitem));
 
+       if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_YEAR)
+               return;
+
        g_date_clear (&sel_start, 1);
        g_date_clear (&sel_end, 1);
 
@@ -511,6 +515,8 @@ cal_shell_content_datepicker_selection_changed_cb (ECalendarItem *calitem,
                } else if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_WORKWEEK) {
                        cal_shell_content_clamp_for_whole_weeks (calitem->week_start_day, &sel_start, 
&sel_end, TRUE);
                        e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_WEEK, &sel_start, 
&sel_end, FALSE);
+               } else if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_YEAR) {
+                       e_cal_shell_content_change_view (cal_shell_content, 
cal_shell_content->priv->current_view, &sel_start, &sel_end, FALSE);
                } else {
                        e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_DAY, &sel_start, 
&sel_end, FALSE);
                }
@@ -548,6 +554,8 @@ cal_shell_content_datepicker_selection_changed_cb (ECalendarItem *calitem,
                        cal_shell_content_clamp_for_whole_weeks (calitem->week_start_day, &sel_start, 
&sel_end, FALSE);
 
                        e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_LIST, &sel_start, 
&sel_end, FALSE);
+               } else if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_YEAR) {
+                       e_cal_shell_content_change_view (cal_shell_content, 
cal_shell_content->priv->current_view, &sel_start, &sel_end, FALSE);
                } else {
                        cal_shell_content_clamp_for_whole_weeks (calitem->week_start_day, &sel_start, 
&sel_end,
                                cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_MONTH || 
cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_WEEK);
@@ -667,6 +675,7 @@ cal_shell_content_current_view_id_changed_cb (ECalShellContent *cal_shell_conten
 
        switch (cal_shell_content->priv->current_view) {
                case E_CAL_VIEW_KIND_DAY:
+               case E_CAL_VIEW_KIND_YEAR:
                        /* Left the start & end being the current view start */
                        sel_end = sel_start;
                        break;
@@ -761,6 +770,9 @@ cal_shell_content_display_view_cb (ECalShellContent *cal_shell_content,
        } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_MONTH) {
                view_kind = E_CAL_VIEW_KIND_MONTH;
 
+       } else if (gal_view_type == GAL_TYPE_VIEW_CALENDAR_YEAR) {
+               view_kind = E_CAL_VIEW_KIND_YEAR;
+
        } else {
                g_return_if_reached ();
        }
@@ -971,8 +983,7 @@ cal_shell_content_check_state (EShellContent *shell_content)
        gboolean selection_can_delegate = FALSE;
        gboolean this_and_future_supported = FALSE;
        guint32 state = 0;
-       GList *selected;
-       GList *link;
+       GSList *selected, *link;
        guint n_selected;
 
        cal_shell_content = E_CAL_SHELL_CONTENT (shell_content);
@@ -985,15 +996,15 @@ cal_shell_content_check_state (EShellContent *shell_content)
        calendar_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content);
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       n_selected = g_list_length (selected);
+       n_selected = g_slist_length (selected);
 
        /* If we have a selection, assume it's
         * editable until we learn otherwise. */
        if (n_selected > 0)
                selection_is_editable = TRUE;
 
-       for (link = selected; link != NULL; link = g_list_next (link)) {
-               ECalendarViewEvent *event = link->data;
+       for (link = selected; link; link = g_slist_next (link)) {
+               ECalendarViewSelectionData *sel_data = link->data;
                ECalClient *client;
                ECalComponent *comp;
                gchar *user_email;
@@ -1004,11 +1015,8 @@ cal_shell_content_check_state (EShellContent *shell_content)
                gboolean icomp_is_delegated;
                gboolean read_only;
 
-               if (!is_comp_data_valid (event))
-                       continue;
-
-               client = event->comp_data->client;
-               icomp = event->comp_data->icalcomp;
+               client = sel_data->client;
+               icomp = sel_data->icalcomp;
 
                read_only = e_client_is_readonly (E_CLIENT (client));
                selection_is_editable &= !read_only;
@@ -1071,7 +1079,7 @@ cal_shell_content_check_state (EShellContent *shell_content)
                g_object_unref (comp);
        }
 
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 
        if (n_selected == 1)
                state |= E_CAL_BASE_SHELL_CONTENT_SELECTION_SINGLE;
@@ -1590,6 +1598,7 @@ e_cal_shell_content_create_calendar_views (ECalShellContent *cal_shell_content)
        ECalModel *model;
        ECalendarView *calendar_view;
        GtkAdjustment *adjustment;
+       GSettings *settings;
        time_t today;
        gint ii;
 
@@ -1597,6 +1606,7 @@ e_cal_shell_content_create_calendar_views (ECalShellContent *cal_shell_content)
        g_return_if_fail (cal_shell_content->priv->calendar_notebook != NULL);
        g_return_if_fail (cal_shell_content->priv->views[0] == NULL);
 
+       settings = e_util_ref_settings ("org.gnome.evolution.calendar");
        model = e_cal_base_shell_content_get_model (E_CAL_BASE_SHELL_CONTENT (cal_shell_content));
 
        /* Day View */
@@ -1635,6 +1645,11 @@ e_cal_shell_content_create_calendar_views (ECalShellContent *cal_shell_content)
                adjustment, "value-changed",
                G_CALLBACK (month_view_adjustment_changed_cb), cal_shell_content);
 
+       /* Year View */
+       calendar_view = e_year_view_new (model);
+       cal_shell_content->priv->views[E_CAL_VIEW_KIND_YEAR] = calendar_view;
+       g_object_ref_sink (calendar_view);
+
        /* List View */
        calendar_view = e_cal_list_view_new (cal_shell_content->priv->list_view_model);
        cal_shell_content->priv->views[E_CAL_VIEW_KIND_LIST] = calendar_view;
@@ -1663,6 +1678,8 @@ e_cal_shell_content_create_calendar_views (ECalShellContent *cal_shell_content)
                        GTK_WIDGET (calendar_view), NULL);
                gtk_widget_show (GTK_WIDGET (calendar_view));
        }
+
+       g_object_unref (settings);
 }
 
 static void
@@ -2206,7 +2223,9 @@ cal_shell_content_switch_list_view (ECalShellContent *cal_shell_content,
        g_return_if_fail (from_view_kind != to_view_kind);
 
        if (to_view_kind != E_CAL_VIEW_KIND_LIST &&
-           from_view_kind != E_CAL_VIEW_KIND_LIST)
+           to_view_kind != E_CAL_VIEW_KIND_YEAR &&
+           from_view_kind != E_CAL_VIEW_KIND_LIST &&
+           from_view_kind != E_CAL_VIEW_KIND_YEAR)
                return;
 
        shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_shell_content));
@@ -2215,15 +2234,17 @@ cal_shell_content_switch_list_view (ECalShellContent *cal_shell_content,
        date_navigator = e_cal_base_shell_sidebar_get_date_navigator (cal_base_shell_sidebar);
        source_selector = e_cal_base_shell_sidebar_get_selector (cal_base_shell_sidebar);
 
-       gtk_widget_set_visible (GTK_WIDGET (date_navigator), to_view_kind != E_CAL_VIEW_KIND_LIST);
+       gtk_widget_set_visible (GTK_WIDGET (date_navigator), to_view_kind != E_CAL_VIEW_KIND_LIST && 
to_view_kind != E_CAL_VIEW_KIND_YEAR);
        e_source_selector_set_show_toggles (source_selector, to_view_kind != E_CAL_VIEW_KIND_LIST);
 
-       model = e_calendar_view_get_model (cal_shell_content->priv->views[from_view_kind]);
-       cal_filter = e_cal_data_model_dup_filter (e_cal_model_get_data_model (model));
-       if (cal_filter) {
-               model = e_calendar_view_get_model (cal_shell_content->priv->views[to_view_kind]);
-               e_cal_data_model_set_filter (e_cal_model_get_data_model (model), cal_filter);
-               g_free (cal_filter);
+       if (to_view_kind == E_CAL_VIEW_KIND_LIST || from_view_kind == E_CAL_VIEW_KIND_LIST) {
+               model = e_calendar_view_get_model (cal_shell_content->priv->views[from_view_kind]);
+               cal_filter = e_cal_data_model_dup_filter (e_cal_model_get_data_model (model));
+               if (cal_filter) {
+                       model = e_calendar_view_get_model (cal_shell_content->priv->views[to_view_kind]);
+                       e_cal_data_model_set_filter (e_cal_model_get_data_model (model), cal_filter);
+                       g_free (cal_filter);
+               }
        }
 
        /* The list view is activated */
@@ -2241,6 +2262,7 @@ e_cal_shell_content_set_current_view_id (ECalShellContent *cal_shell_content,
                                         ECalViewKind view_kind)
 {
        EShellView *shell_view;
+       EShellWindow *shell_window;
        time_t start_time = -1, end_time = -1;
        gint ii;
 
@@ -2313,13 +2335,17 @@ e_cal_shell_content_set_current_view_id (ECalShellContent *cal_shell_content,
 
        cal_shell_content_switch_list_view (cal_shell_content, cal_shell_content->priv->current_view, 
view_kind);
 
+       shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_shell_content));
+       shell_window = e_shell_view_get_shell_window (shell_view);
+
+       gtk_action_set_sensitive (ACTION (CALENDAR_PREVIEW_MENU), view_kind == E_CAL_VIEW_KIND_YEAR);
+
        cal_shell_content->priv->current_view = view_kind;
 
        g_object_notify (G_OBJECT (cal_shell_content), "current-view-id");
 
        gtk_widget_queue_draw (GTK_WIDGET 
(cal_shell_content->priv->views[cal_shell_content->priv->current_view]));
 
-       shell_view = e_shell_content_get_shell_view (E_SHELL_CONTENT (cal_shell_content));
        e_shell_view_update_actions (shell_view);
        e_cal_shell_view_update_sidebar (E_CAL_SHELL_VIEW (shell_view));
 }
@@ -2449,6 +2475,15 @@ cal_shell_content_move_view_range_relative (ECalShellContent *cal_shell_content,
                        g_date_set_day (&end, g_date_get_days_in_month (g_date_get_month (&start), 
g_date_get_year (&start)));
                        g_date_add_days (&end, 6);
                        break;
+               case E_CAL_VIEW_KIND_YEAR:
+                       if (direction > 0) {
+                               g_date_add_years (&start, direction);
+                               g_date_add_years (&end, direction);
+                       } else {
+                               g_date_subtract_years (&start, direction * -1);
+                               g_date_subtract_years (&end, direction * -1);
+                       }
+                       break;
                case E_CAL_VIEW_KIND_LAST:
                        return;
        }
@@ -2492,13 +2527,25 @@ e_cal_shell_content_move_view_range (ECalShellContent *cal_shell_content,
                case E_CALENDAR_VIEW_MOVE_TO_TODAY:
                        tt = i_cal_time_new_current_with_zone (zone);
                        g_date_set_dmy (&date, i_cal_time_get_day (tt), i_cal_time_get_month (tt), 
i_cal_time_get_year (tt));
+                       if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_YEAR) {
+                               ECalendarView *cal_view = e_cal_shell_content_get_current_calendar_view 
(cal_shell_content);
+                               time_t tmt;
+
+                               tmt = i_cal_time_as_timet (tt);
+                               e_calendar_view_set_selected_time_range (cal_view, tmt, tmt);
+                       }
                        g_clear_object (&tt);
                        /* one-day selection takes care of the view range move with left view kind */
                        e_calendar_item_set_selection (e_calendar_get_item (calendar), &date, &date);
                        break;
                case E_CALENDAR_VIEW_MOVE_TO_EXACT_DAY:
-                       time_to_gdate_with_zone (&date, exact_date, zone);
-                       e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_DAY, &date, 
&date, FALSE);
+                       if (cal_shell_content->priv->current_view == E_CAL_VIEW_KIND_YEAR) {
+                               ECalendarView *cal_view = e_cal_shell_content_get_current_calendar_view 
(cal_shell_content);
+                               e_calendar_view_set_selected_time_range (cal_view, exact_date, exact_date);
+                       } else {
+                               time_to_gdate_with_zone (&date, exact_date, zone);
+                               e_cal_shell_content_change_view (cal_shell_content, E_CAL_VIEW_KIND_DAY, 
&date, &date, FALSE);
+                       }
                        break;
        }
 }
diff --git a/src/modules/calendar/e-cal-shell-content.h b/src/modules/calendar/e-cal-shell-content.h
index 23dfe46d08..63045b0737 100644
--- a/src/modules/calendar/e-cal-shell-content.h
+++ b/src/modules/calendar/e-cal-shell-content.h
@@ -54,6 +54,7 @@ typedef enum {
        E_CAL_VIEW_KIND_WORKWEEK,
        E_CAL_VIEW_KIND_WEEK,
        E_CAL_VIEW_KIND_MONTH,
+       E_CAL_VIEW_KIND_YEAR,
        E_CAL_VIEW_KIND_LIST,
        E_CAL_VIEW_KIND_LAST
 } ECalViewKind;
diff --git a/src/modules/calendar/e-cal-shell-view-actions.c b/src/modules/calendar/e-cal-shell-view-actions.c
index c2ffd82089..0c282cc105 100644
--- a/src/modules/calendar/e-cal-shell-view-actions.c
+++ b/src/modules/calendar/e-cal-shell-view-actions.c
@@ -23,6 +23,7 @@
 #include "calendar/gui/e-cal-dialogs.h"
 #include "calendar/gui/e-cal-ops.h"
 #include "calendar/gui/e-comp-editor.h"
+#include "calendar/gui/e-year-view.h"
 #include "calendar/gui/itip-utils.h"
 #include "calendar/gui/print.h"
 
@@ -204,6 +205,7 @@ cal_shell_view_actions_print_or_preview (ECalShellView *cal_shell_view,
 
                switch (e_cal_shell_content_get_current_view_id (cal_shell_content)) {
                        case E_CAL_VIEW_KIND_DAY:
+                       case E_CAL_VIEW_KIND_YEAR:
                                print_view_type = E_PRINT_VIEW_DAY;
                                break;
                        case E_CAL_VIEW_KIND_WORKWEEK:
@@ -498,6 +500,10 @@ action_calendar_view_cb (GtkRadioAction *action,
                        view_id = "Month_View";
                        break;
 
+               case E_CAL_VIEW_KIND_YEAR:
+                       view_id = "Year_View";
+                       break;
+
                case E_CAL_VIEW_KIND_LIST:
                        view_id = "List_View";
                        break;
@@ -509,6 +515,30 @@ action_calendar_view_cb (GtkRadioAction *action,
        e_shell_view_set_view_id (shell_view, view_id);
 }
 
+static void
+action_calendar_preview_cb (GtkRadioAction *action,
+                           GtkRadioAction *current,
+                           ECalShellView *cal_shell_view)
+{
+       GtkOrientation orientation;
+       EYearView *year_view;
+
+       year_view = E_YEAR_VIEW (cal_shell_view->priv->views[E_CAL_VIEW_KIND_YEAR].calendar_view);
+
+       switch (gtk_radio_action_get_current_value (action)) {
+               case 0:
+                       orientation = GTK_ORIENTATION_VERTICAL;
+                       break;
+               case 1:
+                       orientation = GTK_ORIENTATION_HORIZONTAL;
+                       break;
+               default:
+                       g_return_if_reached ();
+       }
+
+       e_year_view_set_preview_orientation (year_view, orientation);
+}
+
 static void
 cal_shell_view_transfer_selected (ECalShellView *cal_shell_view,
                                  gboolean is_move)
@@ -520,7 +550,7 @@ cal_shell_view_transfer_selected (ECalShellView *cal_shell_view,
        ESource *source_source = NULL;
        ESource *destination_source = NULL;
        ESourceRegistry *registry;
-       GList *selected, *link;
+       GSList *selected, *link;
        GHashTable *by_source; /* ESource ~> GSList{ICalComponent} */
        GHashTableIter iter;
        gpointer key, value;
@@ -548,26 +578,23 @@ cal_shell_view_transfer_selected (ECalShellView *cal_shell_view,
                GTK_WINDOW (shell_window), registry,
                E_CAL_CLIENT_SOURCE_TYPE_EVENTS, source_source);
        if (destination_source == NULL) {
-               g_list_free (selected);
+               g_slist_free_full (selected, e_calendar_view_selection_data_free);
                return;
        }
 
        by_source = g_hash_table_new ((GHashFunc) e_source_hash, (GEqualFunc) e_source_equal);
 
-       for (link = selected; link != NULL; link = g_list_next (link)) {
-               ECalendarViewEvent *event = link->data;
+       for (link = selected; link; link = g_slist_next (link)) {
+               ECalendarViewSelectionData *sel_data = link->data;
                ESource *source;
                GSList *icomps;
 
-               if (!event || !event->comp_data)
-                       continue;
-
-               source = e_client_get_source (E_CLIENT (event->comp_data->client));
+               source = e_client_get_source (E_CLIENT (sel_data->client));
                if (!source)
                        continue;
 
                icomps = g_hash_table_lookup (by_source, source);
-               icomps = g_slist_prepend (icomps, event->comp_data->icalcomp);
+               icomps = g_slist_prepend (icomps, sel_data->icalcomp);
                g_hash_table_insert (by_source, source, icomps);
        }
 
@@ -583,7 +610,7 @@ cal_shell_view_transfer_selected (ECalShellView *cal_shell_view,
 
        g_hash_table_destroy (by_source);
        g_clear_object (&destination_source);
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -607,11 +634,11 @@ action_event_delegate_cb (GtkAction *action,
        ESourceRegistry *registry;
        ECalShellContent *cal_shell_content;
        ECalendarView *calendar_view;
-       ECalendarViewEvent *event;
+       ECalendarViewSelectionData *sel_data;
        ECalComponent *component;
        ECalClient *client;
        ECalModel *model;
-       GList *selected;
+       GSList *selected;
        ICalComponent *clone;
        ICalProperty *prop;
        gboolean found = FALSE;
@@ -621,18 +648,15 @@ action_event_delegate_cb (GtkAction *action,
        calendar_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content);
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       g_return_if_fail (g_list_length (selected) == 1);
+       g_return_if_fail (g_slist_length (selected) == 1);
 
        model = e_calendar_view_get_model (calendar_view);
        registry = e_cal_model_get_registry (model);
 
-       event = selected->data;
-
-       if (!is_comp_data_valid (event))
-               return;
+       sel_data = selected->data;
 
-       client = event->comp_data->client;
-       clone = i_cal_component_clone (event->comp_data->icalcomp);
+       client = sel_data->client;
+       clone = i_cal_component_clone (sel_data->icalcomp);
 
        /* Set the attendee status for the delegate. */
 
@@ -692,11 +716,11 @@ action_event_delegate_cb (GtkAction *action,
        g_object_unref (component);
 
        e_calendar_view_open_event_with_flags (
-               calendar_view, event->comp_data->client, clone,
+               calendar_view, sel_data->client, clone,
                E_COMP_EDITOR_FLAG_WITH_ATTENDEES | E_COMP_EDITOR_FLAG_DELEGATE);
 
        g_object_unref (clone);
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -744,25 +768,21 @@ action_event_forward_cb (GtkAction *action,
 {
        ECalShellContent *cal_shell_content;
        ECalendarView *calendar_view;
-       ECalendarViewEvent *event;
+       ECalendarViewSelectionData *sel_data;
        ECalComponent *component;
        ECalClient *client;
        ICalComponent *icomp;
-       GList *selected;
+       GSList *selected;
 
        cal_shell_content = cal_shell_view->priv->cal_shell_content;
        calendar_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content);
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       g_return_if_fail (g_list_length (selected) == 1);
-
-       event = selected->data;
+       g_return_if_fail (g_slist_length (selected) == 1);
 
-       if (!is_comp_data_valid (event))
-               return;
-
-       client = event->comp_data->client;
-       icomp = event->comp_data->icalcomp;
+       sel_data = selected->data;
+       client = sel_data->client;
+       icomp = sel_data->icalcomp;
 
        component = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
        g_return_if_fail (component != NULL);
@@ -772,7 +792,7 @@ action_event_forward_cb (GtkAction *action,
                NULL, NULL, NULL, E_ITIP_SEND_COMPONENT_FLAG_STRIP_ALARMS | 
E_ITIP_SEND_COMPONENT_FLAG_ENSURE_MASTER_OBJECT);
 
        g_object_unref (component);
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -803,13 +823,13 @@ action_event_popup_rsvp_response_cb (GtkAction *action,
 {
        ECalShellContent *cal_shell_content;
        ECalendarView *calendar_view;
-       ECalendarViewEvent *event;
+       ECalendarViewSelectionData *sel_data;
        ECalClient *client;
        ECalComponent *comp;
        ECalModel *model;
        ICalParameterPartstat partstat = I_CAL_PARTSTAT_NONE;
        ICalComponent *clone;
-       GList *selected;
+       GSList *selected;
        const gchar *action_name;
        gboolean ensure_master_object;
 
@@ -831,22 +851,18 @@ action_event_popup_rsvp_response_cb (GtkAction *action,
        }
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       g_return_if_fail (g_list_length (selected) == 1);
-
-       event = selected->data;
+       g_return_if_fail (g_slist_length (selected) == 1);
 
-       g_list_free (selected);
-
-       if (!is_comp_data_valid (event))
-               return;
+       sel_data = selected->data;
 
-       client = event->comp_data->client;
+       client = sel_data->client;
        model = e_calendar_view_get_model (calendar_view);
 
-       clone = i_cal_component_clone (event->comp_data->icalcomp);
+       clone = i_cal_component_clone (sel_data->icalcomp);
        comp = e_cal_component_new_from_icalcomponent (clone);
 
        if (!comp) {
+               g_slist_free_full (selected, e_calendar_view_selection_data_free);
                g_warn_if_reached ();
                return;
        }
@@ -863,6 +879,7 @@ action_event_popup_rsvp_response_cb (GtkAction *action,
                (partstat == I_CAL_PARTSTAT_DECLINED ? E_ITIP_SEND_COMPONENT_FLAG_SAVE_RESPONSE_DECLINED : 0) 
|
                (partstat == I_CAL_PARTSTAT_TENTATIVE ? E_ITIP_SEND_COMPONENT_FLAG_SAVE_RESPONSE_TENTATIVE : 
0));
 
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
        g_clear_object (&comp);
 }
 
@@ -910,7 +927,7 @@ action_event_occurrence_movable_cb (GtkAction *action,
        ECalShellContent *cal_shell_content;
        ECalModel *model;
        ECalendarView *calendar_view;
-       ECalendarViewEvent *event;
+       ECalendarViewSelectionData *sel_data;
        ECalComponent *exception_component;
        ECalComponent *recurring_component;
        ECalComponentDateTime *date;
@@ -918,7 +935,9 @@ action_event_occurrence_movable_cb (GtkAction *action,
        ECalClient *client;
        ICalComponent *icomp;
        ICalTimezone *timezone;
-       GList *selected;
+       ICalTime *itt_start = NULL, *itt_end = NULL;
+       GSList *selected;
+       time_t instance_start, instance_end;
        gchar *uid;
        EActivity *activity;
        MakeMovableData *mmd;
@@ -930,15 +949,21 @@ action_event_occurrence_movable_cb (GtkAction *action,
        timezone = e_cal_model_get_timezone (model);
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       g_return_if_fail (g_list_length (selected) == 1);
+       g_return_if_fail (g_slist_length (selected) == 1);
 
-       event = selected->data;
+       sel_data = selected->data;
+       client = sel_data->client;
+       icomp = sel_data->icalcomp;
 
-       if (!is_comp_data_valid (event))
-               return;
+       cal_comp_get_instance_times (client, icomp, timezone, &itt_start, &itt_end, NULL);
+
+       instance_start = itt_start ? i_cal_time_as_timet_with_zone (itt_start,
+               i_cal_time_get_timezone (itt_start)) : 0;
+       instance_end = itt_end ? i_cal_time_as_timet_with_zone (itt_end,
+               i_cal_time_get_timezone (itt_end)) : 0;
 
-       client = event->comp_data->client;
-       icomp = event->comp_data->icalcomp;
+       g_clear_object (&itt_start);
+       g_clear_object (&itt_end);
 
        /* For the recurring object, we add an exception
         * to get rid of the instance. */
@@ -962,10 +987,10 @@ action_event_occurrence_movable_cb (GtkAction *action,
        e_cal_component_set_exdates (exception_component, NULL);
        e_cal_component_set_exrules (exception_component, NULL);
 
-       date = e_cal_component_datetime_new_take (i_cal_time_new_from_timet_with_zone 
(event->comp_data->instance_start, FALSE, timezone),
+       date = e_cal_component_datetime_new_take (i_cal_time_new_from_timet_with_zone (instance_start, FALSE, 
timezone),
                timezone ? g_strdup (i_cal_timezone_get_tzid (timezone)) : NULL);
        cal_comp_set_dtstart_with_oldzone (client, exception_component, date);
-       e_cal_component_datetime_take_value (date, i_cal_time_new_from_timet_with_zone 
(event->comp_data->instance_end, FALSE, timezone));
+       e_cal_component_datetime_take_value (date, i_cal_time_new_from_timet_with_zone (instance_end, FALSE, 
timezone));
        cal_comp_set_dtend_with_oldzone (client, exception_component, date);
        e_cal_component_datetime_free (date);
 
@@ -985,7 +1010,7 @@ action_event_occurrence_movable_cb (GtkAction *action,
        e_cal_component_id_free (id);
        g_object_unref (recurring_component);
        g_object_unref (exception_component);
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -1007,8 +1032,8 @@ action_event_edit_as_new_cb (GtkAction *action,
 {
        ECalShellContent *cal_shell_content;
        ECalendarView *calendar_view;
-       ECalendarViewEvent *event;
-       GList *selected;
+       ECalendarViewSelectionData *sel_data;
+       GSList *selected;
        ICalComponent *clone;
        gchar *uid;
 
@@ -1016,28 +1041,27 @@ action_event_edit_as_new_cb (GtkAction *action,
        calendar_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content);
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       g_return_if_fail (g_list_length (selected) == 1);
+       g_return_if_fail (g_slist_length (selected) == 1);
 
-       event = selected->data;
+       sel_data = selected->data;
 
-       if (!is_comp_data_valid (event) ||
-           e_cal_util_component_is_instance (event->comp_data->icalcomp)) {
-               g_list_free (selected);
+       if (e_cal_util_component_is_instance (sel_data->icalcomp)) {
+               g_slist_free_full (selected, e_calendar_view_selection_data_free);
                return;
        }
 
-       clone = i_cal_component_clone (event->comp_data->icalcomp);
+       clone = i_cal_component_clone (sel_data->icalcomp);
 
        uid = e_util_generate_uid ();
        i_cal_component_set_uid (clone, uid);
        g_free (uid);
 
        e_calendar_view_open_event_with_flags (
-               calendar_view, event->comp_data->client, clone,
+               calendar_view, sel_data->client, clone,
                E_COMP_EDITOR_FLAG_IS_NEW);
 
        g_clear_object (&clone);
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -1046,27 +1070,23 @@ action_event_print_cb (GtkAction *action,
 {
        ECalShellContent *cal_shell_content;
        ECalendarView *calendar_view;
-       ECalendarViewEvent *event;
+       ECalendarViewSelectionData *sel_data;
        ECalComponent *component;
        ECalModel *model;
        ECalClient *client;
        ICalComponent *icomp;
-       GList *selected;
+       GSList *selected;
 
        cal_shell_content = cal_shell_view->priv->cal_shell_content;
        calendar_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content);
        model = e_calendar_view_get_model (calendar_view);
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       g_return_if_fail (g_list_length (selected) == 1);
-
-       event = selected->data;
-
-       if (!is_comp_data_valid (event))
-               return;
+       g_return_if_fail (g_slist_length (selected) == 1);
 
-       client = event->comp_data->client;
-       icomp = event->comp_data->icalcomp;
+       sel_data = selected->data;
+       client = sel_data->client;
+       icomp = sel_data->icalcomp;
 
        component = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
 
@@ -1077,7 +1097,7 @@ action_event_print_cb (GtkAction *action,
                GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG);
 
        g_object_unref (component);
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -1086,27 +1106,23 @@ cal_shell_view_actions_reply (ECalShellView *cal_shell_view,
 {
        ECalShellContent *cal_shell_content;
        ECalendarView *calendar_view;
-       ECalendarViewEvent *event;
+       ECalendarViewSelectionData *sel_data;
        ECalComponent *component;
        ECalClient *client;
        ESourceRegistry *registry;
        ICalComponent *icomp;
-       GList *selected;
+       GSList *selected;
 
        cal_shell_content = cal_shell_view->priv->cal_shell_content;
        registry = e_shell_get_registry (e_shell_window_get_shell (e_shell_view_get_shell_window 
(E_SHELL_VIEW (cal_shell_view))));
        calendar_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content);
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       g_return_if_fail (g_list_length (selected) == 1);
+       g_return_if_fail (g_slist_length (selected) == 1);
 
-       event = selected->data;
-
-       if (!is_comp_data_valid (event))
-               return;
-
-       client = event->comp_data->client;
-       icomp = event->comp_data->icalcomp;
+       sel_data = selected->data;
+       client = sel_data->client;
+       icomp = sel_data->icalcomp;
 
        component = e_cal_component_new_from_icalcomponent (i_cal_component_clone (icomp));
 
@@ -1115,7 +1131,7 @@ cal_shell_view_actions_reply (ECalShellView *cal_shell_view,
                component, client, reply_all, NULL, NULL);
 
        g_object_unref (component);
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -1142,11 +1158,11 @@ action_event_save_as_cb (GtkAction *action,
        EShellBackend *shell_backend;
        ECalShellContent *cal_shell_content;
        ECalendarView *calendar_view;
-       ECalendarViewEvent *event;
+       ECalendarViewSelectionData *sel_data;
        ECalClient *client;
        ICalComponent *icomp;
        EActivity *activity;
-       GList *selected;
+       GSList *selected;
        GFile *file;
        gchar *string = NULL;
 
@@ -1159,15 +1175,11 @@ action_event_save_as_cb (GtkAction *action,
        calendar_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content);
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       g_return_if_fail (g_list_length (selected) == 1);
+       g_return_if_fail (g_slist_length (selected) == 1);
 
-       event = selected->data;
-
-       if (!is_comp_data_valid (event))
-               return;
-
-       client = event->comp_data->client;
-       icomp = event->comp_data->icalcomp;
+       sel_data = selected->data;
+       client = sel_data->client;
+       icomp = sel_data->icalcomp;
 
        /* Translators: Default filename part saving an event to a file when
         * no summary is filed, the '.ics' extension is concatenated to it. */
@@ -1177,7 +1189,7 @@ action_event_save_as_cb (GtkAction *action,
                "*.ics:text/calendar", NULL, NULL);
        g_free (string);
        if (file == NULL)
-               return;
+               goto exit;
 
        string = e_cal_client_get_component_as_string (client, icomp);
        if (string == NULL) {
@@ -1197,9 +1209,9 @@ action_event_save_as_cb (GtkAction *action,
                "file-content", string,
                (GDestroyNotify) g_free);
 
-exit:
-       g_object_unref (file);
-       g_list_free (selected);
+ exit:
+       g_clear_object (&file);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -1208,24 +1220,20 @@ edit_event_as (ECalShellView *cal_shell_view,
 {
        ECalShellContent *cal_shell_content;
        ECalendarView *calendar_view;
-       ECalendarViewEvent *event;
+       ECalendarViewSelectionData *sel_data;
        ECalClient *client;
        ICalComponent *icomp;
-       GList *selected;
+       GSList *selected;
 
        cal_shell_content = cal_shell_view->priv->cal_shell_content;
        calendar_view = e_cal_shell_content_get_current_calendar_view (cal_shell_content);
 
        selected = e_calendar_view_get_selected_events (calendar_view);
-       g_return_if_fail (g_list_length (selected) == 1);
+       g_return_if_fail (g_slist_length (selected) == 1);
 
-       event = selected->data;
-
-       if (!is_comp_data_valid (event))
-               return;
-
-       client = event->comp_data->client;
-       icomp = event->comp_data->icalcomp;
+       sel_data = selected->data;
+       client = sel_data->client;
+       icomp = sel_data->icalcomp;
 
        if (!as_meeting && icomp) {
                /* remove organizer and all attendees */
@@ -1244,7 +1252,7 @@ edit_event_as (ECalShellView *cal_shell_view,
                g_object_unref (icomp);
        }
 
-       g_list_free (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 }
 
 static void
@@ -1609,6 +1617,13 @@ static GtkActionEntry calendar_entries[] = {
          N_("_Actions"),
          NULL,
          NULL,
+         NULL },
+
+       { "calendar-preview-menu",
+         NULL,
+         N_("_Preview"),
+         NULL,
+         NULL,
          NULL }
 };
 
@@ -1725,6 +1740,14 @@ static EPopupActionEntry calendar_popup_entries[] = {
 
 static GtkToggleActionEntry calendar_toggle_entries[] = {
 
+       { "calendar-preview",
+         NULL,
+         N_("Show Event _Preview"),
+         "<Control>m",
+         N_("Show event preview pane"),
+         NULL,  /* Handled by property bindings */
+         TRUE },
+
        { "calendar-show-tag-vpane",
          NULL,
          N_("Show T_asks and Memos pane"),
@@ -1779,7 +1802,41 @@ static GtkRadioActionEntry calendar_view_entries[] = {
          N_("Work Week"),
          "<Control>j",
          N_("Show one work week"),
-         E_CAL_VIEW_KIND_WORKWEEK }
+         E_CAL_VIEW_KIND_WORKWEEK },
+
+       { "calendar-view-year",
+         "view-calendar-year",
+         N_("Year"),
+         NULL,
+         N_("Show as year"),
+         E_CAL_VIEW_KIND_YEAR }
+};
+
+static GtkRadioActionEntry calendar_preview_entries[] = {
+
+       /* This action represents the initial active preview.
+        * It should not be visible in the UI, nor should it be
+        * possible to switch to it from another shell view. */
+       { "calendar-preview-initial",
+         NULL,
+         NULL,
+         NULL,
+         NULL,
+         BOGUS_INITIAL_VALUE },
+
+       { "calendar-preview-horizontal",
+         NULL,
+         N_("_Horizontal View"),
+         NULL,
+         N_("Show event preview below the calendar"),
+         0 },
+
+       { "calendar-preview-vertical",
+         NULL,
+         N_("_Vertical View"),
+         NULL,
+         N_("Show event preview alongside the calendar"),
+         1 }
 };
 
 static GtkRadioActionEntry calendar_filter_entries[] = {
@@ -1908,6 +1965,7 @@ e_cal_shell_view_actions_init (ECalShellView *cal_shell_view)
        EShellSearchbar *searchbar;
        GtkActionGroup *action_group;
        GtkAction *action;
+       GSettings *settings;
 
        shell_view = E_SHELL_VIEW (cal_shell_view);
        shell_window = e_shell_view_get_shell_window (shell_view);
@@ -1930,6 +1988,10 @@ e_cal_shell_view_actions_init (ECalShellView *cal_shell_view)
                action_group, calendar_view_entries,
                G_N_ELEMENTS (calendar_view_entries), BOGUS_INITIAL_VALUE,
                G_CALLBACK (action_calendar_view_cb), cal_shell_view);
+       gtk_action_group_add_radio_actions (
+               action_group, calendar_preview_entries,
+               G_N_ELEMENTS (calendar_preview_entries), BOGUS_INITIAL_VALUE,
+               G_CALLBACK (action_calendar_preview_cb), cal_shell_view);
        gtk_action_group_add_radio_actions (
                action_group, calendar_search_entries,
                G_N_ELEMENTS (calendar_search_entries),
@@ -1960,6 +2022,15 @@ e_cal_shell_view_actions_init (ECalShellView *cal_shell_view)
                action_group, lockdown_save_to_disk_popup_entries,
                G_N_ELEMENTS (lockdown_save_to_disk_popup_entries));
 
+       settings = e_util_ref_settings ("org.gnome.evolution.calendar");
+
+       g_settings_bind (
+               settings, "year-layout",
+               ACTION (CALENDAR_PREVIEW_VERTICAL), "current-value",
+               G_SETTINGS_BIND_DEFAULT);
+
+       g_clear_object (&settings);
+
        /* Fine tuning. */
 
        action = ACTION (CALENDAR_GO_TODAY);
@@ -1989,6 +2060,20 @@ e_cal_shell_view_actions_init (ECalShellView *cal_shell_view)
                action, "active",
                G_SETTINGS_BIND_GET);
 
+       action = ACTION (CALENDAR_VIEW_YEAR);
+       gtk_action_set_is_important (action, TRUE);
+
+       g_settings_bind (
+               cal_shell_view->priv->settings, "year-show-preview",
+               ACTION (CALENDAR_PREVIEW), "active",
+               G_SETTINGS_BIND_DEFAULT);
+
+       e_binding_bind_property (
+               ACTION (CALENDAR_PREVIEW), "active",
+               cal_shell_view->priv->views[E_CAL_VIEW_KIND_YEAR].calendar_view, "preview-visible",
+               G_BINDING_BIDIRECTIONAL |
+               G_BINDING_SYNC_CREATE);
+
        /* Initialize the memo and task pad actions. */
        e_cal_shell_view_memopad_actions_init (cal_shell_view);
        e_cal_shell_view_taskpad_actions_init (cal_shell_view);
diff --git a/src/modules/calendar/e-cal-shell-view-actions.h b/src/modules/calendar/e-cal-shell-view-actions.h
index 6917e74827..1529a9545b 100644
--- a/src/modules/calendar/e-cal-shell-view-actions.h
+++ b/src/modules/calendar/e-cal-shell-view-actions.h
@@ -38,6 +38,14 @@
        E_SHELL_WINDOW_ACTION ((window), "calendar-jump-to")
 #define E_SHELL_WINDOW_ACTION_CALENDAR_NEW(window) \
        E_SHELL_WINDOW_ACTION ((window), "calendar-new")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_PREVIEW(window) \
+       E_SHELL_WINDOW_ACTION ((window), "calendar-preview")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_PREVIEW_MENU(window) \
+       E_SHELL_WINDOW_ACTION ((window), "calendar-preview-menu")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_PREVIEW_HORIZONTAL(window) \
+       E_SHELL_WINDOW_ACTION ((window), "calendar-preview-horizontal")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_PREVIEW_VERTICAL(window) \
+       E_SHELL_WINDOW_ACTION ((window), "calendar-preview-vertical")
 #define E_SHELL_WINDOW_ACTION_CALENDAR_PRINT(window) \
        E_SHELL_WINDOW_ACTION ((window), "calendar-print")
 #define E_SHELL_WINDOW_ACTION_CALENDAR_PRINT_PREVIEW(window) \
@@ -74,6 +82,8 @@
        E_SHELL_WINDOW_ACTION ((window), "calendar-view-week")
 #define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_WORKWEEK(window) \
        E_SHELL_WINDOW_ACTION ((window), "calendar-view-workweek")
+#define E_SHELL_WINDOW_ACTION_CALENDAR_VIEW_YEAR(window) \
+       E_SHELL_WINDOW_ACTION ((window), "calendar-view-year")
 
 /* Event Actions */
 #define E_SHELL_WINDOW_ACTION_EVENT_DELEGATE(window) \
diff --git a/src/modules/calendar/e-cal-shell-view-private.c b/src/modules/calendar/e-cal-shell-view-private.c
index e098b32252..da0142958e 100644
--- a/src/modules/calendar/e-cal-shell-view-private.c
+++ b/src/modules/calendar/e-cal-shell-view-private.c
@@ -70,7 +70,7 @@ static void
 cal_shell_view_popup_event_cb (EShellView *shell_view,
                                GdkEvent *button_event)
 {
-       GList *list;
+       GSList *selected;
        ECalendarView *view;
        ECalShellViewPrivate *priv;
        const gchar *widget_path;
@@ -80,9 +80,9 @@ cal_shell_view_popup_event_cb (EShellView *shell_view,
 
        view = e_cal_shell_content_get_current_calendar_view (priv->cal_shell_content);
 
-       list = e_calendar_view_get_selected_events (view);
-       n_selected = g_list_length (list);
-       g_list_free (list);
+       selected = e_calendar_view_get_selected_events (view);
+       n_selected = g_slist_length (selected);
+       g_slist_free_full (selected, e_calendar_view_selection_data_free);
 
        if (n_selected <= 0)
                widget_path = "/calendar-empty-popup";
diff --git a/src/modules/calendar/e-cal-shell-view.c b/src/modules/calendar/e-cal-shell-view.c
index 5b5f3b6b09..e273d52994 100644
--- a/src/modules/calendar/e-cal-shell-view.c
+++ b/src/modules/calendar/e-cal-shell-view.c
@@ -230,7 +230,7 @@ cal_shell_view_execute_search (EShellView *shell_view)
                view_kind = e_cal_shell_content_get_current_view_id (cal_shell_content);
 
                /* Ensure the date navigator is visible. */
-               gtk_widget_set_visible (GTK_WIDGET (calendar), view_kind != E_CAL_VIEW_KIND_LIST);
+               gtk_widget_set_visible (GTK_WIDGET (calendar), view_kind != E_CAL_VIEW_KIND_LIST && view_kind 
!= E_CAL_VIEW_KIND_YEAR);
                e_cal_shell_content_get_current_range (cal_shell_content, &start_range, &end_range);
                end_range = time_day_end (end_range) - 1;
        }
@@ -651,6 +651,7 @@ e_cal_shell_view_class_init (ECalShellViewClass *class)
        g_type_ensure (GAL_TYPE_VIEW_CALENDAR_WORK_WEEK);
        g_type_ensure (GAL_TYPE_VIEW_CALENDAR_WEEK);
        g_type_ensure (GAL_TYPE_VIEW_CALENDAR_MONTH);
+       g_type_ensure (GAL_TYPE_VIEW_CALENDAR_YEAR);
        g_type_ensure (GAL_TYPE_VIEW_ETABLE);
 
        e_calendar_a11y_init ();
diff --git a/src/shell/main.c b/src/shell/main.c
index d0e625a61e..ac7b2a3a9e 100644
--- a/src/shell/main.c
+++ b/src/shell/main.c
@@ -582,6 +582,7 @@ main (gint argc,
                settings = e_util_ref_settings ("org.gnome.evolution.calendar");
                g_settings_set_boolean (settings, "show-memo-preview", FALSE);
                g_settings_set_boolean (settings, "show-task-preview", FALSE);
+               g_settings_set_boolean (settings, "year-show-preview", FALSE);
                g_object_unref (settings);
        }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]