[evolution] Calendar: Implement 'Year View'
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution] Calendar: Implement 'Year View'
- Date: Fri, 25 Mar 2022 12:50:47 +0000 (UTC)
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="<Control>k"/>
<GalView id="Month_View" _title="_Month View" filename="Month_View.galview"
type="month_view" accelerator="<Control>m"/>
+ <GalView id="Year_View" _title="_Year View" filename="Year_View.galview"
+ type="year_view" accelerator="<Control>i"/>
<GalView id="List_View" _title="_List View" filename="List_View.galview"
type="etable" accelerator="<Control>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 (®istry);
+ 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 (®istry);
+
+ 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]