Top-Bar-Organizer/src/extension.js
Julian Schacher a73ed96bda
Feature: Handle changes of configured box orders
Handle changes of configured box orders, by ordering the relevant top
bar items according to the configured box order, which changed.
2021-05-16 21:32:30 +02:00

303 lines
12 KiB
JavaScript

"use strict";
const ExtensionUtils = imports.misc.extensionUtils;
const Main = imports.ui.main;
const Panel = imports.ui.panel;
class Extension {
constructor() {
this.settings = ExtensionUtils.getSettings();
}
enable() {
this._addNewItemsToBoxOrders();
this._orderTopBarItemsOfAllBoxes();
this._overwritePanelAddToPanelBox();
// Handle changes of configured box orders.
this._settingsHandlerIds = [ ];
const addConfiguredBoxOrderChangeHandler = (box) => {
let handlerId = this.settings.connect(`changed::${box}-box-order`, () => {
this._orderTopBarItems(box);
});
this._settingsHandlerIds.push(handlerId);
}
addConfiguredBoxOrderChangeHandler("left");
addConfiguredBoxOrderChangeHandler("center");
addConfiguredBoxOrderChangeHandler("right");
}
disable() {
// Revert the overwrite of `Panel._addToPanelBox`.
Panel.Panel.prototype._addToPanelBox = Panel.Panel.prototype._originalAddToPanelBox;
// Disconnect signals.
for (const handlerId of this._settingsHandlerIds) {
this.settings.disconnect(handlerId);
}
}
/**
* This method adds all new items currently present in the Gnome Shell top
* bar to the box orders.
*/
_addNewItemsToBoxOrders() {
// Load the configured box orders from settings.
let leftBoxOrder = this.settings.get_strv("left-box-order");
let centerBoxOrder = this.settings.get_strv("center-box-order");
let rightBoxOrder = this.settings.get_strv("right-box-order");
// Get items (or rather their roles) currently present in the Gnome
// Shell top bar and index them using their associated indicator
// container.
let indicatorContainerRoleMap = new Map();
for (const role in Main.panel.statusArea) {
indicatorContainerRoleMap.set(Main.panel.statusArea[role].container, role);
}
// Get the indicator containers (of the items) currently present in the
// Gnome Shell top bar.
const leftBoxIndicatorContainers = Main.panel._leftBox.get_children();
const centerBoxIndicatorContainers = Main.panel._centerBox.get_children();
// Reverse this array, since the items in the left and center box are
// logically LTR, while the items in the right box are RTL.
const rightBoxIndicatorContainers = Main.panel._rightBox.get_children().reverse();
// Go through the items (or rather their indicator containers) of each
// box and add new items (or rather their roles) to the box orders.
const addNewRolesToBoxOrder = (boxIndicatorContainers, boxOrder, atToBeginning = false) => {
// Create a box order set from the box order for fast easy access.
const boxOrderSet = new Set(boxOrder);
for (const indicatorContainer of boxIndicatorContainers) {
// First get the role associated with the current indicator
// container.
const associatedRole = indicatorContainerRoleMap.get(indicatorContainer);
// Add the role to the box order, if it isn't in it already.
if (!boxOrderSet.has(associatedRole)) {
if (atToBeginning) {
boxOrder.unshift(associatedRole);
} else {
boxOrder.push(associatedRole);
}
}
}
}
// Add new items (or rather their roles) to the box orders and save
// them.
addNewRolesToBoxOrder(leftBoxIndicatorContainers, leftBoxOrder);
addNewRolesToBoxOrder(centerBoxIndicatorContainers, centerBoxOrder);
// Add the items to the beginning for this array, since its RTL.
addNewRolesToBoxOrder(rightBoxIndicatorContainers, rightBoxOrder, true);
this.settings.set_strv("left-box-order", leftBoxOrder);
this.settings.set_strv("center-box-order", centerBoxOrder);
this.settings.set_strv("right-box-order", rightBoxOrder);
}
/**
* This methods orders the top bar items of all boxes according to the
* configured box orders using `this._orderTopBarItems`.
*/
_orderTopBarItemsOfAllBoxes() {
this._orderTopBarItems("left");
this._orderTopBarItems("center");
this._orderTopBarItems("right");
}
/**
* This method orders the top bar items of the specified box according to
* the configured box orders.
* @param {string} box - The box to order.
*/
_orderTopBarItems(box) {
// Get the valid box order.
const validBoxOrder = this._createValidBoxOrder(box);
// Get the relevant box of `Main.panel`.
let panelBox;
switch (box) {
case "left":
panelBox = Main.panel._leftBox;
break;
case "center":
panelBox = Main.panel._centerBox;
break;
case "right":
panelBox = Main.panel._rightBox;
break;
}
// Go through the items (or rather their roles) of the validBoxOrder and
// order the panelBox accordingly.
for (let i = 0; i < validBoxOrder.length; i++) {
const role = validBoxOrder[i];
// Get the indicator container associated with the current role.
const associatedIndicatorContainer = Main.panel.statusArea[role].container;
panelBox.set_child_at_index(associatedIndicatorContainer, i);
}
}
/**
* This function creates a valid box order for the given box.
* @param {string} box - The box to return the valid box order for.
* Must be one of the following values:
* - "left"
* - "center"
* - "right"
* @returns {string[]} - The valid box order.
*/
_createValidBoxOrder(box) {
// Load the configured box order from settings and get the indicator
// containers (of the items) currently present in the Gnome Shell top
// bar.
let boxOrder;
let boxIndicatorContainers;
switch (box) {
case "left":
boxOrder = this.settings.get_strv("left-box-order");
boxIndicatorContainers = Main.panel._leftBox.get_children();
break;
case "center":
boxOrder = this.settings.get_strv("center-box-order");
boxIndicatorContainers = Main.panel._centerBox.get_children();
break;
case "right":
boxOrder = this.settings.get_strv("right-box-order");
boxIndicatorContainers = Main.panel._rightBox.get_children();
break;
}
// Create an indicator containers set from the indicator containers for
// fast easy access.
const boxIndicatorContainersSet = new Set(boxIndicatorContainers);
// Go through the box order and only add items to the valid box order,
// where their indicator is present in the Gnome Shell top bar
// currently.
let validBoxOrder = [ ];
for (const role of boxOrder) {
// Get the indicator container associated with the current role.
const associatedIndicatorContainer = Main.panel.statusArea[role]?.container;
if (boxIndicatorContainersSet.has(associatedIndicatorContainer)) validBoxOrder.push(role);
}
return validBoxOrder;
}
/**
* Overwrite `Panel._addToPanelBox` with a custom method, which handles top
* bar item additions to make sure that they are added in the correct
* position.
*/
_overwritePanelAddToPanelBox() {
// Add the original `Panel._addToPanelBox` method as
// `Panel._originalAddToPanelBox`.
Panel.Panel.prototype._originalAddToPanelBox = Panel.Panel.prototype._addToPanelBox;
// This function gets used by the `Panel._addToPanelBox` overwrite to
// determine the position for a new item.
// It also adds the new item to the relevant box order, if it isn't in
// it already.
const getPositionOverwrite = (role, box) => {
let boxOrder;
let validBoxOrder;
switch (box) {
case "left":
boxOrder = this.settings.get_strv("left-box-order");
validBoxOrder = this._createValidBoxOrder("left");
break;
case "center":
boxOrder = this.settings.get_strv("center-box-order");
validBoxOrder = this._createValidBoxOrder("center");
break;
case "right":
boxOrder = this.settings.get_strv("right-box-order");
validBoxOrder = this._createValidBoxOrder("right");
break;
}
// Get the index of the role in the box order.
const index = boxOrder.indexOf(role);
// If the role is not already configured in the box order, just add
// it to the box order at the end/beginning, save the updated box
// order and return the relevant position.
if (index === -1) {
switch (box) {
// For the left and center box, insert the role at the end,
// since they're LTR.
case "left":
boxOrder.push(role);
this.settings.set_strv("left-box-order", boxOrder);
return validBoxOrder.length - 1;
case "center":
boxOrder.push(role);
this.settings.set_strv("center-box-order", boxOrder);
return validBoxOrder.length - 1;
// For the right box, insert the role at the beginning,
// since it's RTL.
case "right":
boxOrder.unshift(role);
this.settings.set_strv("right-box-order", boxOrder);
return 0;
}
}
// Since the role is already configured in the box order, determine
// the correct insertion index for the position.
// Set the insertion index initially to 0, so that if no closest
// item can be found, the new item just gets inserted at the
// beginning.
let insertionIndex = 0;
// Find the index of the closest item, which is also in the valid
// box order and before the new item.
// This way, we can insert the new item just after the index of this
// closest item.
for (let i = index - 1; i >= 0; i--) {
let potentialClosestItemIndex = validBoxOrder.indexOf(boxOrder[i]);
if (potentialClosestItemIndex !== -1) {
insertionIndex = potentialClosestItemIndex + 1;
break;
}
}
return insertionIndex;
}
// Overwrite `Panel._addToPanelBox`.
Panel.Panel.prototype._addToPanelBox = function (role, indicator, position, box) {
// Get the position overwrite.
let positionOverwrite;
switch (box) {
case this._leftBox:
positionOverwrite = getPositionOverwrite(role, "left");
break;
case this._centerBox:
positionOverwrite = getPositionOverwrite(role, "center");
break;
case this._rightBox:
positionOverwrite = getPositionOverwrite(role, "right");
break;
}
// Call the original `Panel._addToPanelBox` with the position
// overwrite as the position argument.
this._originalAddToPanelBox(role, indicator, positionOverwrite, box);
}
}
}
function init() {
return new Extension();
}