/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const Clutter = imports.gi.Clutter; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const Shell = imports.gi.Shell; const Signals = imports.signals; const St = imports.gi.St; const Main = imports.ui.main; const Params = imports.misc.params; const PopupMenu = imports.ui.popupMenu; function ButtonBox(params) { this._init.apply(this, arguments); }; ButtonBox.prototype = { _init: function(params) { params = Params.parse(params, { style_class: 'panel-button' }, true); this.actor = new Shell.GenericContainer(params); this.actor._delegate = this; 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; } if (natHeight <= availHeight) { childBox.y1 = Math.floor((availHeight - natHeight) / 2); childBox.y2 = childBox.y1 + natHeight; } else { childBox.y1 = 0; childBox.y2 = availHeight; } child.allocate(childBox, flags); }, } function Button(menuAlignment) { this._init(menuAlignment); } Button.prototype = { __proto__: ButtonBox.prototype, _init: function(menuAlignment) { ButtonBox.prototype._init.call(this, { reactive: true, can_focus: true, track_hover: true }); this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress)); this.actor.connect('key-press-event', Lang.bind(this, this._onSourceKeyPress)); this.menu = new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP); 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(); }, _onButtonPress: function(actor, event) { if (!this.menu.isOpen) { // 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 monitor = Main.layoutManager.primaryMonitor; this.menu.actor.style = ('max-height: ' + Math.round(monitor.height - Main.panel.actor.height) + 'px;'); } this.menu.toggle(); }, _onSourceKeyPress: function(actor, event) { let symbol = event.get_key_symbol(); if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) { this.menu.toggle(); return true; } else if (symbol == Clutter.KEY_Escape && this.menu.isOpen) { this.menu.close(); return true; } 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 true; } else return false; }, _onMenuKeyPress: function(actor, event) { let symbol = event.get_key_symbol(); if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { let focusManager = St.FocusManager.get_for_stage(global.stage); let group = focusManager.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 true; } } return false; }, _onOpenStateChanged: function(menu, open) { if (open) this.actor.add_style_pseudo_class('active'); else this.actor.remove_style_pseudo_class('active'); }, destroy: function() { this.actor._delegate = null; this.menu.destroy(); this.actor.destroy(); this.emit('destroy'); } }; Signals.addSignalMethods(Button.prototype); /* SystemStatusButton: * * This class manages one System Status indicator (network, keyboard, * volume, bluetooth...), which is just a PanelMenuButton with an * icon and a tooltip */ function SystemStatusButton() { this._init.apply(this, arguments); } SystemStatusButton.prototype = { __proto__: Button.prototype, _init: function(iconName,tooltipText) { Button.prototype._init.call(this, 0.0); this._iconActor = new St.Icon({ icon_name: iconName, icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); this.actor.add_actor(this._iconActor); this.actor.add_style_class_name('panel-status-button'); this.setTooltip(tooltipText); }, setIcon: function(iconName) { this._iconActor.icon_name = iconName; }, setGIcon: function(gicon) { this._iconActor.gicon = gicon; }, setTooltip: function(text) { if (text != null) { this.tooltip = text; this.actor.has_tooltip = true; this.actor.tooltip_text = text; } else { this.actor.has_tooltip = false; this.tooltip = null; } } };