71670bad3b
This helps take cruft out of the uiGroup, and ensures that components remain stacked properly on top of each other. In the future, we'll use this group to ensure that grabs are ordered properly, as well.
247 lines
8.3 KiB
JavaScript
247 lines
8.3 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
const Gio = imports.gi.Gio;
|
|
const Gtk = imports.gi.Gtk;
|
|
const Lang = imports.lang;
|
|
const Shell = imports.gi.Shell;
|
|
const Signals = imports.signals;
|
|
const St = imports.gi.St;
|
|
const Atk = imports.gi.Atk;
|
|
|
|
const Main = imports.ui.main;
|
|
const Params = imports.misc.params;
|
|
const PopupMenu = imports.ui.popupMenu;
|
|
|
|
const ButtonBox = new Lang.Class({
|
|
Name: 'ButtonBox',
|
|
|
|
_init: function(params) {
|
|
params = Params.parse(params, { style_class: 'panel-button' }, true);
|
|
this.actor = new Shell.GenericContainer(params);
|
|
this.actor._delegate = this;
|
|
|
|
this.container = new St.Bin({ y_fill: true,
|
|
x_fill: true,
|
|
child: this.actor });
|
|
|
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
|
|
|
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
|
this._minHPadding = this._natHPadding = 0.0;
|
|
},
|
|
|
|
_onStyleChanged: function(actor) {
|
|
let themeNode = actor.get_theme_node();
|
|
|
|
this._minHPadding = themeNode.get_length('-minimum-hpadding');
|
|
this._natHPadding = themeNode.get_length('-natural-hpadding');
|
|
},
|
|
|
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
|
let children = actor.get_children();
|
|
let child = children.length > 0 ? children[0] : null;
|
|
|
|
if (child) {
|
|
[alloc.min_size, alloc.natural_size] = child.get_preferred_width(-1);
|
|
} else {
|
|
alloc.min_size = alloc.natural_size = 0;
|
|
}
|
|
|
|
alloc.min_size += 2 * this._minHPadding;
|
|
alloc.natural_size += 2 * this._natHPadding;
|
|
},
|
|
|
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
|
let children = actor.get_children();
|
|
let child = children.length > 0 ? children[0] : null;
|
|
|
|
if (child) {
|
|
[alloc.min_size, alloc.natural_size] = child.get_preferred_height(-1);
|
|
} else {
|
|
alloc.min_size = alloc.natural_size = 0;
|
|
}
|
|
},
|
|
|
|
_allocate: function(actor, box, flags) {
|
|
let children = actor.get_children();
|
|
if (children.length == 0)
|
|
return;
|
|
|
|
let child = children[0];
|
|
let [minWidth, natWidth] = child.get_preferred_width(-1);
|
|
let [minHeight, natHeight] = child.get_preferred_height(-1);
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
if (natWidth + 2 * this._natHPadding <= availWidth) {
|
|
childBox.x1 = this._natHPadding;
|
|
childBox.x2 = availWidth - this._natHPadding;
|
|
} else {
|
|
childBox.x1 = this._minHPadding;
|
|
childBox.x2 = availWidth - this._minHPadding;
|
|
}
|
|
|
|
childBox.y1 = 0;
|
|
childBox.y2 = availHeight;
|
|
|
|
child.allocate(childBox, flags);
|
|
},
|
|
});
|
|
|
|
const Button = new Lang.Class({
|
|
Name: 'PanelMenuButton',
|
|
Extends: ButtonBox,
|
|
|
|
_init: function(menuAlignment, nameText, dontCreateMenu) {
|
|
this.parent({ reactive: true,
|
|
can_focus: true,
|
|
track_hover: true,
|
|
accessible_name: nameText ? nameText : "",
|
|
accessible_role: Atk.Role.MENU });
|
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
|
this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress));
|
|
this.actor.connect('notify::visible', Lang.bind(this, this._onVisibilityChanged));
|
|
|
|
if (dontCreateMenu)
|
|
this.menu = new PopupMenu.PopupDummyMenu(this.actor);
|
|
else
|
|
this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0));
|
|
},
|
|
|
|
setSensitive: function(sensitive) {
|
|
this.actor.reactive = sensitive;
|
|
this.actor.can_focus = sensitive;
|
|
this.actor.track_hover = sensitive;
|
|
},
|
|
|
|
setMenu: function(menu) {
|
|
if (this.menu)
|
|
this.menu.destroy();
|
|
|
|
this.menu = menu;
|
|
if (this.menu) {
|
|
this.menu.actor.add_style_class_name('panel-menu');
|
|
this.menu.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged));
|
|
this.menu.actor.connect('key-press-event', Lang.bind(this, this._onMenuKeyPress));
|
|
|
|
Main.layoutManager.menuGroup.add_actor(this.menu.actor);
|
|
this.menu.actor.hide();
|
|
}
|
|
},
|
|
|
|
_onButtonPress: function(actor, event) {
|
|
if (!this.menu)
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
this.menu.toggle();
|
|
return Clutter.EVENT_PROPAGATE;
|
|
},
|
|
|
|
_onSourceKeyPress: function(actor, event) {
|
|
if (!this.menu)
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
let symbol = event.get_key_symbol();
|
|
if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
|
|
this.menu.toggle();
|
|
return Clutter.EVENT_STOP;
|
|
} else if (symbol == Clutter.KEY_Escape && this.menu.isOpen) {
|
|
this.menu.close();
|
|
return Clutter.EVENT_STOP;
|
|
} else if (symbol == Clutter.KEY_Down) {
|
|
if (!this.menu.isOpen)
|
|
this.menu.toggle();
|
|
this.menu.actor.navigate_focus(this.actor, Gtk.DirectionType.DOWN, false);
|
|
return Clutter.EVENT_STOP;
|
|
} else
|
|
return Clutter.EVENT_PROPAGATE;
|
|
},
|
|
|
|
_onVisibilityChanged: function() {
|
|
if (!this.menu)
|
|
return;
|
|
|
|
if (!this.actor.visible)
|
|
this.menu.close();
|
|
},
|
|
|
|
_onMenuKeyPress: function(actor, event) {
|
|
if (global.focus_manager.navigate_from_event(event))
|
|
return Clutter.EVENT_STOP;
|
|
|
|
let symbol = event.get_key_symbol();
|
|
if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) {
|
|
let group = global.focus_manager.get_group(this.actor);
|
|
if (group) {
|
|
let direction = (symbol == Clutter.KEY_Left) ? Gtk.DirectionType.LEFT : Gtk.DirectionType.RIGHT;
|
|
group.navigate_focus(this.actor, direction, false);
|
|
return Clutter.EVENT_STOP;
|
|
}
|
|
}
|
|
return Clutter.EVENT_PROPAGATE;
|
|
},
|
|
|
|
_onOpenStateChanged: function(menu, open) {
|
|
if (open)
|
|
this.actor.add_style_pseudo_class('active');
|
|
else
|
|
this.actor.remove_style_pseudo_class('active');
|
|
|
|
// Setting the max-height won't do any good if the minimum height of the
|
|
// menu is higher then the screen; it's useful if part of the menu is
|
|
// scrollable so the minimum height is smaller than the natural height
|
|
let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
|
|
this.menu.actor.style = ('max-height: ' + Math.round(workArea.height) + 'px;');
|
|
},
|
|
|
|
destroy: function() {
|
|
this.actor._delegate = null;
|
|
|
|
if (this.menu)
|
|
this.menu.destroy();
|
|
this.actor.destroy();
|
|
|
|
this.emit('destroy');
|
|
}
|
|
});
|
|
Signals.addSignalMethods(Button.prototype);
|
|
|
|
/* SystemIndicator:
|
|
*
|
|
* This class manages one system indicator, which are the icons
|
|
* that you see at the top right. A system indicator is composed
|
|
* of an icon and a menu section, which will be composed into the
|
|
* aggregate menu.
|
|
*/
|
|
const SystemIndicator = new Lang.Class({
|
|
Name: 'SystemIndicator',
|
|
|
|
_init: function() {
|
|
this.indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box',
|
|
reactive: true });
|
|
this.indicators.hide();
|
|
this.menu = new PopupMenu.PopupMenuSection();
|
|
},
|
|
|
|
_syncIndicatorsVisible: function() {
|
|
this.indicators.visible = this.indicators.get_children().some(function(actor) {
|
|
return actor.visible;
|
|
});
|
|
},
|
|
|
|
_addIndicator: function() {
|
|
let icon = new St.Icon({ style_class: 'system-status-icon' });
|
|
this.indicators.add_actor(icon);
|
|
icon.connect('notify::visible', Lang.bind(this, this._syncIndicatorsVisible));
|
|
this._syncIndicatorsVisible();
|
|
return icon;
|
|
}
|
|
});
|
|
Signals.addSignalMethods(SystemIndicator.prototype);
|