diff --git a/data/ui/prefs-box-order-item-row.ui b/data/ui/prefs-box-order-item-row.ui index 86bf461..d8e6e73 100644 --- a/data/ui/prefs-box-order-item-row.ui +++ b/data/ui/prefs-box-order-item-row.ui @@ -42,6 +42,16 @@ +
+ + Move Up + row.move-up + + + Move Down + row.move-down + +
Forget diff --git a/data/ui/prefs-page.ui b/data/ui/prefs-page.ui index 288fa75..7eca8d0 100644 --- a/data/ui/prefs-page.ui +++ b/data/ui/prefs-page.ui @@ -13,8 +13,9 @@ - + left-box-order + @@ -26,8 +27,9 @@ - + center-box-order + @@ -39,8 +41,9 @@ - + right-box-order + diff --git a/src/prefsModules/PrefsBoxOrderItemRow.js b/src/prefsModules/PrefsBoxOrderItemRow.js index 976e4ba..0b4818e 100644 --- a/src/prefsModules/PrefsBoxOrderItemRow.js +++ b/src/prefsModules/PrefsBoxOrderItemRow.js @@ -7,13 +7,24 @@ import GObject from "gi://GObject"; import Adw from "gi://Adw"; import GLib from "gi://GLib"; -const PrefsBoxOrderItemRow = GObject.registerClass({ - GTypeName: "PrefsBoxOrderItemRow", - Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-box-order-item-row.ui", GLib.UriFlags.NONE), - InternalChildren: [ - "item-name-display-label" - ] -}, class PrefsBoxOrderItemRow extends Adw.ActionRow { +export default class PrefsBoxOrderItemRow extends Adw.ActionRow { + static { + GObject.registerClass({ + GTypeName: "PrefsBoxOrderItemRow", + Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-box-order-item-row.ui", GLib.UriFlags.NONE), + InternalChildren: [ + "item-name-display-label" + ], + Signals: { + "move": { + param_types: [GObject.TYPE_STRING] + } + } + }, this); + this.install_action("row.move-up", null, (self, _actionName, _param) => self.emit("move", "up")); + this.install_action("row.move-down", null, (self, _actionName, _param) => self.emit("move", "down")); + } + #drag_starting_point_x; #drag_starting_point_y; @@ -52,8 +63,9 @@ const PrefsBoxOrderItemRow = GObject.registerClass({ }); forgetAction.connect("activate", (_action, _params) => { const parentListBox = this.get_parent(); - parentListBox.remove(this); + parentListBox.removeRow(this); parentListBox.saveBoxOrderToSettings(); + parentListBox.determineRowMoveActionEnable(); }); actionGroup.add_action(forgetAction); @@ -101,7 +113,7 @@ const PrefsBoxOrderItemRow = GObject.registerClass({ const valuePosition = value.get_index(); // Remove the drop value from its list box. - valueListBox.remove(value); + valueListBox.removeRow(value); // Since an element got potentially removed from the list of `this`, // get the position of `this` again. @@ -115,31 +127,31 @@ const PrefsBoxOrderItemRow = GObject.registerClass({ || (ownListBox.boxOrder === "center-box-order" && valueListBox.boxOrder === "left-box-order")) { // If the list box of the drop value comes before the list // box of `this`, add the drop value after `this`. - ownListBox.insert(value, updatedOwnPosition + 1); + ownListBox.insertRow(value, updatedOwnPosition + 1); } else { // Otherwise, add the drop value where `this` currently is. - ownListBox.insert(value, updatedOwnPosition); + ownListBox.insertRow(value, updatedOwnPosition); } } else { if (valuePosition < ownPosition) { // If the drop value was before `this`, add the drop value // after `this`. - ownListBox.insert(value, updatedOwnPosition + 1); + ownListBox.insertRow(value, updatedOwnPosition + 1); } else { // Otherwise, add the drop value where `this` currently is. - ownListBox.insert(value, updatedOwnPosition); + ownListBox.insertRow(value, updatedOwnPosition); } } - /// Finally save the box order(/s) to settings. + /// Finally save the box order(/s) to settings and make sure move + /// actions are correctly enabled/disabled. ownListBox.saveBoxOrderToSettings(); - // If the list boxes of `this` and the drop value were different, - // save an updated box order for the list were the drop value was in - // as well. + ownListBox.determineRowMoveActionEnable(); + // If the list boxes of `this` and the drop value were different, handle + // the former list box of the drop value as well. if (ownListBox !== valueListBox) { valueListBox.saveBoxOrderToSettings(); + valueListBox.determineRowMoveActionEnable(); } } -}); - -export default PrefsBoxOrderItemRow; +} diff --git a/src/prefsModules/PrefsBoxOrderListBox.js b/src/prefsModules/PrefsBoxOrderListBox.js index 68cf7d4..5c08922 100644 --- a/src/prefsModules/PrefsBoxOrderListBox.js +++ b/src/prefsModules/PrefsBoxOrderListBox.js @@ -17,12 +17,18 @@ const PrefsBoxOrderListBox = GObject.registerClass({ "box-order", "Box Order", "The box order this PrefsBoxOrderListBox is associated with.", - GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + GObject.ParamFlags.READWRITE, "" ) + }, + Signals: { + "row-move": { + param_types: [PrefsBoxOrderItemRow, GObject.TYPE_STRING] + } } }, class PrefsBoxOrderListBox extends Gtk.ListBox { #settings; + #rowSignalHandlerIds = new Map(); /** * @param {Object} params @@ -45,21 +51,49 @@ const PrefsBoxOrderListBox = GObject.registerClass({ set boxOrder(value) { this._boxOrder = value; - // Load the settings here as well, since a `CONSTRUCT_ONLY` property - // apparently can't access `this.#settings`. - const settings = ExtensionPreferences.lookupByURL(import.meta.url).getSettings(); // Get the actual box order for the given box order name from settings. - const boxOrder = settings.get_strv(this._boxOrder); + const boxOrder = this.#settings.get_strv(this._boxOrder); // Populate this GtkListBox with GtkListBoxRows for the items of the // given configured box order. for (const item of boxOrder) { - const listBoxRow = new PrefsBoxOrderItemRow({}, item); - this.append(listBoxRow); + const row = new PrefsBoxOrderItemRow({}, item); + this.insertRow(row, -1); } + this.determineRowMoveActionEnable(); this.notify("box-order"); } + /** + * Inserts the given PrefsBoxOrderItemRow to this list box at the given + * position. + * Also handles stuff like connecting signals. + */ + insertRow(row, position) { + this.insert(row, position); + + const signalHandlerIds = []; + signalHandlerIds.push(row.connect("move", (row, direction) => { + this.emit("row-move", row, direction); + })); + + this.#rowSignalHandlerIds.set(row, signalHandlerIds); + } + + /** + * Removes the given PrefsBoxOrderItemRow from this list box. + * Also handles stuff like disconnecting signals. + */ + removeRow(row) { + const signalHandlerIds = this.#rowSignalHandlerIds.get(row); + + for (const id of signalHandlerIds) { + row.disconnect(id); + } + + this.remove(row); + } + /** * Saves the box order represented by `this` (and its * `PrefsBoxOrderItemRows`) to settings. @@ -77,6 +111,38 @@ const PrefsBoxOrderListBox = GObject.registerClass({ } this.#settings.set_strv(this.boxOrder, currentBoxOrder); } + + /** + * Determines whether or not each move action of each PrefsBoxOrderItemRow + * should be enabled or disabled. + */ + determineRowMoveActionEnable() { + for (let potentialPrefsBoxOrderItemRow of this) { + // Only process PrefsBoxOrderItemRows. + if (potentialPrefsBoxOrderItemRow.constructor.$gtype.name !== "PrefsBoxOrderItemRow") { + continue; + } + + const row = potentialPrefsBoxOrderItemRow; + + // If the current row is the topmost row in the topmost list box, + // then disable the move-up action. + if (row.get_index() === 0 && this.boxOrder === "left-box-order") { + row.action_set_enabled("row.move-up", false); + } else { // Else enable it. + row.action_set_enabled("row.move-up", true); + } + + // If the current row is the bottommost row in the bottommost list + // box, then disable the move-down action. + const rowNextSibling = row.get_next_sibling(); + if ((rowNextSibling instanceof PrefsBoxOrderListEmptyPlaceholder || rowNextSibling === null) && this.boxOrder === "right-box-order") { + row.action_set_enabled("row.move-down", false); + } else { // Else enable it. + row.action_set_enabled("row.move-down", true); + } + } + } }); export default PrefsBoxOrderListBox; diff --git a/src/prefsModules/PrefsBoxOrderListEmptyPlaceholder.js b/src/prefsModules/PrefsBoxOrderListEmptyPlaceholder.js index 2f32e2e..b04d256 100644 --- a/src/prefsModules/PrefsBoxOrderListEmptyPlaceholder.js +++ b/src/prefsModules/PrefsBoxOrderListEmptyPlaceholder.js @@ -16,14 +16,17 @@ const PrefsBoxOrderListEmptyPlaceholder = GObject.registerClass({ const valueListBox = value.get_parent(); // Remove the drop value from its list box. - valueListBox.remove(value); + valueListBox.removeRow(value); // Insert the drop value into the list box of `this`. - ownListBox.insert(value, 0); + ownListBox.insertRow(value, 0); - /// Finally save the box orders to settings. + /// Finally save the box orders to settings and make sure move actions + /// are correctly enabled/disabled. ownListBox.saveBoxOrderToSettings(); + ownListBox.determineRowMoveActionEnable(); valueListBox.saveBoxOrderToSettings(); + valueListBox.determineRowMoveActionEnable(); } }); diff --git a/src/prefsModules/PrefsPage.js b/src/prefsModules/PrefsPage.js index cd9e476..47f34a6 100644 --- a/src/prefsModules/PrefsPage.js +++ b/src/prefsModules/PrefsPage.js @@ -6,13 +6,19 @@ import Adw from "gi://Adw"; import GLib from "gi://GLib"; import ScrollManager from "./ScrollManager.js"; +import PrefsBoxOrderListEmptyPlaceholder from "./PrefsBoxOrderListEmptyPlaceholder.js"; // Imports to make UI file work. import PrefsBoxOrderListBox from "./PrefsBoxOrderListBox.js"; const PrefsPage = GObject.registerClass({ GTypeName: "PrefsPage", - Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-page.ui", GLib.UriFlags.NONE) + Template: GLib.uri_resolve_relative(import.meta.url, "../ui/prefs-page.ui", GLib.UriFlags.NONE), + InternalChildren: [ + "left-box-order-list-box", + "center-box-order-list-box", + "right-box-order-list-box" + ] }, class PrefsPage extends Adw.PreferencesPage { constructor(params = {}) { super(params); @@ -81,6 +87,91 @@ const PrefsPage = GObject.registerClass({ this.add_controller(controller); } + + onRowMove(listBox, row, direction) { + const rowPosition = row.get_index(); + + if (direction === "up") { // If the direction of the move is up. + // Handle the case, where the row is the topmost row in the list box. + if (rowPosition === 0) { + switch (listBox.boxOrder) { + // If the row is also in the topmost list box, then do + // nothing and return. + case "left-box-order": + log("The row is already the topmost row in the topmost box order."); + return; + // If the row is in the center list box, then move it up to + // the left one. + case "center-box-order": + listBox.removeRow(row); + this._left_box_order_list_box.insertRow(row, -1); + // First save the box order of the destination, then do + // "a save for clean up". + this._left_box_order_list_box.saveBoxOrderToSettings(); + this._left_box_order_list_box.determineRowMoveActionEnable(); + listBox.saveBoxOrderToSettings(); + listBox.determineRowMoveActionEnable(); + return; + // If the row is in the right list box, then move it up to + // the center one. + case "right-box-order": + listBox.removeRow(row); + this._center_box_order_list_box.insertRow(row, -1); + this._center_box_order_list_box.saveBoxOrderToSettings(); + this._center_box_order_list_box.determineRowMoveActionEnable(); + listBox.saveBoxOrderToSettings(); + listBox.determineRowMoveActionEnable(); + return; + } + } + + // Else just move the row up in the box. + listBox.removeRow(row); + listBox.insertRow(row, rowPosition - 1); + listBox.saveBoxOrderToSettings(); + listBox.determineRowMoveActionEnable(); + return; + } else { // Else the direction of the move must be down. + // Handle the case, where the row is the bottommost row in the list box. + const rowNextSibling = row.get_next_sibling(); + if (rowNextSibling instanceof PrefsBoxOrderListEmptyPlaceholder || rowNextSibling === null) { + switch (listBox.boxOrder) { + // If the row is also in the bottommost list box, then do + // nothing and return. + case "right-box-order": + log("The row is already the bottommost row in the bottommost box order."); + return; + // If the row is in the center list box, then move it down + // to the right one. + case "center-box-order": + listBox.removeRow(row); + this._right_box_order_list_box.insertRow(row, 0); + this._right_box_order_list_box.saveBoxOrderToSettings(); + this._right_box_order_list_box.determineRowMoveActionEnable(); + listBox.saveBoxOrderToSettings(); + listBox.determineRowMoveActionEnable(); + return; + // If the row is in the left list box, then move it down to + // the center one. + case "left-box-order": + listBox.removeRow(row); + this._center_box_order_list_box.insertRow(row, 0); + this._center_box_order_list_box.saveBoxOrderToSettings(); + this._center_box_order_list_box.determineRowMoveActionEnable(); + listBox.saveBoxOrderToSettings(); + listBox.determineRowMoveActionEnable(); + return; + } + } + + // Else just move the row down in the box. + listBox.removeRow(row); + listBox.insertRow(row, rowPosition + 1); + listBox.saveBoxOrderToSettings(); + listBox.determineRowMoveActionEnable(); + return; + } + } }); export default PrefsPage;