gnome-shell-teatime/src/extension.js
2014-08-03 19:23:19 +02:00

406 lines
15 KiB
JavaScript

/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/* Olaf Leidinger <oleid@mescharet.de>
Thomas Liebetraut <thomas@tommie-lie.de>
*/
const Gdk = imports.gi.Gdk;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Mainloop = imports.mainloop; // timer
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Clutter = imports.gi.Clutter;
const Layout = imports.ui.layout;
const FileUtils = imports.misc.fileUtils;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Panel = imports.ui.panel;
const Gettext = imports.gettext;
const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Utils = Me.imports.utils;
const bUseGnome34Workarounds = imports.misc.extensionUtils.versionCheck( ["3.4"], imports.misc.config.PACKAGE_VERSION);
Utils.initTranslations();
const _ = Gettext.gettext;
const N_ = function(e) { return e; };
const TeaTimeFullscreenNotification = new Lang.Class({
Name: 'TeaTimeFullscreenNotification',
_init: function() {
// this spans the whole monitor and contains
// the actual layout, which it displays in
// the center of itself
this._bin = new St.Bin({ x_align: St.Align.MIDDLE, y_align: St.Align.MIDDLE});
if (typeof Layout.MonitorConstraint != 'undefined') {
// MonitorConstraint was introduced in gnome-3.6
this._monitorConstraint = new Layout.MonitorConstraint();
this._bin.add_constraint(this._monitorConstraint);
}
Main.uiGroup.add_actor(this._bin);
// a table imitating a vertical box layout to hold the texture and
// a label underneath it
this._layout = new St.Table();
this._bin.set_child(this._layout);
// find all the textures
let datadir = Me.dir.get_child("data");
this._textureFiles = [];
if (datadir.query_exists(null)) {
let enumerator = datadir.enumerate_children(Gio.FILE_ATTRIBUTE_STANDARD_NAME,
Gio.FileQueryInfoFlags.NONE,
null);
let info;
info = enumerator.next_file(null);
while ( info != null ) {
let filename = info.get_name();
if (filename.match(/^cup.*/)) {
this._textureFiles.push(datadir.get_child(filename).get_path());
}
info = enumerator.next_file(null);
}
}
this._textureFiles.sort();
this._texture = new Clutter.Texture({ reactive: true, keep_aspect_ratio: true });
this._texture.connect("button-release-event", Lang.bind(this, this.hide));
this._layout.add(this._texture, {row: 0, col: 0});
this._timeline = new Clutter.Timeline({ duration: 2000, repeat_count: -1, progress_mode: Clutter.AnimationMode.LINEAR });
this._timeline.connect("new-frame", Lang.bind(this, this._newFrame));
this._label = new St.Label({ text: _("Your tea is ready!"), style_class: "dash-label" });
this._layout.add(this._label, {row: 1, col: 0});
this._lightbox = new imports.ui.lightbox.Lightbox(Main.uiGroup); // Seems not to work on Gnome 3.10 { fadeInTime: 0.5, fadeOutTime: 0.5 }
this._lightbox.highlight(this._bin);
},
destroy: function() {
this.hide();
Main.popModal(this._bin);
this._bin.destroy();
this._lightbox.hide();
},
_newFrame: function(timeline, msecs, user) {
let progress = timeline.get_progress();
let idx = Math.round(progress * this._textureFiles.length) % this._textureFiles.length;
this._texture.set_from_file(this._textureFiles[idx]);
},
show: function() {
if (typeof Layout.MonitorConstraint != 'undefined') {
this._monitorConstraint.index = global.screen.get_current_monitor()
}
Main.pushModal(this._bin);
this._timeline.start();
this._lightbox.show();
this._bin.show_all();
},
hide: function() {
Main.popModal(this._bin);
this._bin.hide();
this._lightbox.hide();
this._timeline.stop();
}
})
const PopupTeaMenuItem = new Lang.Class({
Name: 'PopupTeaMenuItem',
Extends: PopupMenu.PopupBaseMenuItem,
_init: function (sTeaname, nBrewtime, params) {
this.parent(params);
this.tealabel = new St.Label({ text: sTeaname });
this.timelabel = new St.Label({ text: Utils.formatTime(nBrewtime) });
if (this.actor instanceof St.BoxLayout) {
// will be used for gnome-shell 3.10 and possibly above where this.actor is BoxLayout
this.actor.add(this.tealabel, { expand: true });
this.actor.add(this.timelabel);
} else {
this.addActor(this.tealabel, {expand: true });
this.addActor(this.timelabel, {expand: false });
}
}
});
const TeaTime = new Lang.Class({
Name : 'TeaTime',
Extends : PanelMenu.Button,
_init : function() {
this.parent(0.0, "TeaTime");
this._settings = Utils.getSettings();
if ( typeof St.IconType == 'undefined' ) {
// St.IconType was removed in May 2012 by commit c21b1e5fe00a99936f67d358a931f3083304ebc8
// but it's needed for Gnome 3.4
this._logo = new St.Icon({ icon_name: 'utilities-teatime',
style_class : 'system-status-icon',
icon_size: 20 });
} else {
this._logo = new St.Icon({ icon_name: 'utilities-teatime',
icon_type: St.IconType.FULLCOLOR,
style_class : 'system-status-icon',
icon_size: 20 });
}
// set timer widget
this._textualTimer = new St.Label({ text: "" });
this._graphicalTimer = new St.DrawingArea({
reactive : true
});
this._graphicalTimer.set_width(20);
this._graphicalTimer.set_height(20);
this._graphicalTimer.connect('repaint', Lang.bind(this, this._drawTimer));
this.actor.add_actor(this._logo);
this._idleTimeout = null;
this._createMenu();
},
_createMenu : function() {
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._settings.connect("changed::" + Utils.TEATIME_STEEP_TIMES_KEY,
Lang.bind(this, this._updateTeaList));
this._settings.connect("changed::" + Utils.TEATIME_GRAPHICAL_COUNTDOWN_KEY,
Lang.bind(this, this._updateCountdownType));
this.teaItemCont = new PopupMenu.PopupMenuSection();
/*******************/
// maybe one day the PopupImageMenuItem works^^
let head = new PopupMenu.PopupMenuSection();
let item = new PopupMenu.PopupMenuItem(_("Show settings")); //, 'gtk-preferences');
// item._icon.icon_size = 15;
item.connect('activate', Lang.bind(this, this._showPreferences));
head.addMenuItem(item);
/*******************/
let bottom = new PopupMenu.PopupMenuSection();
this._customEntry = new St.Entry({ style_class: 'teatime-custom-entry',
track_hover: true,
hint_text: _("min:sec") });
this._customEntry.get_clutter_text().set_max_length(10);
this._customEntry.get_clutter_text().connect("key-press-event", Lang.bind(this, this._createCustomTimer));
bottom.box.add(this._customEntry);
bottom.actor.set_style("padding: 0px 18px;")
/*******************/
this.menu.addMenuItem(head);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addMenuItem(this.teaItemCont);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this.menu.addMenuItem(bottom);
this._updateTeaList();
},
_updateTeaList : function(config, output) {
// make sure the menu is empty
this.teaItemCont.removeAll();
// fill with new teas
let list = this._settings.get_value(Utils.TEATIME_STEEP_TIMES_KEY).unpack();
for (let teaname in list) {
let time = list[teaname].get_uint32();
let menuItem = new PopupTeaMenuItem(_(teaname), time);
menuItem.connect('activate', Lang.bind(this, function() {
this._initCountdown(time);
}));
this.teaItemCont.addMenuItem(menuItem);
}
},
_updateCountdownType : function(config, output) {
let bWantGraphicalCountdown = this._settings.get_boolean(Utils.TEATIME_GRAPHICAL_COUNTDOWN_KEY);
if ( bWantGraphicalCountdown != this._bGraphicalCountdown) {
if (this._idleTimeout != null) {
// we have a running countdown, replace the display
this.actor.remove_actor( this._bGraphicalCountdown
? this._graphicalTimer : this._textualTimer);
this._bGraphicalCountdown = bWantGraphicalCountdown;
this.actor.add_actor( this._bGraphicalCountdown
? this._graphicalTimer : this._textualTimer);
this._updateTimerDisplay( this._getRemainingSec() );
} // if timeout active
} // value changed
},
_createCustomTimer: function(text, event) {
if (event.get_key_symbol() == Clutter.KEY_Enter ||
event.get_key_symbol() == Clutter.KEY_Return) {
let customTime = text.get_text();
let seconds = 0;
let match = customTime.match(/^(?:(\d+)(?::(\d{0,2}))?|:(\d+))$/)
if (match) {
let factor = 1;
for (var i = match.length-2; i > 0; i--) {
let s = match[i].replace(/^0/,''); // fix for elder GNOME <= 3.10 which don't like leading zeros
seconds += factor * parseInt(s);
factor *= 60;
}
if (seconds > 0) {
this._initCountdown(seconds);
this.menu.close();
}
}
this._customEntry.set_text("");
}
},
_showNotification : function(subject, text) {
let source = ( bUseGnome34Workarounds )
? new MessageTray.Source(_("TeaTime applet"))
: new MessageTray.Source(_("TeaTime applet"), 'utilities-teatime');
if (bUseGnome34Workarounds) { source.createNotificationIcon =
function() {
let iconBox = new St.Bin();
iconBox._size = this.ICON_SIZE;
iconBox.child = new St.Icon({ icon_name: 'utilities-teatime',
icon_type: St.IconType.FULLCOLOR,
icon_size: iconBox._size });
return iconBox;
} // createNotificationIcon
}
Main.messageTray.add(source);
let notification = new MessageTray.Notification(source, subject, text);
notification.setTransient(true);
source.notify(notification);
},
_initCountdown : function(time) {
this._startTime = new Date();
this._stopTime = new Date();
this._cntdownStart = time;
this._progress = 0;
this._bGraphicalCountdown = this._settings.get_boolean(Utils.TEATIME_GRAPHICAL_COUNTDOWN_KEY);
let dt = this._bGraphicalCountdown
? Math.max(0.1, time / 180) // set time step to fit animation
: 1.0; // show every second for the textual countdown
this._stopTime.setTime(this._startTime.getTime() + time*1000); // in msec
this.actor.remove_actor(this._logo); // show timer instead of default icon
this._updateTimerDisplay(time);
this.actor.add_actor( this._bGraphicalCountdown
? this._graphicalTimer : this._textualTimer);
this._idleTimeout = Mainloop.timeout_add_seconds(dt, Lang.bind(this, this._doCountdown));
},
_getRemainingSec: function() {
let a = new Date();
return (this._stopTime.getTime() - a.getTime()) * 1e-3;
},
_updateTimerDisplay: function(remainingTime) {
if ( this._bGraphicalCountdown ) {
this._progress = (this._cntdownStart - remainingTime) / this._cntdownStart;
this._graphicalTimer.queue_repaint();
} else {
this._textualTimer.text = Utils.formatTime(remainingTime);
}
},
_doCountdown : function() {
let remainingTime = this._getRemainingSec();
if (remainingTime <= 0) {
// count down finished, switch display again
this.actor.remove_actor( this._bGraphicalCountdown
? this._graphicalTimer : this._textualTimer);
this.actor.add_actor(this._logo);
this._playSound();
if ( !bUseGnome34Workarounds && this._settings.get_boolean(Utils.TEATIME_FULLSCREEN_NOTIFICATION_KEY)) {
this.dialog = new TeaTimeFullscreenNotification();
this.dialog.show();
} else {
this._showNotification(_("Your tea is ready!"),
_("Drink it, while it is hot!"));
}
this._idleTimeout = null;
return false;
} else {
this._updateTimerDisplay(remainingTime);
return true; // continue timer
}
},
_drawTimer : function() {
let[width, height] = this._graphicalTimer.get_surface_size();
let cr = this._graphicalTimer.get_context();
let pi = Math.PI;
let r = Math.min(width, height) * 0.5;;
// TODO: get colors from current theme!
cr.setSourceRGB(0, 0, 0);
cr.rectangle(0, 0, width, height);
cr.fill();
cr.translate(Math.floor(width / 2), Math.floor(height / 2));
cr.save();
cr.setSourceRGB(0.2, 0.2, 0.2);
cr.moveTo(0, 0);
cr.arc(0, 0, r, 3 / 2 * pi + 2 * pi * this._progress, 3 / 2 * pi + 2
* pi);
cr.fill();
cr.setSourceRGB(0.8, 0.8, 0.8);
cr.moveTo(0, 0);
cr.arc(0, 0, r, 3 / 2 * pi, 3 / 2 * pi + 2 * pi * this._progress);
cr.fill();
},
_playSound : function() {
let bPlayAlarmSound = this._settings.get_boolean(Utils.TEATIME_USE_ALARM_SOUND_KEY);
if (bPlayAlarmSound) {
Utils.playSound(this._settings.get_string(Utils.TEATIME_ALARM_SOUND_KEY));
}
},
_showPreferences : function() {
imports.misc.util.spawn(["gnome-shell-extension-prefs", ExtensionUtils.getCurrentExtension().metadata['uuid']]);
return 0;
}
});
function init(metadata) {
// TODO: at some point, add translations
let theme = imports.gi.Gtk.IconTheme.get_default();
theme.append_search_path(metadata.path);
}
let _TeaTime;
function enable() {
_TeaTime = new TeaTime();
Main.panel.addToStatusArea('teatime', _TeaTime);
}
function disable() {
if (_TeaTime._idleTimeout != null) Mainloop.source_remove(_TeaTime._idleTimeout);
_TeaTime.destroy();
};