// Copyright (C) 2011-2013 R M Yorston
// Licence: GPLv2+
const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const St = imports.gi.St;
const Mainloop = imports.mainloop;
const AppFavorites = imports.ui.appFavorites;
const Main = imports.ui.main;
const Panel = imports.ui.panel;
const Tweener = imports.ui.tweener;
const PANEL_LAUNCHER_LABEL_SHOW_TIME = 0.15;
const PANEL_LAUNCHER_LABEL_HIDE_TIME = 0.1;
const PANEL_LAUNCHER_HOVER_TIMEOUT = 300;
const PanelLauncher = new Lang.Class({
Name: 'PanelLauncher',
_init: function(app) {
this.actor = new St.Button({ style_class: 'panel-button',
reactive: true });
this.iconSize = 24;
let icon = app.create_icon_texture(this.iconSize);
this.actor.set_child(icon);
this.actor._delegate = this;
let text = app.get_name();
if ( app.get_description() ) {
text += '\n' + app.get_description();
}
this.label = new St.Label({ style_class: 'panel-launcher-label'});
this.label.set_text(text);
Main.layoutManager.addChrome(this.label);
this.label.hide();
this.actor.label_actor = this.label;
this._app = app;
this.actor.connect('clicked', Lang.bind(this, function() {
this._app.open_new_window(-1);
}));
this.actor.connect('notify::hover',
Lang.bind(this, this._onHoverChanged));
this.actor.opacity = 207;
this.actor.connect('notify::allocation', Lang.bind(this, this._alloc));
},
_onHoverChanged: function(actor) {
actor.opacity = actor.hover ? 255 : 207;
},
_alloc: function() {
let size = this.actor.allocation.y2 - this.actor.allocation.y1 - 3;
if ( size >= 24 && size != this.iconSize ) {
this.actor.get_child().destroy();
this.iconSize = size;
let icon = this._app.create_icon_texture(this.iconSize);
this.actor.set_child(icon);
}
},
showLabel: function() {
this.label.opacity = 0;
this.label.show();
let [stageX, stageY] = this.actor.get_transformed_position();
let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1;
let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1;
let labelWidth = this.label.get_width();
let node = this.label.get_theme_node();
let yOffset = node.get_length('-y-offset');
let y = stageY + itemHeight + yOffset;
let x = Math.floor(stageX + itemWidth/2 - labelWidth/2);
let parent = this.label.get_parent();
let parentWidth = parent.allocation.x2 - parent.allocation.x1;
if ( Clutter.get_default_text_direction() == Clutter.TextDirection.LTR ) {
// stop long tooltips falling off the right of the screen
x = Math.min(x, parentWidth-labelWidth-6);
// but whatever happens don't let them fall of the left
x = Math.max(x, 6);
}
else {
x = Math.max(x, 6);
x = Math.min(x, parentWidth-labelWidth-6);
}
this.label.set_position(x, y);
Tweener.addTween(this.label,
{ opacity: 255,
time: PANEL_LAUNCHER_LABEL_SHOW_TIME,
transition: 'easeOutQuad',
});
},
hideLabel: function() {
this.label.opacity = 255;
Tweener.addTween(this.label,
{ opacity: 0,
time: PANEL_LAUNCHER_LABEL_HIDE_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this, function() {
this.label.hide();
})
});
},
destroy: function() {
this.label.destroy();
this.actor.destroy();
}
});
const PanelFavorites = new Lang.Class({
Name: 'PanelFavorites',
_init: function() {
this._showLabelTimeoutId = 0;
this._resetHoverTimeoutId = 0;
this._labelShowing = false;
this.actor = new St.BoxLayout({ name: 'panelFavorites',
x_expand: true, y_expand: true,
style_class: 'panel-favorites' });
this._display();
this.container = new St.Bin({ y_fill: true,
x_fill: true,
child: this.actor });
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._installChangedId = Shell.AppSystem.get_default().connect('installed-changed', Lang.bind(this, this._redisplay));
this._changedId = AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._redisplay));
},
_redisplay: function() {
for ( let i=0; i<this._buttons.length; ++i ) {
this._buttons[i].destroy();
}
this._display();
},
_display: function() {
let launchers = global.settings.get_strv(AppFavorites.getAppFavorites().FAVORITE_APPS_KEY);
this._buttons = [];
let j = 0;
for ( let i=0; i<launchers.length; ++i ) {
let app = Shell.AppSystem.get_default().lookup_app(launchers[i]);
if ( app == null ) {
continue;
}
let launcher = new PanelLauncher(app);
this.actor.add(launcher.actor);
launcher.actor.connect('notify::hover',
Lang.bind(this, function() {
this._onHover(launcher);
}));
this._buttons[j] = launcher;
++j;
}
},
// this routine stolen from dash.js
_onHover: function(launcher) {
if ( launcher.actor.hover ) {
if (this._showLabelTimeoutId == 0) {
let timeout = this._labelShowing ?
0 : PANEL_LAUNCHER_HOVER_TIMEOUT;
this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
Lang.bind(this, function() {
this._labelShowing = true;
launcher.showLabel();
this._showLabelTimeoutId = 0;
return GLib.SOURCE_REMOVE;
}));
if (this._resetHoverTimeoutId > 0) {
Mainloop.source_remove(this._resetHoverTimeoutId);
this._resetHoverTimeoutId = 0;
}
}
} else {
if (this._showLabelTimeoutId > 0) {
Mainloop.source_remove(this._showLabelTimeoutId);
this._showLabelTimeoutId = 0;
}
launcher.hideLabel();
if (this._labelShowing) {
this._resetHoverTimeoutId = Mainloop.timeout_add(
PANEL_LAUNCHER_HOVER_TIMEOUT,
Lang.bind(this, function() {
this._labelShowing = false;
this._resetHoverTimeoutId = 0;
return GLib.SOURCE_REMOVE;
}));
}
}
},
_onDestroy: function() {
if ( this._installChangedId != 0 ) {
Shell.AppSystem.get_default().disconnect(this._installChangedId);
this._installChangedId = 0;
}
if ( this._changedId != 0 ) {
AppFavorites.getAppFavorites().disconnect(this._changedId);
this._changedId = 0;
}
}
});
Signals.addSignalMethods(PanelFavorites.prototype);
let myAddToStatusArea;
let panelFavorites;
function enable() {
Panel.Panel.prototype.myAddToStatusArea = myAddToStatusArea;
// place panel to left of app menu, or failing that at right end of box
let siblings = Main.panel._leftBox.get_children();
let appMenu = Main.panel.statusArea['appMenu'];
let pos = appMenu ? siblings.indexOf(appMenu.container) : siblings.length;
panelFavorites = new PanelFavorites();
Main.panel.myAddToStatusArea('panel-favorites', panelFavorites,
pos, 'left');
}
function disable() {
delete Panel.Panel.prototype.myAddToStatusArea;
panelFavorites.actor.destroy();
panelFavorites.emit('destroy');
panelFavorites = null;
}
function init() {
myAddToStatusArea = function(role, indicator, position, box) {
if (this.statusArea[role])
throw new Error('Extension point conflict: there is already a status indicator for role ' + role);
position = position || 0;
let boxes = {
left: this._leftBox,
center: this._centerBox,
right: this._rightBox
};
let boxContainer = boxes[box] || this._rightBox;
this.statusArea[role] = indicator;
this._addToPanelBox(role, indicator, position, boxContainer);
return indicator;
};
}