/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const Clutter = imports.gi.Clutter; const Lang = imports.lang; const St = imports.gi.St; const Gettext_gtk20 = imports.gettext.domain('gtk20'); const MSECS_IN_DAY = 24 * 60 * 60 * 1000; function _sameDay(dateA, dateB) { return (dateA.getDate() == dateB.getDate() && dateA.getMonth() == dateB.getMonth() && dateA.getYear() == dateB.getYear()); } function Calendar() { this._init(); } Calendar.prototype = { _init: function() { // FIXME: This is actually the fallback method for GTK+ for the week start; // GTK+ by preference uses nl_langinfo (NL_TIME_FIRST_WEEKDAY). We probably // should add a C function so we can do the full handling. this._weekStart = NaN; let weekStartString = Gettext_gtk20.gettext('calendar:week_start:0'); if (weekStartString.indexOf('calendar:week_start:') == 0) { this._weekStart = parseInt(weekStartString.substring(20)); } if (isNaN(this._weekStart) || this._weekStart < 0 || this._weekStart > 6) { log('Translation of "calendar:week_start:0" in GTK+ is not correct'); this._weekStart = 0; } // Find the ordering for month/year in the calendar heading switch (Gettext_gtk20.gettext('calendar:MY')) { case 'calendar:MY': this._headerFormat = '%B %Y'; break; case 'calendar:YM': this._headerFormat = '%Y %B'; break; default: log('Translation of "calendar:MY" in GTK+ is not correct'); this._headerFormat = '%B %Y'; break; } // Start off with the current date this.date = new Date(); this.actor = new St.Table({ homogeneous: false, style_class: 'calendar', reactive: true }); this.actor.connect('scroll-event', Lang.bind(this, this._onScroll)); // Top line of the calendar '<| September 2009 |>' this._topBox = new St.BoxLayout(); this.actor.add(this._topBox, { row: 0, col: 0, col_span: 7 }); let [backlabel, forwardlabel] = ['<', '>']; if (St.Widget.get_default_direction () == St.TextDirection.RTL) { [backlabel, forwardlabel] = [forwardlabel, backlabel]; } let back = new St.Button({ label: backlabel, style_class: 'calendar-change-month' }); this._topBox.add(back); back.connect('clicked', Lang.bind(this, this._prevMonth)); this._dateLabel = new St.Label(); this._topBox.add(this._dateLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE }); let forward = new St.Button({ label: forwardlabel, style_class: 'calendar-change-month' }); this._topBox.add(forward); forward.connect('clicked', Lang.bind(this, this._nextMonth)); // We need to figure out the abbreviated localized names for the days of the week; // we do this by just getting the next 7 days starting from right now and then putting // them in the right cell in the table. It doesn't matter if we add them in order let iter = new Date(this.date); iter.setSeconds(0); // Leap second protection. Hah! iter.setHours(12); for (let i = 0; i < 7; i++) { this.actor.add(new St.Label({ text: iter.toLocaleFormat('%a') }), { row: 1, col: (7 + iter.getDay() - this._weekStart) % 7, x_fill: false, x_align: St.Align.END }); iter.setTime(iter.getTime() + MSECS_IN_DAY); } // All the children after this are days, and get removed when we update the calendar this._firstDayIndex = this.actor.get_children().length; this._update(); }, // Sets the calendar to show a specific date setDate: function(date) { if (!_sameDay(date, this.date)) { this.date = date; this._update(); } }, _onScroll : function(actor, event) { switch (event.get_scroll_direction()) { case Clutter.ScrollDirection.UP: case Clutter.ScrollDirection.LEFT: this._prevMonth(); break; case Clutter.ScrollDirection.DOWN: case Clutter.ScrollDirection.RIGHT: this._nextMonth(); break; } }, _prevMonth: function() { if (this.date.getMonth() == 0) { this.date.setMonth(11); this.date.setFullYear(this.date.getFullYear() - 1); } else { this.date.setMonth(this.date.getMonth() - 1); } this._update(); }, _nextMonth: function() { if (this.date.getMonth() == 11) { this.date.setMonth(0); this.date.setFullYear(this.date.getFullYear() + 1); } else { this.date.setMonth(this.date.getMonth() + 1); } this._update(); }, _update: function() { this._dateLabel.text = this.date.toLocaleFormat(this._headerFormat); // Remove everything but the topBox and the weekday labels let children = this.actor.get_children(); for (let i = this._firstDayIndex; i < children.length; i++) children[i].destroy(); // Start at the beginning of the week before the start of the month let iter = new Date(this.date); iter.setDate(1); iter.setSeconds(0); iter.setHours(12); iter.setTime(iter.getTime() - (iter.getDay() - this._weekStart) * MSECS_IN_DAY); let now = new Date(); let row = 2; while (true) { let label = new St.Label({ text: iter.getDate().toString() }); if (_sameDay(now, iter)) label.style_class = 'calendar-day calendar-today'; else if (iter.getMonth() != this.date.getMonth()) label.style_class = 'calendar-day calendar-other-month-day'; else label.style_class = 'calendar-day'; this.actor.add(label, { row: row, col: (7 + iter.getDay() - this._weekStart) % 7, x_fill: false, x_align: St.Align.END }); iter.setTime(iter.getTime() + MSECS_IN_DAY); if (iter.getDay() == this._weekStart) { // We stop on the first "first day of the week" after the month we are displaying if (iter.getMonth() > this.date.getMonth() || iter.getYear() > this.date.getYear()) break; row++; } } } };