calendar: implement Events List
Signed-off-by: David Zeuthen <davidz@redhat.com>
This commit is contained in:
parent
1ca1a2712d
commit
b4c038c036
@ -63,6 +63,7 @@ GJS_MIN_VERSION=0.7
|
|||||||
MUTTER_MIN_VERSION=2.91.0
|
MUTTER_MIN_VERSION=2.91.0
|
||||||
GTK_MIN_VERSION=2.91.0
|
GTK_MIN_VERSION=2.91.0
|
||||||
GIO_MIN_VERSION=2.25.9
|
GIO_MIN_VERSION=2.25.9
|
||||||
|
LIBICAL_MIN_VERSION=0.43
|
||||||
|
|
||||||
# Collect more than 20 libraries for a prize!
|
# Collect more than 20 libraries for a prize!
|
||||||
PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION
|
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
|
gtk+-3.0 >= $GTK_MIN_VERSION
|
||||||
mutter-plugins >= $MUTTER_MIN_VERSION
|
mutter-plugins >= $MUTTER_MIN_VERSION
|
||||||
gjs-gi-1.0 >= $GJS_MIN_VERSION
|
gjs-gi-1.0 >= $GJS_MIN_VERSION
|
||||||
|
libical >= $LIBICAL_MIN_VERSION
|
||||||
libgnome-menu $recorder_modules gconf-2.0
|
libgnome-menu $recorder_modules gconf-2.0
|
||||||
gdk-x11-3.0
|
gdk-x11-3.0
|
||||||
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
|
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
|
||||||
|
@ -843,6 +843,10 @@ StTooltip {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.calendar-day-base:active {
|
||||||
|
background: #666;
|
||||||
|
}
|
||||||
|
|
||||||
.calendar-day-heading {
|
.calendar-day-heading {
|
||||||
color: #666666;
|
color: #666666;
|
||||||
}
|
}
|
||||||
@ -876,6 +880,60 @@ StTooltip {
|
|||||||
color: #333333;
|
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 */
|
||||||
#message-tray {
|
#message-tray {
|
||||||
background-gradient-direction: vertical;
|
background-gradient-direction: vertical;
|
||||||
|
@ -4,6 +4,7 @@ const Clutter = imports.gi.Clutter;
|
|||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
const Signals = imports.signals;
|
||||||
const Pango = imports.gi.Pango;
|
const Pango = imports.gi.Pango;
|
||||||
const Gettext_gtk20 = imports.gettext.domain('gtk20');
|
const Gettext_gtk20 = imports.gettext.domain('gtk20');
|
||||||
const Gettext = imports.gettext.domain('gnome-shell');
|
const Gettext = imports.gettext.domain('gnome-shell');
|
||||||
@ -263,6 +264,12 @@ Calendar.prototype = {
|
|||||||
this._update();
|
this._update();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearButtonsState: function() {
|
||||||
|
for (let i = 0; i < this._dayButtons.length; i++) {
|
||||||
|
this._dayButtons[i].remove_style_pseudo_class('active');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
_update: function() {
|
_update: function() {
|
||||||
this._dateLabel.text = this.date.toLocaleFormat(this._headerFormat);
|
this._dateLabel.text = this.date.toLocaleFormat(this._headerFormat);
|
||||||
|
|
||||||
@ -282,8 +289,21 @@ Calendar.prototype = {
|
|||||||
let now = new Date();
|
let now = new Date();
|
||||||
|
|
||||||
let row = 2;
|
let row = 2;
|
||||||
|
let dayButtons = [];
|
||||||
|
this._dayButtons = dayButtons;
|
||||||
while (true) {
|
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;
|
let style_class;
|
||||||
|
|
||||||
style_class = 'calendar-day-base calendar-day';
|
style_class = 'calendar-day-base calendar-day';
|
||||||
@ -297,10 +317,10 @@ Calendar.prototype = {
|
|||||||
else if (iter.getMonth() != this.date.getMonth())
|
else if (iter.getMonth() != this.date.getMonth())
|
||||||
style_class += ' calendar-other-month-day';
|
style_class += ' calendar-other-month-day';
|
||||||
|
|
||||||
label.style_class = style_class;
|
button.style_class = style_class;
|
||||||
|
|
||||||
let offsetCols = this._useWeekdate ? 1 : 0;
|
let offsetCols = this._useWeekdate ? 1 : 0;
|
||||||
this.actor.add(label,
|
this.actor.add(button,
|
||||||
{ row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 });
|
{ row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 });
|
||||||
|
|
||||||
if (this._useWeekdate && iter.getDay() == 4) {
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
@ -67,6 +67,15 @@ DateMenuButton.prototype = {
|
|||||||
this._calendar = new Calendar.Calendar();
|
this._calendar = new Calendar.Calendar();
|
||||||
this.menu._box.add(this._calendar.actor);
|
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();
|
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||||
this.menu.addMenuItem(item);
|
this.menu.addMenuItem(item);
|
||||||
|
|
||||||
@ -89,7 +98,15 @@ DateMenuButton.prototype = {
|
|||||||
hbox = new St.BoxLayout();
|
hbox = new St.BoxLayout();
|
||||||
hbox.add(orig_menu_box);
|
hbox.add(orig_menu_box);
|
||||||
hbox.add(this._vertSep);
|
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._boxPointer.bin.set_child(hbox);
|
||||||
this.menu._box = hbox;
|
this.menu._box = hbox;
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include <X11/extensions/Xfixes.h>
|
#include <X11/extensions/Xfixes.h>
|
||||||
#include <gjs/gjs.h>
|
#include <gjs/gjs.h>
|
||||||
#include <canberra.h>
|
#include <canberra.h>
|
||||||
|
#include <libical/ical.h>
|
||||||
#ifdef HAVE_SYS_RESOURCE_H
|
#ifdef HAVE_SYS_RESOURCE_H
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#endif
|
#endif
|
||||||
@ -446,6 +447,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:
|
* shell_global_set_cursor:
|
||||||
* @global: A #ShellGlobal
|
* @global: A #ShellGlobal
|
||||||
|
@ -101,6 +101,7 @@ void shell_global_get_pointer (ShellGlobal *global,
|
|||||||
ClutterModifierType *mods);
|
ClutterModifierType *mods);
|
||||||
|
|
||||||
GSettings *shell_global_get_settings (ShellGlobal *global);
|
GSettings *shell_global_get_settings (ShellGlobal *global);
|
||||||
|
char* shell_global_icalcomponent_to_str (long icalcomp);
|
||||||
|
|
||||||
ClutterModifierType shell_get_event_state (ClutterEvent *event);
|
ClutterModifierType shell_get_event_state (ClutterEvent *event);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user