[gnome-shell/datetime: 23/23] calendar: implement Events List



commit b4c038c036cfc3926a249d1aeca6be5c2096d4c0
Author: Maxim Ermilov <zaspire rambler ru>
Date:   Wed Oct 20 14:30:35 2010 -0400

    calendar: implement Events List
    
    Signed-off-by: David Zeuthen <davidz redhat com>

 configure.ac               |    2 +
 data/theme/gnome-shell.css |   58 ++++++++++++
 js/ui/calendar.js          |  221 +++++++++++++++++++++++++++++++++++++++++++-
 js/ui/dateMenu.js          |   19 ++++-
 src/shell-global.c         |   12 +++
 src/shell-global.h         |    1 +
 6 files changed, 309 insertions(+), 4 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index a9533aa..6da1d19 100644
--- a/configure.ac
+++ b/configure.ac
@@ -63,6 +63,7 @@ GJS_MIN_VERSION=0.7
 MUTTER_MIN_VERSION=2.91.0
 GTK_MIN_VERSION=2.91.0
 GIO_MIN_VERSION=2.25.9
+LIBICAL_MIN_VERSION=0.43
 
 # Collect more than 20 libraries for a prize!
 PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION
@@ -70,6 +71,7 @@ PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION
                                  gtk+-3.0 >= $GTK_MIN_VERSION
                                  mutter-plugins >= $MUTTER_MIN_VERSION
                                  gjs-gi-1.0 >= $GJS_MIN_VERSION
+                                 libical >= $LIBICAL_MIN_VERSION
 				 libgnome-menu $recorder_modules gconf-2.0
                                  gdk-x11-3.0
 				 clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index e8b26da..1348370 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -843,6 +843,10 @@ StTooltip {
     text-align: center;
 }
 
+.calendar-day-base:active {
+    background: #666;
+}
+
 .calendar-day-heading {
     color: #666666;
 }
@@ -876,6 +880,60 @@ StTooltip {
     color: #333333;
 }
 
+.events-header {
+    height: 40px;
+}
+
+.events-day-header {
+    padding-left: 20px;
+    padding-right: 40px;
+    font-weight: bold;
+    font-size: 14px;
+    color: rgba(153, 153, 153, 1.0);
+}
+
+.events-day-time {
+    font-size: 14px;
+    font-weight: bold;
+    color: #fff;
+}
+
+.events-day-task {
+    font-weight: bold;
+    font-size: 14px;
+    color: rgba(153, 153, 153, 1.0);
+}
+
+.events-day-name-box {
+    width: 50px;
+}
+
+.events-time-box {
+    width: 70px;
+}
+
+.events-event-box {
+    width: 300px;
+}
+
+.events-no-events {
+    font-weight: bold;
+    padding-left: 40px;
+    padding-right: 40px;
+    font-size: 14px;
+    color: rgba(153, 153, 153, 1.0);
+}
+
+.open-calendar {
+    padding-bottom: 12px;
+    padding-left: 12px;
+    height: 40px;
+}
+
+.open-calendar:hover {
+    background: #666;
+}
+
 /* Message Tray */
 #message-tray {
     background-gradient-direction: vertical;
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index 1ead975..7aeb5ed 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -4,6 +4,7 @@ const Clutter = imports.gi.Clutter;
 const Gio = imports.gi.Gio;
 const Lang = imports.lang;
 const St = imports.gi.St;
+const Signals = imports.signals;
 const Pango = imports.gi.Pango;
 const Gettext_gtk20 = imports.gettext.domain('gtk20');
 const Gettext = imports.gettext.domain('gnome-shell');
@@ -263,6 +264,12 @@ Calendar.prototype = {
         this._update();
     },
 
+    clearButtonsState: function() {
+        for (let i = 0; i < this._dayButtons.length; i++) {
+            this._dayButtons[i].remove_style_pseudo_class('active');
+        }
+    },
+
     _update: function() {
         this._dateLabel.text = this.date.toLocaleFormat(this._headerFormat);
 
@@ -282,8 +289,21 @@ Calendar.prototype = {
         let now = new Date();
 
         let row = 2;
+        let dayButtons = [];
+        this._dayButtons = dayButtons;
         while (true) {
-            let label = new St.Label({ text: iter.getDate().toString() });
+            let button = new St.Button({ label: iter.getDate().toString() });
+
+            dayButtons.push(button);
+
+            let iterStr = iter.toUTCString();
+            button.connect('clicked', Lang.bind(this, function() {
+                this.emit('activate', new Date(iterStr));
+                for (let i = 0; i < dayButtons.length; i++) {
+                    dayButtons[i].remove_style_pseudo_class('active');
+                }
+                button.add_style_pseudo_class('active');
+            }));
             let style_class;
 
             style_class = 'calendar-day-base calendar-day';
@@ -297,10 +317,10 @@ Calendar.prototype = {
             else if (iter.getMonth() != this.date.getMonth())
                 style_class += ' calendar-other-month-day';
 
-            label.style_class = style_class;
+            button.style_class = style_class;
 
             let offsetCols = this._useWeekdate ? 1 : 0;
-            this.actor.add(label,
+            this.actor.add(button,
                            { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 });
 
             if (this._useWeekdate && iter.getDay() == 4) {
@@ -320,3 +340,198 @@ Calendar.prototype = {
         }
     }
 };
+
+Signals.addSignalMethods(Calendar.prototype);
+
+function EvolutionEventsSource() {
+    this._init();
+}
+
+EvolutionEventsSource.prototype = {
+    _init: function() {
+        try {
+            this.EDataServer = imports.gi.EDataServer;
+            this.ECalendar = imports.gi.ECalendar;
+        } catch (e) {log(e);}
+    },
+
+    _ISODateString: function(d) {
+        function pad(n) {
+            return n < 10 ? '0' + n : n;
+        }
+        return '' + d.getUTCFullYear()// + '-'
+               + pad(d.getUTCMonth() + 1)// + '-'
+               + pad(d.getUTCDate()) + 'T'
+               + pad(d.getUTCHours())// + ':'
+               + pad(d.getUTCMinutes())// + ':'
+               + pad(d.getUTCSeconds()) + 'Z'
+    },
+
+    _getStartTime: function(str) {
+        let start = /DTSTART:\d{8}T\d{6}/.exec(str);
+        if (!start)
+            throw new Error("Bad data");
+        start = start[0].substr(8);
+        let year = start.substr(0, 4), month = start.substr(4, 2), day = start.substr(6, 2);
+        let hour = start.substr(9, 2), minute = start.substr(12, 2);
+        return new Date(year, month - 1, day, hour, minute, 0, 0);
+    },
+
+    _getSummary: function(str) {
+        let match = /\nSUMMARY:.*(?=\r\n)/.exec(str);
+        if (!match)
+            return "";
+        return match[0].substr(9);
+    },
+
+    getForInterval: function(begin, end, callback) {
+        if (!this.EDataServer) {
+            callback([]);
+            return;
+        }
+        let res = [];
+        let wait = 0;
+        let list = this.EDataServer.SourceList.new_for_gconf_default("/apps/evolution/calendar/sources");
+
+        let groups = list.peek_groups();
+
+        let query = '(occur-in-time-range? (make-time "' + this._ISODateString(begin) + '") (make-time "' + this._ISODateString(end) + '"))';
+
+        function decrimentWait() {
+            wait--;
+            if (wait == 0) {
+                res.sort(function (a, b) {
+                    if (a.time == b.time)
+                        return 0;
+                    if (a.time > b.time)
+                        return 1;
+                    return -1;
+                });
+                callback(res);
+            }
+        }
+
+        for (let i = 0; i < groups.length; i++) {
+            let sources = groups[i].peek_sources();
+            for (let k = 0; k < sources.length; k++) {
+                let cal = this.ECalendar.Cal.new(sources[k], this.ECalendar.CalSourceType.EVENT);
+                if (!cal)
+                    continue;
+                let calOpenedExId = cal.connect('cal-opened-ex', Lang.bind(this, function(s, error) {
+                    if (error) {
+                        decrimentWait;
+                        return;
+                    }
+                    let [success, view] = cal.get_query(query);
+                    let viewObjectsAddedId = view.connect('objects-added', Lang.bind(this, function(o, list) {
+                        for (let j = 0; j < list.length; j++) {
+                            let event = global.icalcomponent_to_str(list[j]);
+                            let start = this._getStartTime(event);
+                            res.push({ time: start, title: this._getSummary(event) });
+                        }
+                    }));
+                    let viewCompleteId = view.connect('view-complete', function() {
+                        cal.disconnect(calOpenedExId);
+                        view.disconnect(viewObjectsAddedId);
+                        view.disconnect(viewCompleteId);
+                        decrimentWait();
+                    });
+                    view.start();
+                }));
+                cal.open_async(false);
+                wait++;
+            }
+        }
+
+        if (wait == 0) {
+            callback([]);
+        }
+    }
+};
+
+function EventsList() {
+    this._init();
+}
+
+EventsList.prototype = {
+    _init: function() {
+        this.actor = new St.BoxLayout({ vertical: true });
+        this.evolutionTasks = new EvolutionEventsSource();
+    },
+
+    _addPeriod: function(header, begin, end, isDay) {
+        this.actor.add(new St.Label({ style_class: 'events-day-header',
+                                      text: header }));
+        let box = new St.BoxLayout();
+        let dayNameBox = new St.BoxLayout({ vertical: true, style_class: 'events-day-name-box' });
+        let timeBox = new St.BoxLayout({ vertical: true, style_class: 'events-time-box' });
+        let eventTitleBox = new St.BoxLayout({ vertical: true, style_class: 'events-event-box' });
+        box.add(dayNameBox);
+        box.add(timeBox);
+        box.add(eventTitleBox);
+        this.actor.add(box);
+
+        this.evolutionTasks.getForInterval(begin, end, Lang.bind(this, function(tasks) {
+            if (!tasks.length) {
+                eventTitleBox.add(new St.Label({ style_class: 'events-no-events', text: _("No events") }));
+                return;
+            }
+
+            for (let i = 0; i < tasks.length; i++) {
+                let time = tasks[i].time.getHours() + ':';
+                if (tasks[i].time.getMinutes() < 10)
+                    time += '0';
+                time += tasks[i].time.getMinutes();
+                timeBox.add(new St.Label({ style_class: 'events-day-time', text: time }));
+                eventTitleBox.add(new St.Label({ style_class: 'events-day-task', text: tasks[i].title }), { expand: false });
+                if (!isDay)
+                    continue;
+                dayNameBox.add(new St.Label({ text: tasks[i].time.toLocaleFormat("%a") }));
+            }
+        }));
+    },
+
+    showDay: function(day) {
+        this.actor.destroy_children();
+
+        this.actor.add(new St.Bin({ style_class: 'events-header' }));
+
+        let dayBegin = new Date(day.getTime());
+        let dayEnd = new Date(day.getTime());
+        dayBegin.setHours(0);
+        dayBegin.setMinutes(1);
+        dayEnd.setHours(23);
+        dayEnd.setMinutes(59);
+        this._addPeriod(day.toLocaleDateString(), dayBegin, dayEnd, false);
+    },
+
+    update: function() {
+        this.actor.destroy_children();
+
+        this.actor.add(new St.Bin({ style_class: 'events-header' }));
+
+        let dayBegin = new Date();
+        let dayEnd = new Date();
+        dayBegin.setHours(0);
+        dayBegin.setMinutes(1);
+        dayEnd.setHours(23);
+        dayEnd.setMinutes(59);
+        this._addPeriod(_("Today"), dayBegin, dayEnd, false);
+
+        dayBegin.setDate(dayBegin.getDate() + 1);
+        dayEnd.setDate(dayEnd.getDate() + 1);
+        this._addPeriod(_("Tomorrow"), dayBegin, dayEnd, false);
+
+        if (dayEnd.getDay() == 6 || dayEnd.getDay() == 0) {
+            dayBegin.setDate(dayEnd.getDate() + 1);
+            dayEnd.setDate(dayBegin.getDate() + 6 - dayBegin.getDay());
+
+            this._addPeriod(_("Next week"), dayBegin, dayEnd, true);
+            return;
+        }
+        let d = 6 - dayEnd.getDay() - 1;
+        dayBegin.setDate(dayBegin.getDate() + 1);
+        dayEnd.setDate(dayEnd.getDate() + 1 + d);
+        this._addPeriod(_("This week"), dayBegin, dayEnd, true);
+    }
+};
diff --git a/js/ui/dateMenu.js b/js/ui/dateMenu.js
index 77d7bba..0e6a644 100644
--- a/js/ui/dateMenu.js
+++ b/js/ui/dateMenu.js
@@ -67,6 +67,15 @@ DateMenuButton.prototype = {
         this._calendar = new Calendar.Calendar();
         this.menu._box.add(this._calendar.actor);
 
+        this._taskList = new Calendar.EventsList();
+        this.menu.connect('opening', Lang.bind(this, function() {
+            this._calendar.clearButtonsState();
+            this._taskList.update();
+        }));
+        this._calendar.connect('activate', Lang.bind(this, function(obj, day) {
+            this._taskList.showDay(day);
+        }));
+
         item = new PopupMenu.PopupSeparatorMenuItem();
         this.menu.addMenuItem(item);
 
@@ -89,7 +98,15 @@ DateMenuButton.prototype = {
         hbox = new St.BoxLayout();
         hbox.add(orig_menu_box);
         hbox.add(this._vertSep);
-        hbox.add(new St.Label({text: "foo0"}));
+
+        let calendarButton = new St.Button({ label: _("Open Calendar"),
+                                             style_class: 'open-calendar',
+                                             x_align: St.Align.START });
+        let box = new St.BoxLayout({ vertical: true });
+        box.add(this._taskList.actor, { expand: true, x_fill: true, y_fill: true });
+        box.add(calendarButton);
+
+        hbox.add(box);
         this.menu._boxPointer.bin.set_child(hbox);
         this.menu._box = hbox;
 
diff --git a/src/shell-global.c b/src/shell-global.c
index 1e42fc9..1a2018d 100644
--- a/src/shell-global.c
+++ b/src/shell-global.c
@@ -25,6 +25,7 @@
 #include <X11/extensions/Xfixes.h>
 #include <gjs/gjs.h>
 #include <canberra.h>
+#include <libical/ical.h>
 #ifdef HAVE_SYS_RESOURCE_H
 #include <sys/resource.h>
 #endif
@@ -447,6 +448,17 @@ shell_global_set_stage_input_mode (ShellGlobal         *global,
 }
 
 /**
+ * shell_global_icalcomponent_to_str:
+ *
+ * Wrap icalcomponent_as_ical_string_r
+ */
+char*
+shell_global_icalcomponent_to_str (long icalcomp)
+{
+  return icalcomponent_as_ical_string_r ((icalcomponent*)icalcomp);
+}
+
+/**
  * shell_global_set_cursor:
  * @global: A #ShellGlobal
  * @type: the type of the cursor
diff --git a/src/shell-global.h b/src/shell-global.h
index 5cf5e13..368b79a 100644
--- a/src/shell-global.h
+++ b/src/shell-global.h
@@ -101,6 +101,7 @@ void shell_global_get_pointer (ShellGlobal         *global,
                                ClutterModifierType *mods);
 
 GSettings *shell_global_get_settings (ShellGlobal *global);
+char* shell_global_icalcomponent_to_str (long icalcomp);
 
 ClutterModifierType shell_get_event_state (ClutterEvent *event);
 



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