cad5e06041
Since the workarea and margins are both in pysical pixels dimensions (we fetch margins from Clutter, not from the theme), but the CSS expects logical (scaled) pixels, unless we consider the scale factor when setting max-height, it won't work on a HiDpi display. This fixes missing scrollbars when the calendar popup is full on HiDpi displays. https://bugzilla.gnome.org/show_bug.cgi?id=753305
231 lines
7.7 KiB
JavaScript
231 lines
7.7 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 child = actor.get_first_child();
|
|
|
|
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 child = actor.get_first_child();
|
|
|
|
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 child = actor.get_first_child();
|
|
if (!child)
|
|
return;
|
|
|
|
let [minWidth, natWidth] = child.get_preferred_width(-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('event', Lang.bind(this, this._onEvent));
|
|
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.uiGroup.add_actor(this.menu.actor);
|
|
this.menu.actor.hide();
|
|
}
|
|
this.emit('menu-set');
|
|
},
|
|
|
|
_onEvent: function(actor, event) {
|
|
if (this.menu &&
|
|
(event.type() == Clutter.EventType.TOUCH_BEGIN ||
|
|
event.type() == Clutter.EventType.BUTTON_PRESS))
|
|
this.menu.toggle();
|
|
|
|
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);
|
|
let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
|
|
let verticalMargins = this.menu.actor.margin_top + this.menu.actor.margin_bottom;
|
|
|
|
// The workarea and margin dimensions are in physical pixels, but CSS
|
|
// measures are in logical pixels, so make sure to consider the scale
|
|
// factor when computing max-height
|
|
let maxHeight = Math.round((workArea.height - verticalMargins) / scaleFactor);
|
|
this.menu.actor.style = ('max-height: %spx;').format(maxHeight);
|
|
},
|
|
|
|
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);
|