Add a calendar pop-down to the clock

js/ui/calendar.js: Generic calendar widget
tests/interactive/calendar.js: Basic test of the calendar

js/ui/panel.js: Add a pop-down from the clock that shows a
  calendar widget. The pop-down is not menu-like to allow the user to
  interact with an application while looking at the calendar.
gnome-shell.css: Add theming for calendar, calendar popup, and for
  buttons on the panel

https://bugzilla.gnome.org/show_bug.cgi?id=596432
This commit is contained in:
Owen W. Taylor 2009-09-30 10:02:08 -04:00
parent 2cc41c6726
commit 04e28cd7c4
4 changed files with 309 additions and 1 deletions

View File

@ -61,6 +61,19 @@ StScrollBar StButton#vhandle:hover
border-image: url("scroll-vhandle.png") 5; border-image: url("scroll-vhandle.png") 5;
} }
/* Panel */
.panel-button {
padding: 4px 12px 3px;
border-radius: 5px;
font: 16px sans-serif;
color: white;
}
.panel-button:active, .panel-button:checked {
background-color: #314a6c;
}
/* LookingGlass */ /* LookingGlass */
#LookingGlassDialog #LookingGlassDialog
@ -95,3 +108,47 @@ StScrollBar StButton#vhandle:hover
padding: 4px; padding: 4px;
spacing: 4px; spacing: 4px;
} }
/* Calendar popup */
#calendarPopup {
border-radius: 5px;
background: rgba(0,0,0,0.9);
border: 1px solid rgba(128,128,128,0.45);
color: white;
padding: 10px;
}
.calendar {
spacing-rows: 5px;
spacing-columns: 3px;
}
.calendar-change-month {
padding: 2px;
}
.calendar-change-month:hover {
background: #314a6c;
border-radius: 5px;
}
.calendar-change-month:active {
background: #213050;
border-radius: 5px;
}
.calendar-day {
padding: 1px 2px;
}
.calendar-today {
font-weight: bold;
background: #ffffff;
color: black;
border-radius: 5px;
}
.calendar-other-month-day {
color: #cccccc;
}

154
js/ui/calendar.js Normal file
View File

@ -0,0 +1,154 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
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" });
// 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 back = new St.Button({ label: "&lt;", 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: "&gt;", 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!
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: 1.0 });
iter.setTime(iter.getTime() + MSECS_IN_DAY);
}
this._update();
},
// Sets the calendar to show a specific date
setDate: function(date) {
if (!_sameDay(date, this.date)) {
this.date = date;
this._update();
}
},
_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("%B %Y");
// Remove everything but the topBox and the weekday labels
let children = this.actor.get_children();
for (let i = 8; 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.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: 1.0 });
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++;
}
}
}
};

View File

@ -7,12 +7,14 @@ const Lang = imports.lang;
const Mainloop = imports.mainloop; const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta; const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell; const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Tweener = imports.ui.tweener; const Tweener = imports.ui.tweener;
const Signals = imports.signals; const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell'); const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext; const _ = Gettext.gettext;
const Button = imports.ui.button; const Button = imports.ui.button;
const Calendar = imports.ui.calendar;
const Main = imports.ui.main; const Main = imports.ui.main;
const PANEL_HEIGHT = 26; const PANEL_HEIGHT = 26;
@ -313,10 +315,17 @@ Panel.prototype = {
/* center */ /* center */
let clockButton = new St.Button({ style_class: "panel-button",
toggle_mode: true });
this._centerBox.append(clockButton, Big.BoxPackFlags.NONE);
clockButton.connect('clicked', Lang.bind(this, this._toggleCalendar));
this._clock = new Clutter.Text({ font_name: DEFAULT_FONT, this._clock = new Clutter.Text({ font_name: DEFAULT_FONT,
color: PANEL_FOREGROUND_COLOR, color: PANEL_FOREGROUND_COLOR,
text: "" }); text: "" });
this._centerBox.append(this._clock, Big.BoxPackFlags.NONE); clockButton.add_actor(this._clock);
this._calendarPopup = null;
/* right */ /* right */
@ -454,6 +463,16 @@ Panel.prototype = {
return false; return false;
}, },
_toggleCalendar: function(clockButton) {
if (clockButton.checked) {
if (this._calendarPopup == null)
this._calendarPopup = new CalendarPopup();
this._calendarPopup.show();
} else {
this._calendarPopup.hide();
}
},
_onHotCornerEntered : function() { _onHotCornerEntered : function() {
if (!this._hotCornerEntered) { if (!this._hotCornerEntered) {
this._hotCornerEntered = true; this._hotCornerEntered = true;
@ -485,3 +504,50 @@ Panel.prototype = {
return false; return false;
} }
}; };
function CalendarPopup() {
this._init();
}
CalendarPopup.prototype = {
_init: function() {
let panelActor = Main.panel.actor;
this.actor = new St.BoxLayout({ name: 'calendarPopup' });
this.calendar = new Calendar.Calendar();
this.actor.add(this.calendar.actor);
Main.chrome.actor.add_actor(this.actor);
Main.chrome.addInputRegionActor(this.actor);
this.actor.y = (panelActor.y + panelActor.height - this.actor.height);
},
show: function() {
let panelActor = Main.panel.actor;
// Reset the calendar to today's date
this.calendar.setDate(new Date());
this.actor.x = Math.round((panelActor.x + panelActor.width - this.actor.width) / 2);
this.actor.lower(panelActor);
this.actor.show();
Tweener.addTween(this.actor,
{ y: panelActor.y + panelActor.height,
time: 0.2,
transition: "easeOutQuad"
});
},
hide: function() {
let panelActor = Main.panel.actor;
Tweener.addTween(this.actor,
{ y: panelActor.y + panelActor.height - this.actor.height,
time: 0.2,
transition: "easeOutQuad",
onComplete: function() { this.actor.hide(); },
onCompleteScope: this
});
}
};

View File

@ -0,0 +1,31 @@
/* -*- 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 Calendar =imports.ui.calendar;
const UI = imports.testcommon.ui;
const Gettext_gtk20 = imports.gettext.domain('gtk20');
UI.init();
let stage = Clutter.Stage.get_default();
stage.width = stage.height = 400;
stage.show();
let vbox = new St.BoxLayout({ vertical: true,
width: stage.width,
height: stage.height,
style: 'padding: 10px; spacing: 10px; font: 15px sans-serif;' });
stage.add_actor(vbox);
let calendar = new Calendar.Calendar();
vbox.add(calendar.actor,
{ expand: true,
x_fill: false, x_align: St.Align.MIDDLE,
y_fill: false, y_align: St.Align.START });
stage.show();
Clutter.main();
stage.destroy();