[gnome-shell] Add a calendar pop-down to the clock
- From: Owen Taylor <otaylor src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gnome-shell] Add a calendar pop-down to the clock
- Date: Thu, 1 Oct 2009 19:05:49 +0000 (UTC)
commit 04e28cd7c4f70d4e404bb1ab78c9c7a70c181246
Author: Owen W. Taylor <otaylor fishsoup net>
Date: Wed Sep 30 10:02:08 2009 -0400
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
data/theme/gnome-shell.css | 57 +++++++++++++++
js/ui/calendar.js | 154 +++++++++++++++++++++++++++++++++++++++++
js/ui/panel.js | 68 ++++++++++++++++++-
tests/interactive/calendar.js | 31 ++++++++
4 files changed, 309 insertions(+), 1 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index bb184c9..ae59609 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -61,6 +61,19 @@ StScrollBar StButton#vhandle:hover
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 */
#LookingGlassDialog
@@ -95,3 +108,47 @@ StScrollBar StButton#vhandle:hover
padding: 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;
+}
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
new file mode 100644
index 0000000..633af04
--- /dev/null
+++ b/js/ui/calendar.js
@@ -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: "<", 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: ">", 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++;
+ }
+ }
+ }
+};
diff --git a/js/ui/panel.js b/js/ui/panel.js
index 2fbc1d8..92a1bf8 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -7,12 +7,14 @@ const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Meta = imports.gi.Meta;
const Shell = imports.gi.Shell;
+const St = imports.gi.St;
const Tweener = imports.ui.tweener;
const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const Button = imports.ui.button;
+const Calendar = imports.ui.calendar;
const Main = imports.ui.main;
const PANEL_HEIGHT = 26;
@@ -313,10 +315,17 @@ Panel.prototype = {
/* 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,
color: PANEL_FOREGROUND_COLOR,
text: "" });
- this._centerBox.append(this._clock, Big.BoxPackFlags.NONE);
+ clockButton.add_actor(this._clock);
+
+ this._calendarPopup = null;
/* right */
@@ -454,6 +463,16 @@ Panel.prototype = {
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() {
if (!this._hotCornerEntered) {
this._hotCornerEntered = true;
@@ -485,3 +504,50 @@ Panel.prototype = {
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
+ });
+ }
+};
diff --git a/tests/interactive/calendar.js b/tests/interactive/calendar.js
new file mode 100644
index 0000000..9336c95
--- /dev/null
+++ b/tests/interactive/calendar.js
@@ -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();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]