Last active
February 4, 2026 15:06
-
-
Save romualdrichard/73080c49a3aeb108815e8c6d291cc5ea to your computer and use it in GitHub Desktop.
#jarchi Align and Spread with dialogbox
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Author: Jean-Charles Perron, based on the work of Rob Kamp | |
| // Requires: jArchi - https://www.archimatetool.com/blog/2018/07/02/jarchi/ | |
| // Purpose: SWT dialog to align (left, top, bottom, right) and spread (horizontal, vertical) selected elements | |
| // Date: 2024-01 | |
| // Version: 2.0 | |
| // Changes: | |
| // 1.0 - Initial version (Rob Kamp / Jean-Charles Perron): spread vertical/horizontal via prompt | |
| // 2.0 - SWT dialog with align left/top/bottom/right + spread H/V, closeable with ESC | |
| // ─── Imports ──────────────────────────────────────────────────────────────── | |
| var Shell = Java.type("org.eclipse.swt.widgets.Shell"); | |
| var Display = Java.type("org.eclipse.swt.widgets.Display"); | |
| var Label = Java.type("org.eclipse.swt.widgets.Label"); | |
| var Button = Java.type("org.eclipse.swt.widgets.Button"); | |
| var GridLayout = Java.type("org.eclipse.swt.layout.GridLayout"); | |
| var GridData = Java.type("org.eclipse.swt.layout.GridData"); | |
| var SWT = Java.type("org.eclipse.swt.SWT"); | |
| var Composite = Java.type("org.eclipse.swt.widgets.Composite"); | |
| // ─── Prepare selection: keep only top-level elements (remove nested children) ─ | |
| console.log("Start: Align & Spread"); | |
| var mySelection = $(selection); | |
| var childrenToRemove = $(""); | |
| mySelection.each(function (element) { | |
| var children = $(element).children(); | |
| if (children != null) { | |
| childrenToRemove.add(children); | |
| } | |
| }); | |
| mySelection = mySelection.not(childrenToRemove); | |
| var selCount = mySelection.size(); | |
| console.log("Objects in selection: " + selCount); | |
| // ─── Guard: need at least 2 elements ───────────────────────────────────────── | |
| if (selCount < 2) { | |
| console.log("Select at least 2 objects."); | |
| } else { | |
| // ════════════════════════════════════════════════════════════════════════════════ | |
| // ALIGNMENT & SPREAD FUNCTIONS | |
| // ════════════════════════════════════════════════════════════════════════════════ | |
| // ── Align Left: all elements share the smallest X ────────────────────────── | |
| function alignLeft() { | |
| var minX = null; | |
| mySelection.each(function (el) { | |
| minX = (minX === null) ? el.bounds.x : Math.min(minX, el.bounds.x); | |
| }); | |
| mySelection.each(function (el) { | |
| el.bounds = { x: minX, y: el.bounds.y, width: el.bounds.width, height: el.bounds.height }; | |
| }); | |
| console.log("Aligned left → x = " + minX); | |
| } | |
| // ── Align Right: all elements share the largest (x + width) ──────────────── | |
| function alignRight() { | |
| var maxRight = null; | |
| mySelection.each(function (el) { | |
| var right = el.bounds.x + el.bounds.width; | |
| maxRight = (maxRight === null) ? right : Math.max(maxRight, right); | |
| }); | |
| mySelection.each(function (el) { | |
| el.bounds = { x: maxRight - el.bounds.width, y: el.bounds.y, width: el.bounds.width, height: el.bounds.height }; | |
| }); | |
| console.log("Aligned right → right edge = " + maxRight); | |
| } | |
| // ── Align Top: all elements share the smallest Y ─────────────────────────── | |
| function alignTop() { | |
| var minY = null; | |
| mySelection.each(function (el) { | |
| minY = (minY === null) ? el.bounds.y : Math.min(minY, el.bounds.y); | |
| }); | |
| mySelection.each(function (el) { | |
| el.bounds = { x: el.bounds.x, y: minY, width: el.bounds.width, height: el.bounds.height }; | |
| }); | |
| console.log("Aligned top → y = " + minY); | |
| } | |
| // ── Align Bottom: all elements share the largest (y + height) ─────────────── | |
| function alignBottom() { | |
| var maxBottom = null; | |
| mySelection.each(function (el) { | |
| var bottom = el.bounds.y + el.bounds.height; | |
| maxBottom = (maxBottom === null) ? bottom : Math.max(maxBottom, bottom); | |
| }); | |
| mySelection.each(function (el) { | |
| el.bounds = { x: el.bounds.x, y: maxBottom - el.bounds.height, width: el.bounds.width, height: el.bounds.height }; | |
| }); | |
| console.log("Aligned bottom → bottom edge = " + maxBottom); | |
| } | |
| // ── Align Center Horizontally: all elements share the average horizontal center ─ | |
| function alignCenterHorizontal() { | |
| var sumCx = 0; | |
| mySelection.each(function (el) { | |
| sumCx += el.bounds.x + el.bounds.width / 2; | |
| }); | |
| var avgCx = sumCx / selCount; | |
| mySelection.each(function (el) { | |
| var newX = avgCx - el.bounds.width / 2; | |
| el.bounds = { x: newX, y: el.bounds.y, width: el.bounds.width, height: el.bounds.height }; | |
| }); | |
| console.log("Aligned center horizontal → x center = " + avgCx); | |
| } | |
| // ── Align Center Vertically: all elements share the average vertical center ─ | |
| function alignCenterVertical() { | |
| var sumCy = 0; | |
| mySelection.each(function (el) { | |
| sumCy += el.bounds.y + el.bounds.height / 2; | |
| }); | |
| var avgCy = sumCy / selCount; | |
| mySelection.each(function (el) { | |
| var newY = avgCy - el.bounds.height / 2; | |
| el.bounds = { x: el.bounds.x, y: newY, width: el.bounds.width, height: el.bounds.height }; | |
| }); | |
| console.log("Aligned center vertical → y center = " + avgCy); | |
| } | |
| // ── Spread Vertically: distribute centers evenly between topmost & bottommost center ─ | |
| function spreadVertically() { | |
| var minY = null, maxY = null; | |
| mySelection.each(function (el) { | |
| var cy = el.bounds.y + el.bounds.height / 2; | |
| minY = (minY === null) ? cy : Math.min(minY, cy); | |
| maxY = (maxY === null) ? cy : Math.max(maxY, cy); | |
| }); | |
| var gap = (maxY - minY) / (selCount - 1); | |
| // Sort by current Y so the visual order is preserved | |
| mySelection.sort(function (a, b) { return a.bounds.y - b.bounds.y; }); | |
| var i = 0; | |
| mySelection.each(function (el) { | |
| var newY = minY + i * gap - el.bounds.height / 2; | |
| el.bounds = { x: el.bounds.x, y: newY, width: el.bounds.width, height: el.bounds.height }; | |
| i++; | |
| }); | |
| console.log("Spread vertically → gap = " + gap); | |
| } | |
| // ── Spread Horizontally: distribute centers evenly between leftmost & rightmost center ─ | |
| function spreadHorizontally() { | |
| var minX = null, maxX = null; | |
| mySelection.each(function (el) { | |
| var cx = el.bounds.x + el.bounds.width / 2; | |
| minX = (minX === null) ? cx : Math.min(minX, cx); | |
| maxX = (maxX === null) ? cx : Math.max(maxX, cx); | |
| }); | |
| var gap = (maxX - minX) / (selCount - 1); | |
| // Sort by current X so the visual order is preserved | |
| mySelection.sort(function (a, b) { return a.bounds.x - b.bounds.x; }); | |
| var i = 0; | |
| mySelection.each(function (el) { | |
| var newX = minX + i * gap - el.bounds.width / 2; | |
| el.bounds = { x: newX, y: el.bounds.y, width: el.bounds.width, height: el.bounds.height }; | |
| i++; | |
| }); | |
| console.log("Spread horizontally → gap = " + gap); | |
| } | |
| // ════════════════════════════════════════════════════════════════════════════════ | |
| // SWT DIALOG | |
| // ════════════════════════════════════════════════════════════════════════════════ | |
| var display = Display.getDefault(); | |
| // Main shell – SWT.SHELL_TRIM gives title-bar + close button; no resize | |
| var shell = new Shell(display, SWT.SHELL_TRIM | SWT.ON_TOP); | |
| shell.setText("Align & Spread (" + selCount + " elements)"); | |
| shell.setLayout(new GridLayout(1, false)); | |
| // ── Helper: create a labelled separator ───────────────────────────────────── | |
| function addSectionLabel(parent, text) { | |
| var lbl = new Label(parent, SWT.NONE); | |
| lbl.setText(text); | |
| lbl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); | |
| return lbl; | |
| } | |
| // ── Helper: create a row of buttons from a spec array ─────────────────────── | |
| // spec = [ { label, action }, … ] | |
| function addButtonRow(parent, spec) { | |
| var row = new Composite(parent, SWT.NONE); | |
| row.setLayout(new GridLayout(spec.length, true)); // equal-width columns | |
| row.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); | |
| for (var i = 0; i < spec.length; i++) { | |
| (function (item) { // closure to capture item | |
| var btn = new Button(row, SWT.PUSH); | |
| btn.setText(item.label); | |
| btn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); | |
| btn.addListener(SWT.Selection, function () { | |
| item.action(); // execute, dialog stays open | |
| }); | |
| })(spec[i]); | |
| } | |
| } | |
| // ── Section: Align ─────────────────────────────────────────────────────────── | |
| addSectionLabel(shell, "— Align —"); | |
| addButtonRow(shell, [ | |
| { label: "⬅ Left", action: alignLeft }, | |
| { label: "➡ Right", action: alignRight }, | |
| { label: "⬆ Top", action: alignTop }, | |
| { label: "⬇ Bottom", action: alignBottom } | |
| ]); | |
| addButtonRow(shell, [ | |
| { label: "↔ Center H", action: alignCenterHorizontal }, | |
| { label: "↕ Center V", action: alignCenterVertical } | |
| ]); | |
| // ── Section: Spread ────────────────────────────────────────────────────────── | |
| addSectionLabel(shell, "— Spread —"); | |
| addButtonRow(shell, [ | |
| { label: "↕ Vertically", action: spreadVertically }, | |
| { label: "↔ Horizontally", action: spreadHorizontally } | |
| ]); | |
| // ── ESC key → close (SWT.Traverse + TRAVERSE_ESCAPE is the reliable way) ──── | |
| shell.addListener(SWT.Traverse, function (event) { | |
| if (event.detail === SWT.TRAVERSE_ESCAPE) { | |
| event.doit = false; // consume the event | |
| shell.close(); | |
| } | |
| }); | |
| // ── Pack & show ────────────────────────────────────────────────────────────── | |
| shell.pack(); | |
| shell.open(); | |
| // ── Event loop: block until the dialog is closed ──────────────────────────── | |
| while (!shell.isDisposed()) { | |
| if (!display.readAndDispatch()) { | |
| display.sleep(); | |
| } | |
| } | |
| console.log("End: Align & Spread"); | |
| } // end of selCount >= 2 guard |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment