Skip to content

Instantly share code, notes, and snippets.

@romualdrichard
Last active February 4, 2026 15:06
Show Gist options
  • Select an option

  • Save romualdrichard/73080c49a3aeb108815e8c6d291cc5ea to your computer and use it in GitHub Desktop.

Select an option

Save romualdrichard/73080c49a3aeb108815e8c6d291cc5ea to your computer and use it in GitHub Desktop.
#jarchi Align and Spread with dialogbox
// 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