2799327c84
Use GSettings for all Shell configuration. GConf is kept to read configuration from external programs (Metacity, Nautilus and Magnifier), but ShellGConf is removed because it's mostly useless for the few calls we still have. Also get rid of unused GConf code in ShellAppSystem. A basic GConf schema is still used to override Metacity defaults and configure Magnifier in a system-wide fashion. GConf is also used as GSettings backend via the GSETTINGS_BACKEND environment variable. All of this will be removed when these programs have been ported to GSettings and able to use dconf. GLib 2.25.9 is required. Schemas are converted to the new XML format, and compiled at build time in data/ so that the Shell can be run from the source tree. This also requires setting the GSETTINGS_SCHEMA_DIR environment variable both when running installed or from source tree, in src/gnome-shell.in and src/gnome-shell-clock-preferences.in. https://bugzilla.gnome.org/show_bug.cgi?id=617917
260 lines
9.5 KiB
JavaScript
260 lines
9.5 KiB
JavaScript
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const Gio = imports.gi.Gio;
|
|
const Lang = imports.lang;
|
|
const St = imports.gi.St;
|
|
const Pango = imports.gi.Pango;
|
|
const Shell = imports.gi.Shell;
|
|
const Gettext_gtk20 = imports.gettext.domain('gtk20');
|
|
|
|
const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
|
|
const MSECS_IN_WEEK = MSECS_IN_DAY * 7;
|
|
const WEEKDATE_HEADER_WIDTH_DIGITS = 3;
|
|
const SHOW_WEEKDATE_KEY = 'show-weekdate';
|
|
|
|
function _sameDay(dateA, dateB) {
|
|
return (dateA.getDate() == dateB.getDate() &&
|
|
dateA.getMonth() == dateB.getMonth() &&
|
|
dateA.getYear() == dateB.getYear());
|
|
}
|
|
|
|
function _getCalendarWeekForDate(date) {
|
|
let startOfYear = new Date(date.getFullYear(), 0, 1);
|
|
let sday = startOfYear.getDay();
|
|
let offset = 4 + (7 * Math.floor(sday * (1/5))) - sday;
|
|
let firstThursday = new Date(date.getFullYear(), 0, 1 + offset);
|
|
let weekOfYear = Math.ceil((date.getTime() - firstThursday.getTime()) / MSECS_IN_WEEK);
|
|
return weekOfYear;
|
|
}
|
|
|
|
function _getDigitWidth(actor){
|
|
let context = actor.get_pango_context();
|
|
let themeNode = actor.get_theme_node();
|
|
let font = themeNode.get_font();
|
|
let metrics = context.get_metrics(font, context.get_language());
|
|
let width = metrics.get_approximate_digit_width();
|
|
return width;
|
|
}
|
|
|
|
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;
|
|
this._weekdate = NaN;
|
|
this._digitWidth = NaN;
|
|
this._settings = new Gio.Settings({ schema: 'org.gnome.shell.calendar' });
|
|
|
|
this._settings.connect('changed::' + SHOW_WEEKDATE_KEY, Lang.bind(this, this._onSettingsChange));
|
|
this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY);
|
|
|
|
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));
|
|
|
|
this._buildHeader ();
|
|
this._update();
|
|
},
|
|
|
|
// Sets the calendar to show a specific date
|
|
setDate: function(date) {
|
|
if (!_sameDay(date, this.date)) {
|
|
this.date = date;
|
|
this._update();
|
|
}
|
|
},
|
|
|
|
_buildHeader: function() {
|
|
let offsetCols = this._useWeekdate ? 1 : 0;
|
|
this.actor.destroy_children();
|
|
|
|
// Top line of the calendar '<| September 2009 |>'
|
|
this._topBox = new St.BoxLayout();
|
|
this.actor.add(this._topBox,
|
|
{ row: 0, col: 0, col_span: offsetCols + 7 });
|
|
|
|
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChange));
|
|
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);
|
|
|
|
if (this._useWeekdate) {
|
|
this._weekdateHeader = new St.Label();
|
|
this.actor.add(this._weekdateHeader,
|
|
{ row: 1,
|
|
col: 0,
|
|
x_fill: false, x_align: St.Align.MIDDLE });
|
|
this._setWeekdateHeaderWidth();
|
|
} else {
|
|
this._weekdateHeader = null;
|
|
}
|
|
|
|
for (let i = 0; i < 7; i++) {
|
|
this.actor.add(new St.Label({ text: iter.toLocaleFormat('%a') }),
|
|
{ row: 1,
|
|
col: offsetCols + (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;
|
|
},
|
|
|
|
_onStyleChange: function(actor, event) {
|
|
// width of a digit in pango units
|
|
this._digitWidth = _getDigitWidth(this.actor) / Pango.SCALE;
|
|
this._setWeekdateHeaderWidth();
|
|
},
|
|
|
|
_setWeekdateHeaderWidth: function() {
|
|
if (this.digitWidth != NaN && this._useWeekdate && this._weekdateHeader) {
|
|
this._weekdateHeader.set_width (this._digitWidth * WEEKDATE_HEADER_WIDTH_DIGITS);
|
|
}
|
|
},
|
|
|
|
_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();
|
|
},
|
|
|
|
_onSettingsChange: function() {
|
|
this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY);
|
|
this._buildHeader();
|
|
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';
|
|
|
|
let offsetCols = this._useWeekdate ? 1 : 0;
|
|
this.actor.add(label,
|
|
{ row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7,
|
|
x_fill: false, x_align: St.Align.END });
|
|
|
|
if (this._useWeekdate && iter.getDay() == 4) {
|
|
let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(),
|
|
style_class: 'calendar-day calendar-calendarweek'});
|
|
this.actor.add(label,
|
|
{ row: row, col: 0,
|
|
x_fill: false, x_align: St.Align.MIDDLE });
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
};
|