From c6313e392bee01c42a04e5a4fe2cfe723b93667f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Oct 2025 06:07:04 +0000 Subject: [PATCH 1/4] Initial plan From 5aa3c474eb6a97e7d2a8610a76c59151696d9cf1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Oct 2025 06:22:25 +0000 Subject: [PATCH 2/4] Add Display.isHeadless() method and create headless widget implementations Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com> --- .../org/eclipse/swt/widgets/Display.java | 17 + .../gtk/org/eclipse/swt/widgets/Display.java | 17 + .../org/eclipse/swt/widgets/Button.java | 141 ++++ .../org/eclipse/swt/widgets/Canvas.java | 58 ++ .../org/eclipse/swt/widgets/Composite.java | 209 ++++++ .../org/eclipse/swt/widgets/Control.java | 349 ++++++++++ .../org/eclipse/swt/widgets/Decorations.java | 110 ++++ .../org/eclipse/swt/widgets/Display.java | 605 ++++++++++++++++++ .../org/eclipse/swt/widgets/Label.java | 83 +++ .../org/eclipse/swt/widgets/Scrollable.java | 48 ++ .../org/eclipse/swt/widgets/Shell.java | 329 ++++++++++ .../org/eclipse/swt/widgets/Widget.java | 345 ++++++++++ .../org/eclipse/swt/widgets/Display.java | 17 + 13 files changed, 2328 insertions(+) create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Button.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Canvas.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Composite.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Control.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Decorations.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Display.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Label.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Scrollable.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Shell.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Widget.java diff --git a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java index abd8b2446dc..f769e24efe0 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Display.java @@ -1774,6 +1774,23 @@ public static boolean isSystemDarkTheme () { return OS.isSystemDarkAppearance(); } +/** + * Returns true if SWT is running in headless mode, else + * returns false. + *

+ * In headless mode, SWT widgets do not create native platform resources + * and operations are no-ops or return default values. + *

+ * + * @return true if SWT is running in headless mode, else + * returns false. + * + * @since 3.132 + */ +public static boolean isHeadless () { + return false; +} + int getLastEventTime () { NSEvent event = application != null ? application.currentEvent() : null; if (event == null) return 0; diff --git a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java index 5b52c106f48..5ae4b85a738 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/gtk/org/eclipse/swt/widgets/Display.java @@ -2613,6 +2613,23 @@ public static boolean isSystemDarkTheme () { return themeDark; } +/** + * Returns true if SWT is running in headless mode, else + * returns false. + *

+ * In headless mode, SWT widgets do not create native platform resources + * and operations are no-ops or return default values. + *

+ * + * @return true if SWT is running in headless mode, else + * returns false. + * + * @since 3.132 + */ +public static boolean isHeadless () { + return false; +} + int getLastEventTime () { return lastEventTime; } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Button.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Button.java new file mode 100644 index 00000000000..c835c8e73cf --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Button.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Button for SWT. + */ +public class Button extends Control { + String text = ""; + Image image; + boolean selected; + boolean grayed; + int alignment = SWT.CENTER; + +public Button(Composite parent, int style) { + super(parent, checkStyle(style)); +} + +static int checkStyle(int style) { + style = checkBits(style, SWT.PUSH, SWT.ARROW, SWT.CHECK, SWT.RADIO, SWT.TOGGLE, 0); + if ((style & (SWT.PUSH | SWT.TOGGLE)) != 0) { + return checkBits(style, SWT.CENTER, SWT.LEFT, SWT.RIGHT, 0, 0, 0); + } + if ((style & (SWT.CHECK | SWT.RADIO)) != 0) { + return checkBits(style, SWT.LEFT, SWT.RIGHT, SWT.CENTER, 0, 0, 0); + } + if ((style & SWT.ARROW) != 0) { + style |= SWT.NO_FOCUS; + return checkBits(style, SWT.UP, SWT.DOWN, SWT.LEFT, SWT.RIGHT, 0, 0); + } + return style; +} + +public void addSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener(listener); + addListener(SWT.Selection, typedListener); + addListener(SWT.DefaultSelection, typedListener); +} + +public int getAlignment() { + checkWidget(); + if ((style & SWT.ARROW) != 0) { + if ((style & SWT.UP) != 0) return SWT.UP; + if ((style & SWT.DOWN) != 0) return SWT.DOWN; + if ((style & SWT.LEFT) != 0) return SWT.LEFT; + if ((style & SWT.RIGHT) != 0) return SWT.RIGHT; + return SWT.UP; + } + return alignment; +} + +public boolean getGrayed() { + checkWidget(); + if ((style & SWT.CHECK) == 0) return false; + return grayed; +} + +public Image getImage() { + checkWidget(); + return image; +} + +public boolean getSelection() { + checkWidget(); + if ((style & (SWT.CHECK | SWT.RADIO | SWT.TOGGLE)) == 0) return false; + return selected; +} + +public String getText() { + checkWidget(); + return text; +} + +public void removeSelectionListener(SelectionListener listener) { + checkWidget(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + if (eventTable == null) return; + eventTable.unhook(SWT.Selection, listener); + eventTable.unhook(SWT.DefaultSelection, listener); +} + +public void setAlignment(int alignment) { + checkWidget(); + if ((style & SWT.ARROW) != 0) { + if ((style & (SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT)) == 0) return; + style &= ~(SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT); + style |= alignment & (SWT.UP | SWT.DOWN | SWT.LEFT | SWT.RIGHT); + return; + } + if ((alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER)) == 0) return; + this.alignment = alignment; +} + +public void setGrayed(boolean grayed) { + checkWidget(); + if ((style & SWT.CHECK) == 0) return; + this.grayed = grayed; +} + +public void setImage(Image image) { + checkWidget(); + if ((style & SWT.ARROW) != 0) return; + this.image = image; +} + +public void setSelection(boolean selected) { + checkWidget(); + if ((style & (SWT.CHECK | SWT.RADIO | SWT.TOGGLE)) == 0) return; + this.selected = selected; +} + +public void setText(String string) { + checkWidget(); + if (string == null) error(SWT.ERROR_NULL_ARGUMENT); + if ((style & SWT.ARROW) != 0) return; + text = string; +} + +@Override +String getNameText() { + return getText(); +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Canvas.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Canvas.java new file mode 100644 index 00000000000..853c01f8c2e --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Canvas.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Canvas for SWT. + */ +public class Canvas extends Composite { + Caret caret; + +public Canvas() { + // No-op +} + +public Canvas(Display display, int style) { + super(null, style); + this.display = display; +} + +public Canvas(Composite parent, int style) { + super(parent, style); +} + +public void drawBackground(GC gc, int x, int y, int width, int height) { + checkWidget(); + // No-op in headless mode +} + +public Caret getCaret() { + checkWidget(); + return caret; +} + +public void scroll(int destX, int destY, int x, int y, int width, int height, boolean all) { + checkWidget(); + // No-op in headless mode +} + +public void setCaret(Caret caret) { + checkWidget(); + this.caret = caret; +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Composite.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Composite.java new file mode 100644 index 00000000000..dd93791edf6 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Composite.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import java.util.*; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Composite for SWT. + */ +public class Composite extends Scrollable { + Layout layout; + Control[] tabList; + int layoutCount, backgroundMode; + List children = new ArrayList<>(); + +public Composite() { + // No-op +} + +public Composite(Composite parent, int style) { + super(parent, checkStyle(style)); +} + +static int checkStyle(int style) { + style &= ~SWT.TRANSPARENT; + return style; +} + +void addChild(Control control) { + children.add(control); +} + +void removeChild(Control control) { + children.remove(control); +} + +public Control[] getChildren() { + checkWidget(); + return children.toArray(new Control[children.size()]); +} + +public int getBackgroundMode() { + checkWidget(); + return backgroundMode; +} + +public Layout getLayout() { + checkWidget(); + return layout; +} + +public boolean getLayoutDeferred() { + checkWidget(); + return layoutCount > 0; +} + +public Control[] getTabList() { + checkWidget(); + if (tabList != null) return tabList; + int count = 0; + Control[] list = getChildren(); + for (Control child : list) { + if (child.isTabGroup()) count++; + } + Control[] result = new Control[count]; + int index = 0; + for (Control child : list) { + if (child.isTabGroup()) { + result[index++] = child; + } + } + return result; +} + +public boolean isLayoutDeferred() { + checkWidget(); + return findDeferredControl() != null; +} + +Composite findDeferredControl() { + return layoutCount > 0 ? this : (parent != null ? parent.findDeferredControl() : null); +} + +public void layout() { + checkWidget(); + layout(true); +} + +public void layout(boolean changed) { + checkWidget(); + layout(changed, false); +} + +public void layout(boolean changed, boolean all) { + checkWidget(); + if (layout == null) return; + if (layoutCount == 0) { + layout.layout(this, changed); + if (all) { + for (Control child : children) { + if (child instanceof Composite) { + ((Composite) child).layout(changed, all); + } + } + } + } +} + +public void layout(Control[] changed) { + checkWidget(); + if (changed == null) error(SWT.ERROR_INVALID_ARGUMENT); + layout(changed, SWT.NONE); +} + +public void layout(Control[] changed, int flags) { + checkWidget(); + if (changed != null) { + for (Control child : changed) { + if (child == null) error(SWT.ERROR_INVALID_ARGUMENT); + if (child.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + if (child.parent != this) error(SWT.ERROR_INVALID_PARENT); + } + } + if (layout == null) return; + if ((flags & SWT.DEFER) != 0) { + setLayoutDeferred(true); + return; + } + layout.layout(this, (flags & SWT.CHANGED) != 0); +} + +public void setBackgroundMode(int mode) { + checkWidget(); + backgroundMode = mode; +} + +public void setLayout(Layout layout) { + checkWidget(); + this.layout = layout; +} + +public void setLayoutDeferred(boolean defer) { + if (!defer) { + if (--layoutCount == 0) { + if ((state & LAYOUT_CHILD) != 0 || (state & LAYOUT_NEEDED) != 0) { + updateLayout(true); + } + } + } else { + layoutCount++; + } +} + +void updateLayout(boolean all) { + Composite parent = findDeferredControl(); + if (parent != null) { + parent.state |= LAYOUT_CHILD; + return; + } + if ((state & LAYOUT_NEEDED) != 0) { + boolean changed = (state & LAYOUT_CHANGED) != 0; + state &= ~(LAYOUT_NEEDED | LAYOUT_CHANGED); + layout(changed, all); + } + if (all) { + state &= ~LAYOUT_CHILD; + for (Control child : children) { + if (child instanceof Composite) { + ((Composite) child).updateLayout(all); + } + } + } +} + +public void setTabList(Control[] tabList) { + checkWidget(); + if (tabList != null) { + for (Control control : tabList) { + if (control == null) error(SWT.ERROR_INVALID_ARGUMENT); + if (control.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + if (control.parent != this) error(SWT.ERROR_INVALID_PARENT); + } + Control[] newList = new Control[tabList.length]; + System.arraycopy(tabList, 0, newList, 0, tabList.length); + tabList = newList; + } + this.tabList = tabList; +} + +@Override +protected void checkSubclass() { + // Allow subclassing +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Control.java new file mode 100644 index 00000000000..624864e71f3 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Control.java @@ -0,0 +1,349 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Control for SWT. + */ +public abstract class Control extends Widget implements Drawable { + Composite parent; + String toolTipText; + Object layoutData; + Menu menu; + Cursor cursor; + Font font; + Color foreground, background; + Image backgroundImage; + boolean enabled = true; + boolean visible = true; + int x, y, width, height; + +public Control() { + // No-op +} + +public Control(Composite parent, int style) { + super(parent, style); + this.parent = parent; + if (parent != null) { + parent.addChild(this); + } + width = DEFAULT_WIDTH; + height = DEFAULT_HEIGHT; +} + +public Point computeSize(int wHint, int hHint) { + return computeSize(wHint, hHint, true); +} + +public Point computeSize(int wHint, int hHint, boolean changed) { + checkWidget(); + int width = DEFAULT_WIDTH; + int height = DEFAULT_HEIGHT; + if (wHint != SWT.DEFAULT) width = wHint; + if (hHint != SWT.DEFAULT) height = hHint; + return new Point(width, height); +} + +public boolean forceFocus() { + checkWidget(); + return false; +} + +public Color getBackground() { + checkWidget(); + if (background != null) return background; + return display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); +} + +public Image getBackgroundImage() { + checkWidget(); + return backgroundImage; +} + +public Rectangle getBounds() { + checkWidget(); + return new Rectangle(x, y, width, height); +} + +public Cursor getCursor() { + checkWidget(); + return cursor; +} + +public boolean getEnabled() { + checkWidget(); + return enabled; +} + +public Font getFont() { + checkWidget(); + if (font != null) return font; + return parent != null ? parent.getFont() : display.getSystemFont(); +} + +public Color getForeground() { + checkWidget(); + if (foreground != null) return foreground; + return display.getSystemColor(SWT.COLOR_WIDGET_FOREGROUND); +} + +public Object getLayoutData() { + checkWidget(); + return layoutData; +} + +public Point getLocation() { + checkWidget(); + return new Point(x, y); +} + +public Menu getMenu() { + checkWidget(); + return menu; +} + +public Composite getParent() { + checkWidget(); + return parent; +} + +public Shell getShell() { + checkWidget(); + return parent != null ? parent.getShell() : null; +} + +public Point getSize() { + checkWidget(); + return new Point(width, height); +} + +public String getToolTipText() { + checkWidget(); + return toolTipText; +} + +public boolean getVisible() { + checkWidget(); + return visible; +} + +@Override +public boolean isEnabled() { + checkWidget(); + return getEnabled() && (parent != null ? parent.isEnabled() : true); +} + +public boolean isFocusControl() { + checkWidget(); + return display.focusControl == this; +} + +public boolean isVisible() { + checkWidget(); + return getVisible() && (parent != null ? parent.isVisible() : true); +} + +public boolean isReparentable() { + checkWidget(); + return true; +} + +boolean isTabGroup() { + Control[] tabList = parent.tabList; + if (tabList != null) { + for (Control control : tabList) { + if (control == this) return true; + } + } + int bits = SWT.NONE; + return (bits & style) != 0; +} + +public void moveAbove(Control control) { + checkWidget(); + // No-op in headless mode +} + +public void moveBelow(Control control) { + checkWidget(); + // No-op in headless mode +} + +public void pack() { + pack(true); +} + +public void pack(boolean changed) { + checkWidget(); + setSize(computeSize(SWT.DEFAULT, SWT.DEFAULT, changed)); +} + +public void redraw() { + checkWidget(); + // No-op in headless mode +} + +public void redraw(int x, int y, int width, int height, boolean all) { + checkWidget(); + // No-op in headless mode +} + +@Override +void releaseParent() { + if (parent != null) parent.removeChild(this); +} + +public boolean setFocus() { + checkWidget(); + if (!isEnabled() || !isVisible()) return false; + display.focusControl = this; + return true; +} + +public void setBackground(Color color) { + checkWidget(); + background = color; +} + +public void setBackgroundImage(Image image) { + checkWidget(); + backgroundImage = image; +} + +public void setBounds(int x, int y, int width, int height) { + checkWidget(); + this.x = x; + this.y = y; + this.width = width; + this.height = height; +} + +public void setBounds(Rectangle rect) { + checkWidget(); + if (rect == null) error(SWT.ERROR_NULL_ARGUMENT); + setBounds(rect.x, rect.y, rect.width, rect.height); +} + +public void setCursor(Cursor cursor) { + checkWidget(); + this.cursor = cursor; +} + +public void setEnabled(boolean enabled) { + checkWidget(); + this.enabled = enabled; +} + +public void setFont(Font font) { + checkWidget(); + this.font = font; +} + +public void setForeground(Color color) { + checkWidget(); + foreground = color; +} + +public void setLayoutData(Object layoutData) { + checkWidget(); + this.layoutData = layoutData; +} + +public void setLocation(int x, int y) { + checkWidget(); + this.x = x; + this.y = y; +} + +public void setLocation(Point location) { + checkWidget(); + if (location == null) error(SWT.ERROR_NULL_ARGUMENT); + setLocation(location.x, location.y); +} + +public void setMenu(Menu menu) { + checkWidget(); + this.menu = menu; +} + +public boolean setParent(Composite parent) { + checkWidget(); + if (parent == null) error(SWT.ERROR_NULL_ARGUMENT); + if (parent.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + if (this.parent == parent) return true; + if (this.parent != null) this.parent.removeChild(this); + this.parent = parent; + parent.addChild(this); + return true; +} + +public void setSize(int width, int height) { + checkWidget(); + this.width = width; + this.height = height; +} + +public void setSize(Point size) { + checkWidget(); + if (size == null) error(SWT.ERROR_NULL_ARGUMENT); + setSize(size.x, size.y); +} + +public void setToolTipText(String string) { + checkWidget(); + toolTipText = string; +} + +public void setVisible(boolean visible) { + checkWidget(); + this.visible = visible; +} + +public Point toControl(int x, int y) { + checkWidget(); + return new Point(x - this.x, y - this.y); +} + +public Point toControl(Point point) { + checkWidget(); + if (point == null) error(SWT.ERROR_NULL_ARGUMENT); + return toControl(point.x, point.y); +} + +public Point toDisplay(int x, int y) { + checkWidget(); + return new Point(x + this.x, y + this.y); +} + +public Point toDisplay(Point point) { + checkWidget(); + if (point == null) error(SWT.ERROR_NULL_ARGUMENT); + return toDisplay(point.x, point.y); +} + +public void update() { + checkWidget(); + // No-op in headless mode +} + +@Override +public boolean isAutoScalable() { + return true; +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Decorations.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Decorations.java new file mode 100644 index 00000000000..695358e194b --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Decorations.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Decorations for SWT. + */ +public class Decorations extends Canvas { + String text = ""; + Image image; + Image[] images = new Image[0]; + Menu menuBar; + Button defaultButton, saveDefault; + +public Decorations() { + // No-op +} + +public Decorations(Display display, int style) { + super(display, style); +} + +public Decorations(Composite parent, int style) { + super(parent, style); +} + +public Button getDefaultButton() { + checkWidget(); + return defaultButton; +} + +public Image getImage() { + checkWidget(); + return image; +} + +public Image[] getImages() { + checkWidget(); + if (images == null) return new Image[0]; + Image[] result = new Image[images.length]; + System.arraycopy(images, 0, result, 0, images.length); + return result; +} + +public Menu getMenuBar() { + checkWidget(); + return menuBar; +} + +public String getText() { + checkWidget(); + return text; +} + +public void setDefaultButton(Button button) { + checkWidget(); + if (button != null) { + if (button.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + if (button.getShell() != this) error(SWT.ERROR_INVALID_PARENT); + if ((button.style & SWT.PUSH) == 0) error(SWT.ERROR_INVALID_ARGUMENT); + } + defaultButton = button; +} + +public void setImage(Image image) { + checkWidget(); + this.image = image; +} + +public void setImages(Image[] images) { + checkWidget(); + if (images != null) { + this.images = new Image[images.length]; + System.arraycopy(images, 0, this.images, 0, images.length); + } else { + this.images = new Image[0]; + } +} + +public void setMenuBar(Menu menu) { + checkWidget(); + if (menuBar == menu) return; + if (menu != null) { + if ((menu.style & SWT.BAR) == 0) error(SWT.ERROR_MENU_NOT_BAR); + if (menu.parent != this) error(SWT.ERROR_INVALID_PARENT); + } + menuBar = menu; +} + +public void setText(String string) { + checkWidget(); + if (string == null) error(SWT.ERROR_NULL_ARGUMENT); + text = string; +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Display.java new file mode 100644 index 00000000000..0d0ed884998 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Display.java @@ -0,0 +1,605 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import java.util.*; +import java.util.concurrent.*; +import java.util.function.*; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Display for SWT. + * This implementation provides no-op or default implementations + * for running SWT code in a headless environment. + */ +public class Display extends Device implements Executor { + + static Display Default; + static String APP_NAME = "SWT"; + static String APP_VERSION = ""; + + /* Widget Table */ + Widget[] widgetTable; + int freeSlot; + static final int GROW_SIZE = 1024; + + /* Synchronization */ + Synchronizer synchronizer = new Synchronizer(this); + Consumer runtimeExceptionHandler = DefaultExceptionHandler.RUNTIME_EXCEPTION_HANDLER; + Consumer errorHandler = DefaultExceptionHandler.RUNTIME_ERROR_HANDLER; + Thread thread; + + /* Shells */ + Shell[] shells = new Shell[0]; + Shell activeShell; + + /* Modality */ + Shell[] modalShells; + + /* Disposal */ + Runnable[] disposeList; + + /* Event Handling */ + EventTable eventTable, filterTable; + Event[] eventQueue; + + /* Timers */ + int[] timerIds; + Runnable[] timerList; + int nextTimerId = 1; + + /* Deferred Layout */ + Composite[] layoutDeferred; + int layoutDeferredCount; + + /* System Resources */ + Font systemFont; + + /* Focus */ + Control focusControl; + + /* Disposed flag */ + boolean disposed; + + /* Package prefix */ + static final String PACKAGE_PREFIX = "org.eclipse.swt.widgets."; + +public Display() { + this(null); +} + +public Display(DeviceData data) { + super(data); + thread = Thread.currentThread(); + synchronized (Device.class) { + if (Default == null) Default = this; + } + widgetTable = new Widget[GROW_SIZE]; +} + +@Override +protected void create(DeviceData data) { + checkSubclass(); + synchronizer = new Synchronizer(this); + systemFont = new Font(this, new FontData("Sans", 10, SWT.NORMAL)); +} + +@Override +protected void init() { + super.init(); +} + +public void addFilter(int eventType, Listener listener) { + checkDevice(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + if (filterTable == null) filterTable = new EventTable(); + filterTable.hook(eventType, listener); +} + +public void addListener(int eventType, Listener listener) { + checkDevice(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + if (eventTable == null) eventTable = new EventTable(); + eventTable.hook(eventType, listener); +} + +public void asyncExec(Runnable runnable) { + synchronized (Device.class) { + if (isDisposed()) error(SWT.ERROR_DEVICE_DISPOSED); + synchronizer.asyncExec(runnable); + } +} + +@Override +public void execute(Runnable runnable) { + Objects.requireNonNull(runnable); + if (thread == Thread.currentThread()) { + runnable.run(); + } else { + syncExec(runnable); + } +} + +public void beep() { + checkDevice(); + // No-op in headless mode +} + +@Override +public void close() { + checkDevice(); + Event event = new Event(); + sendEvent(SWT.Close, event); + if (event.doit) dispose(); +} + +@Override +protected void destroy() { + if (disposed) return; + disposed = true; + Shell[] shells = getShells(); + for (Shell shell : shells) { + if (!shell.isDisposed()) shell.dispose(); + } +} + +public void disposeExec(Runnable runnable) { + checkDevice(); + if (disposeList == null) disposeList = new Runnable[4]; + for (int i = 0; i < disposeList.length; i++) { + if (disposeList[i] == null) { + disposeList[i] = runnable; + return; + } + } + Runnable[] newList = new Runnable[disposeList.length + 4]; + System.arraycopy(disposeList, 0, newList, 0, disposeList.length); + newList[disposeList.length] = runnable; + disposeList = newList; +} + +public static Display findDisplay(Thread thread) { + synchronized (Device.class) { + if (Default != null && Default.thread == thread) { + return Default; + } + return null; + } +} + +public Widget findWidget(long handle) { + checkDevice(); + return null; +} + +public Widget findWidget(long handle, long id) { + checkDevice(); + return null; +} + +public Widget findWidget(Widget widget, long id) { + checkDevice(); + return null; +} + +public Shell getActiveShell() { + checkDevice(); + return activeShell; +} + +public static String getAppName() { + return APP_NAME; +} + +public static String getAppVersion() { + return APP_VERSION; +} + +public Rectangle getBounds() { + checkDevice(); + return new Rectangle(0, 0, 1024, 768); +} + +public Rectangle getClientArea() { + checkDevice(); + return new Rectangle(0, 0, 1024, 768); +} + +public static Display getCurrent() { + return findDisplay(Thread.currentThread()); +} + +public Control getCursorControl() { + checkDevice(); + return null; +} + +public Point getCursorLocation() { + checkDevice(); + return new Point(0, 0); +} + +public Point[] getCursorSizes() { + checkDevice(); + return new Point[] { new Point(16, 16), new Point(32, 32) }; +} + +public Object getData(String key) { + checkDevice(); + if (key == null) error(SWT.ERROR_NULL_ARGUMENT); + return super.getData(key); +} + +public Object getData() { + checkDevice(); + return super.getData(); +} + +public static Display getDefault() { + synchronized (Device.class) { + if (Default == null) Default = new Display(); + return Default; + } +} + +public int getDismissalAlignment() { + checkDevice(); + return SWT.LEFT; +} + +public int getDoubleClickTime() { + checkDevice(); + return 500; +} + +public Control getFocusControl() { + checkDevice(); + return focusControl; +} + +public boolean getHighContrast() { + checkDevice(); + return false; +} + +public int getDepth() { + return 24; +} + +public int getIconDepth() { + return 24; +} + +public Point[] getIconSizes() { + checkDevice(); + return new Point[] { new Point(16, 16), new Point(32, 32) }; +} + +public Menu getMenuBar() { + checkDevice(); + return null; +} + +public Monitor[] getMonitors() { + checkDevice(); + Monitor monitor = new Monitor(); + Rectangle bounds = new Rectangle(0, 0, 1024, 768); + monitor.handle = 0; + monitor.x = bounds.x; + monitor.y = bounds.y; + monitor.width = bounds.width; + monitor.height = bounds.height; + monitor.clientX = bounds.x; + monitor.clientY = bounds.y; + monitor.clientWidth = bounds.width; + monitor.clientHeight = bounds.height; + monitor.zoom = 100; + return new Monitor[] { monitor }; +} + +public Monitor getPrimaryMonitor() { + checkDevice(); + return getMonitors()[0]; +} + +public Shell[] getShells() { + checkDevice(); + return shells; +} + +public Thread getSyncThread() { + synchronized (Device.class) { + if (isDisposed()) error(SWT.ERROR_DEVICE_DISPOSED); + return synchronizer.syncThread; + } +} + +@Override +public Font getSystemFont() { + checkDevice(); + return systemFont; +} + +public Thread getThread() { + synchronized (Device.class) { + if (isDisposed()) error(SWT.ERROR_DEVICE_DISPOSED); + return thread; + } +} + +public static boolean isSystemDarkTheme() { + return false; +} + +public static boolean isHeadless() { + return true; +} + +public boolean readAndDispatch() { + checkDevice(); + return runAsyncMessages(false); +} + +@Override +protected void release() { + sendEvent(SWT.Dispose, new Event()); + Shell[] shells = getShells(); + for (Shell shell : shells) { + if (!shell.isDisposed()) shell.dispose(); + } + if (eventTable != null) eventTable.unhook(SWT.Dispose, null); + super.release(); + releaseDisplay(); +} + +void releaseDisplay() { + if (disposeList != null) { + for (Runnable runnable : disposeList) { + if (runnable != null) { + try { + runnable.run(); + } catch (RuntimeException exception) { + runtimeExceptionHandler.accept(exception); + } catch (Error error) { + errorHandler.accept(error); + } + } + } + } + disposeList = null; + synchronizer.releaseSynchronizer(); + synchronizer = null; + thread = null; + widgetTable = null; +} + +void removeWidget(long handle) { + // No-op in headless mode +} + +public void removeFilter(int eventType, Listener listener) { + checkDevice(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + if (filterTable == null) return; + filterTable.unhook(eventType, listener); + if (filterTable.size() == 0) filterTable = null; +} + +public void removeListener(int eventType, Listener listener) { + checkDevice(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + if (eventTable == null) return; + eventTable.unhook(eventType, listener); +} + +boolean runAsyncMessages(boolean all) { + return synchronizer.runAsyncMessages(all); +} + +boolean runDeferredLayouts() { + if (layoutDeferredCount != 0) { + Composite[] temp = layoutDeferred; + int count = layoutDeferredCount; + layoutDeferred = null; + layoutDeferredCount = 0; + for (int i = 0; i < count; i++) { + Composite comp = temp[i]; + if (!comp.isDisposed()) comp.setLayoutDeferred(false); + } + return true; + } + return false; +} + +boolean runDeferredEvents() { + boolean run = false; + if (runDeferredLayouts()) run = true; + return run; +} + +void sendEvent(int eventType, Event event) { + if (eventTable == null && filterTable == null) return; + if (event == null) event = new Event(); + event.display = this; + event.type = eventType; + if (event.time == 0) event.time = (int) System.currentTimeMillis(); + if (filterTable != null) filterTable.sendEvent(event); + if (eventTable != null) eventTable.sendEvent(event); +} + +public static void setAppName(String name) { + APP_NAME = name; +} + +public static void setAppVersion(String version) { + APP_VERSION = version; +} + +public void setData(String key, Object value) { + checkDevice(); + if (key == null) error(SWT.ERROR_NULL_ARGUMENT); + super.setData(key, value); +} + +public void setData(Object data) { + checkDevice(); + super.setData(data); +} + +public void setRuntimeExceptionHandler(Consumer handler) { + checkDevice(); + if (handler == null) error(SWT.ERROR_NULL_ARGUMENT); + runtimeExceptionHandler = handler; +} + +public void setErrorHandler(Consumer handler) { + checkDevice(); + if (handler == null) error(SWT.ERROR_NULL_ARGUMENT); + errorHandler = handler; +} + +public boolean sleep() { + checkDevice(); + return runAsyncMessages(false) || runDeferredEvents(); +} + +public void syncExec(Runnable runnable) { + synchronized (Device.class) { + if (isDisposed()) error(SWT.ERROR_DEVICE_DISPOSED); + synchronizer.syncExec(runnable); + } +} + +public int timerExec(int milliseconds, Runnable runnable) { + checkDevice(); + if (runnable == null) error(SWT.ERROR_NULL_ARGUMENT); + if (timerList == null) { + timerList = new Runnable[4]; + timerIds = new int[4]; + } + int timerId = nextTimerId++; + int index = 0; + while (index < timerList.length) { + if (timerList[index] == null) break; + index++; + } + if (index == timerList.length) { + Runnable[] newTimerList = new Runnable[timerList.length + 4]; + System.arraycopy(timerList, 0, newTimerList, 0, timerList.length); + timerList = newTimerList; + int[] newTimerIds = new int[timerIds.length + 4]; + System.arraycopy(timerIds, 0, newTimerIds, 0, timerIds.length); + timerIds = newTimerIds; + } + timerList[index] = runnable; + timerIds[index] = timerId; + + // Schedule the timer to run + new Timer().schedule(new TimerTask() { + @Override + public void run() { + asyncExec(runnable); + } + }, milliseconds); + + return timerId; +} + +public void wake() { + synchronized (Device.class) { + if (isDisposed()) error(SWT.ERROR_DEVICE_DISPOSED); + // No-op in headless mode + } +} + +void addShell(Shell shell) { + int length = shells.length; + Shell[] newShells = new Shell[length + 1]; + System.arraycopy(shells, 0, newShells, 0, length); + newShells[length] = shell; + shells = newShells; +} + +void removeShell(Shell shell) { + int length = shells.length; + for (int i = 0; i < length; i++) { + if (shells[i] == shell) { + Shell[] newShells = new Shell[length - 1]; + System.arraycopy(shells, 0, newShells, 0, i); + System.arraycopy(shells, i + 1, newShells, i, length - i - 1); + shells = newShells; + break; + } + } +} + +void addLayoutDeferred(Composite comp) { + if (layoutDeferred == null) layoutDeferred = new Composite[64]; + if (layoutDeferredCount == layoutDeferred.length) { + Composite[] temp = new Composite[layoutDeferred.length + 64]; + System.arraycopy(layoutDeferred, 0, temp, 0, layoutDeferred.length); + layoutDeferred = temp; + } + layoutDeferred[layoutDeferredCount++] = comp; +} + +void removeLayoutDeferred(Composite comp) { + if (layoutDeferred == null) return; + for (int i = 0; i < layoutDeferredCount; i++) { + if (layoutDeferred[i] == comp) { + layoutDeferredCount--; + System.arraycopy(layoutDeferred, i + 1, layoutDeferred, i, layoutDeferredCount - i); + layoutDeferred[layoutDeferredCount] = null; + break; + } + } +} + +boolean filterEvent(Event event) { + if (filterTable != null) { + filterTable.sendEvent(event); + } + return false; +} + +boolean filters(int eventType) { + if (filterTable == null) return false; + return filterTable.hooks(eventType); +} + +void postEvent(Event event) { + if (eventQueue == null) eventQueue = new Event[4]; + int index = 0; + int length = eventQueue.length; + while (index < length) { + if (eventQueue[index] == null) break; + index++; + } + if (index == length) { + Event[] newQueue = new Event[length + 4]; + System.arraycopy(eventQueue, 0, newQueue, 0, length); + eventQueue = newQueue; + } + eventQueue[index] = event; +} + +static boolean isValidClass(Class clazz) { + String name = clazz.getName(); + int index = name.lastIndexOf('.'); + return name.substring(0, index + 1).equals(PACKAGE_PREFIX); +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Label.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Label.java new file mode 100644 index 00000000000..b8a14659b83 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Label.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Label for SWT. + */ +public class Label extends Control { + String text = ""; + Image image; + int alignment = SWT.LEFT; + +public Label(Composite parent, int style) { + super(parent, checkStyle(style)); +} + +static int checkStyle(int style) { + style |= SWT.NO_FOCUS; + if ((style & SWT.SEPARATOR) != 0) { + style = checkBits(style, SWT.VERTICAL, SWT.HORIZONTAL, 0, 0, 0, 0); + return checkBits(style, SWT.SHADOW_OUT, SWT.SHADOW_IN, SWT.SHADOW_NONE, 0, 0, 0); + } + return checkBits(style, SWT.LEFT, SWT.CENTER, SWT.RIGHT, 0, 0, 0); +} + +public int getAlignment() { + checkWidget(); + if ((style & SWT.SEPARATOR) != 0) return 0; + return alignment; +} + +public Image getImage() { + checkWidget(); + return image; +} + +public String getText() { + checkWidget(); + return text; +} + +public void setAlignment(int alignment) { + checkWidget(); + if ((style & SWT.SEPARATOR) != 0) return; + if ((alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER)) == 0) return; + this.alignment = alignment & (SWT.LEFT | SWT.RIGHT | SWT.CENTER); +} + +public void setImage(Image image) { + checkWidget(); + if ((style & SWT.SEPARATOR) != 0) return; + this.image = image; + this.text = ""; +} + +public void setText(String string) { + checkWidget(); + if (string == null) error(SWT.ERROR_NULL_ARGUMENT); + if ((style & SWT.SEPARATOR) != 0) return; + text = string; + this.image = null; +} + +@Override +String getNameText() { + return getText(); +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Scrollable.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Scrollable.java new file mode 100644 index 00000000000..d81382dfda8 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Scrollable.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Scrollable for SWT. + */ +public abstract class Scrollable extends Control { + ScrollBar horizontalBar, verticalBar; + +public Scrollable() { + // No-op +} + +public Scrollable(Composite parent, int style) { + super(parent, style); +} + +public Rectangle getClientArea() { + checkWidget(); + return new Rectangle(0, 0, width, height); +} + +public ScrollBar getHorizontalBar() { + checkWidget(); + return horizontalBar; +} + +public ScrollBar getVerticalBar() { + checkWidget(); + return verticalBar; +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Shell.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Shell.java new file mode 100644 index 00000000000..465fe127ba4 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Shell.java @@ -0,0 +1,329 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Shell for SWT. + */ +public class Shell extends Decorations { + boolean opened; + String text = ""; + Image image; + Image[] images = new Image[0]; + int alpha = 255; + Region region; + int minWidth = 0, minHeight = 0; + int maxWidth = Integer.MAX_VALUE, maxHeight = Integer.MAX_VALUE; + boolean modified; + boolean fullScreen; + +public Shell() { + this((Display) null, SWT.SHELL_TRIM); +} + +public Shell(int style) { + this((Display) null, style); +} + +public Shell(Display display) { + this(display, SWT.SHELL_TRIM); +} + +public Shell(Display display, int style) { + super(display, checkStyle(display, style)); + if (display == null) display = Display.getCurrent(); + if (display == null) display = Display.getDefault(); + this.display = display; + createWidget(); +} + +public Shell(Shell parent) { + this(parent, SWT.DIALOG_TRIM); +} + +public Shell(Shell parent, int style) { + super(parent, checkStyle(parent, style)); + if (parent != null) { + this.display = parent.display; + if (this.parent == null) this.parent = parent; + } + createWidget(); +} + +void createWidget() { + display.addShell(this); + width = 200; + height = 200; +} + +static int checkStyle(Display display, int style) { + style = checkBits(style, SWT.ON_TOP, SWT.TOOL, SWT.NO_TRIM, 0, 0, 0); + if ((style & (SWT.ON_TOP | SWT.TOOL)) != 0) { + if ((style & SWT.CLOSE) != 0) style |= SWT.TITLE; + } + if ((style & SWT.NO_TRIM) != 0) { + return style & ~(SWT.CLOSE | SWT.TITLE | SWT.MIN | SWT.MAX | SWT.RESIZE | SWT.BORDER); + } + if ((style & (SWT.CLOSE | SWT.TITLE | SWT.MIN | SWT.MAX | SWT.RESIZE | SWT.BORDER)) == 0) { + style |= SWT.CLOSE | SWT.TITLE | SWT.MIN | SWT.MAX | SWT.RESIZE | SWT.BORDER; + } + return style; +} + +static int checkStyle(Shell parent, int style) { + if ((style & SWT.ON_TOP) != 0) style &= ~SWT.SHELL_TRIM; + int mask = SWT.SYSTEM_MODAL | SWT.APPLICATION_MODAL | SWT.PRIMARY_MODAL; + if ((style & SWT.SHEET) != 0) { + style &= ~SWT.SHEET; + style |= (parent != null) ? SWT.DIALOG_TRIM : SWT.NONE; + if ((style & mask) == 0) { + style |= (parent != null) ? SWT.APPLICATION_MODAL : SWT.NONE; + } + } + int bits = style & ~mask; + if ((style & SWT.SYSTEM_MODAL) != 0) return bits | SWT.SYSTEM_MODAL; + if ((style & SWT.APPLICATION_MODAL) != 0) return bits | SWT.APPLICATION_MODAL; + if ((style & SWT.PRIMARY_MODAL) != 0) return bits | SWT.PRIMARY_MODAL; + return bits; +} + +public void addShellListener(ShellListener listener) { + checkWidget(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener(listener); + addListener(SWT.Close, typedListener); + addListener(SWT.Iconify, typedListener); + addListener(SWT.Deiconify, typedListener); + addListener(SWT.Activate, typedListener); + addListener(SWT.Deactivate, typedListener); +} + +public void close() { + checkWidget(); + Event event = new Event(); + sendEvent(SWT.Close, event); + if (event.doit && !isDisposed()) dispose(); +} + +public void dispose() { + if (isDisposed()) return; + display.removeShell(this); + super.dispose(); +} + +public int getAlpha() { + checkWidget(); + return alpha; +} + +public Rectangle getBounds() { + checkWidget(); + return new Rectangle(x, y, width, height); +} + +public boolean getFullScreen() { + checkWidget(); + return fullScreen; +} + +public Image getImage() { + checkWidget(); + return image; +} + +public Image[] getImages() { + checkWidget(); + if (images == null) return new Image[0]; + Image[] result = new Image[images.length]; + System.arraycopy(images, 0, result, 0, images.length); + return result; +} + +public boolean getMaximized() { + checkWidget(); + return false; +} + +public Point getMaximumSize() { + checkWidget(); + return new Point(maxWidth, maxHeight); +} + +public boolean getMinimized() { + checkWidget(); + return false; +} + +public Point getMinimumSize() { + checkWidget(); + return new Point(minWidth, minHeight); +} + +public boolean getModified() { + checkWidget(); + return modified; +} + +public Region getRegion() { + checkWidget(); + return region; +} + +@Override +public Shell getShell() { + checkWidget(); + return this; +} + +public Shell[] getShells() { + checkWidget(); + int count = 0; + Shell[] allShells = display.getShells(); + for (Shell shell : allShells) { + Control control = shell; + while (control != null && control != this) { + control = control.parent; + } + if (control == this) count++; + } + int index = 0; + Shell[] result = new Shell[count]; + for (Shell shell : allShells) { + Control control = shell; + while (control != null && control != this) { + control = control.parent; + } + if (control == this) { + result[index++] = shell; + } + } + return result; +} + +public String getText() { + checkWidget(); + return text; +} + +public void open() { + checkWidget(); + setVisible(true); + opened = true; +} + +public void removeShellListener(ShellListener listener) { + checkWidget(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + if (eventTable == null) return; + eventTable.unhook(SWT.Close, listener); + eventTable.unhook(SWT.Iconify, listener); + eventTable.unhook(SWT.Deiconify, listener); + eventTable.unhook(SWT.Activate, listener); + eventTable.unhook(SWT.Deactivate, listener); +} + +public void setActive() { + checkWidget(); + display.activeShell = this; +} + +public void setAlpha(int alpha) { + checkWidget(); + this.alpha = alpha; +} + +public void setEnabled(boolean enabled) { + checkWidget(); + this.enabled = enabled; +} + +public void setFullScreen(boolean fullScreen) { + checkWidget(); + this.fullScreen = fullScreen; +} + +public void setImage(Image image) { + checkWidget(); + this.image = image; +} + +public void setImages(Image[] images) { + checkWidget(); + if (images != null) { + this.images = new Image[images.length]; + System.arraycopy(images, 0, this.images, 0, images.length); + } else { + this.images = new Image[0]; + } +} + +public void setMaximized(boolean maximized) { + checkWidget(); + // No-op in headless mode +} + +public void setMaximumSize(int width, int height) { + checkWidget(); + maxWidth = width; + maxHeight = height; +} + +public void setMaximumSize(Point size) { + checkWidget(); + if (size == null) error(SWT.ERROR_NULL_ARGUMENT); + setMaximumSize(size.x, size.y); +} + +public void setMinimized(boolean minimized) { + checkWidget(); + // No-op in headless mode +} + +public void setMinimumSize(int width, int height) { + checkWidget(); + minWidth = width; + minHeight = height; +} + +public void setMinimumSize(Point size) { + checkWidget(); + if (size == null) error(SWT.ERROR_NULL_ARGUMENT); + setMinimumSize(size.x, size.y); +} + +public void setModified(boolean modified) { + checkWidget(); + this.modified = modified; +} + +public void setRegion(Region region) { + checkWidget(); + this.region = region; +} + +public void setText(String string) { + checkWidget(); + if (string == null) error(SWT.ERROR_NULL_ARGUMENT); + text = string; +} + +@Override +protected void checkSubclass() { + // Allow subclassing +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Widget.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Widget.java new file mode 100644 index 00000000000..fd6a5c15a9f --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Widget.java @@ -0,0 +1,345 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.events.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Widget for SWT. + */ +public abstract class Widget { + public long handle; + int style, state; + Display display; + EventTable eventTable; + Object data; + + /* Global state flags */ + static final int DISPOSED = 1<<0; + static final int CANVAS = 1<<1; + static final int KEYED_DATA = 1<<2; + static final int HANDLE = 1<<3; + static final int DISABLED = 1<<4; + static final int MENU = 1<<5; + static final int OBSCURED = 1<<6; + static final int MOVED = 1<<7; + static final int RESIZED = 1<<8; + static final int ZERO_WIDTH = 1<<9; + static final int ZERO_HEIGHT = 1<<10; + static final int HIDDEN = 1<<11; + static final int FOREGROUND = 1<<12; + static final int BACKGROUND = 1<<13; + static final int FONT = 1<<14; + static final int PARENT_BACKGROUND = 1<<15; + static final int THEME_BACKGROUND = 1<<16; + static final int LAYOUT_NEEDED = 1<<17; + static final int LAYOUT_CHANGED = 1<<18; + static final int LAYOUT_CHILD = 1<<19; + static final int RELEASED = 1<<20; + static final int DISPOSE_SENT = 1<<21; + static final int FOREIGN_HANDLE = 1<<22; + static final int DRAG_DETECT = 1<<23; + static final int SKIN_NEEDED = 1<<24; + + /* Default sizes */ + static final int DEFAULT_WIDTH = 64; + static final int DEFAULT_HEIGHT = 64; + +public Widget() { + // No-op +} + +public Widget(Widget parent, int style) { + checkSubclass(); + checkParent(parent); + this.style = style; + display = parent.display; +} + +protected void checkSubclass() { + if (!isValidSubclass()) error(SWT.ERROR_INVALID_SUBCLASS); +} + +protected void checkParent(Widget parent) { + if (parent == null) error(SWT.ERROR_NULL_ARGUMENT); + if (parent.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + if (parent.display != display) error(SWT.ERROR_INVALID_PARENT); +} + +protected void checkWidget() { + Display display = this.display; + if (display == null) error(SWT.ERROR_WIDGET_DISPOSED); + if (display.thread != Thread.currentThread()) error(SWT.ERROR_THREAD_INVALID_ACCESS); + if ((state & DISPOSED) != 0) error(SWT.ERROR_WIDGET_DISPOSED); +} + +static int checkBits(int style, int int0, int int1, int int2, int int3, int int4, int int5) { + int mask = int0 | int1 | int2 | int3 | int4 | int5; + if ((style & mask) == 0) style |= int0; + if ((style & int0) != 0) style = (style & ~mask) | int0; + if ((style & int1) != 0) style = (style & ~mask) | int1; + if ((style & int2) != 0) style = (style & ~mask) | int2; + if ((style & int3) != 0) style = (style & ~mask) | int3; + if ((style & int4) != 0) style = (style & ~mask) | int4; + if ((style & int5) != 0) style = (style & ~mask) | int5; + return style; +} + +public void addListener(int eventType, Listener listener) { + checkWidget(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + _addListener(eventType, listener); +} + +void _addListener(int eventType, Listener listener) { + if (eventTable == null) eventTable = new EventTable(); + eventTable.hook(eventType, listener); +} + +public void addDisposeListener(DisposeListener listener) { + checkWidget(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + TypedListener typedListener = new TypedListener(listener); + addListener(SWT.Dispose, typedListener); +} + +public void dispose() { + if (isDisposed()) return; + if (!isValidThread()) error(SWT.ERROR_THREAD_INVALID_ACCESS); + release(true); +} + +void error(int code) { + SWT.error(code); +} + +public Object getData() { + checkWidget(); + return (state & KEYED_DATA) != 0 ? ((Object[]) data)[0] : data; +} + +public Object getData(String key) { + checkWidget(); + if (key == null) error(SWT.ERROR_NULL_ARGUMENT); + if ((state & KEYED_DATA) != 0) { + Object[] table = (Object[]) data; + for (int i = 1; i < table.length; i += 2) { + if (key.equals(table[i])) return table[i + 1]; + } + } + return null; +} + +public Display getDisplay() { + Display display = this.display; + if (display == null) error(SWT.ERROR_WIDGET_DISPOSED); + return display; +} + +public int getStyle() { + checkWidget(); + return style; +} + +public boolean isDisposed() { + return (state & DISPOSED) != 0; +} + +public boolean isListening(int eventType) { + checkWidget(); + return eventTable != null && eventTable.hooks(eventType); +} + +boolean isValidSubclass() { + return Display.isValidClass(getClass()); +} + +boolean isValidThread() { + return getDisplay().thread == Thread.currentThread(); +} + +public void notifyListeners(int eventType, Event event) { + checkWidget(); + if (event == null) event = new Event(); + sendEvent(eventType, event); +} + +void postEvent(int eventType) { + sendEvent(eventType, null, false); +} + +void postEvent(int eventType, Event event) { + sendEvent(eventType, event, false); +} + +void release(boolean destroy) { + if ((state & DISPOSE_SENT) == 0) { + state |= DISPOSE_SENT; + sendEvent(SWT.Dispose); + } + if ((state & DISPOSED) == 0) { + releaseChildren(destroy); + } + if ((state & RELEASED) == 0) { + state |= RELEASED; + if (destroy) { + releaseParent(); + releaseWidget(); + } + } +} + +void releaseChildren(boolean destroy) { + // No-op +} + +void releaseHandle() { + handle = 0; + state |= DISPOSED; +} + +void releaseParent() { + // No-op +} + +void releaseWidget() { + eventTable = null; + data = null; +} + +public void removeListener(int eventType, Listener listener) { + checkWidget(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + if (eventTable == null) return; + eventTable.unhook(eventType, listener); +} + +public void removeDisposeListener(DisposeListener listener) { + checkWidget(); + if (listener == null) error(SWT.ERROR_NULL_ARGUMENT); + if (eventTable == null) return; + eventTable.unhook(SWT.Dispose, listener); +} + +void sendEvent(Event event) { + Display display = event.display = this.display; + if (!display.filterEvent(event)) { + if (eventTable != null) eventTable.sendEvent(event); + } +} + +void sendEvent(int eventType) { + sendEvent(eventType, null, true); +} + +void sendEvent(int eventType, Event event) { + sendEvent(eventType, event, true); +} + +void sendEvent(int eventType, Event event, boolean send) { + if (eventTable == null && !display.filters(eventType)) { + return; + } + if (event == null) event = new Event(); + event.type = eventType; + event.display = display; + event.widget = this; + if (event.time == 0) { + event.time = (int) System.currentTimeMillis(); + } + if (send) { + sendEvent(event); + } else { + display.postEvent(event); + } +} + +public void setData(Object data) { + checkWidget(); + if ((state & KEYED_DATA) != 0) { + ((Object[]) this.data)[0] = data; + } else { + this.data = data; + } +} + +public void setData(String key, Object value) { + checkWidget(); + if (key == null) error(SWT.ERROR_NULL_ARGUMENT); + int index = 1; + Object[] table = null; + if ((state & KEYED_DATA) != 0) { + table = (Object[]) data; + while (index < table.length) { + if (key.equals(table[index])) break; + index += 2; + } + } + if (value != null) { + if ((state & KEYED_DATA) != 0) { + if (index == table.length) { + Object[] newTable = new Object[table.length + 2]; + System.arraycopy(table, 0, newTable, 0, table.length); + data = table = newTable; + } + } else { + table = new Object[3]; + table[0] = data; + data = table; + state |= KEYED_DATA; + } + table[index] = key; + table[index + 1] = value; + } else { + if ((state & KEYED_DATA) != 0) { + if (index != table.length) { + int length = table.length - 2; + if (length == 1) { + data = table[0]; + state &= ~KEYED_DATA; + } else { + Object[] newTable = new Object[length]; + System.arraycopy(table, 0, newTable, 0, index); + System.arraycopy(table, index + 2, newTable, index, length - index); + data = newTable; + } + } + } + } +} + +@Override +public String toString() { + String string = "*Disposed*"; + if (!isDisposed()) { + string = "*Wrong Thread*"; + if (isValidThread()) string = getNameText(); + } + return getName() + " {" + string + "}"; +} + +String getName() { + String string = getClass().getName(); + int index = string.lastIndexOf('.'); + if (index == -1) return string; + return string.substring(index + 1, string.length()); +} + +String getNameText() { + return ""; +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java index 12ed1c2f9d5..5f04b065275 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java @@ -2187,6 +2187,23 @@ public static boolean isSystemDarkTheme () { return isDarkTheme; } +/** + * Returns true if SWT is running in headless mode, else + * returns false. + *

+ * In headless mode, SWT widgets do not create native platform resources + * and operations are no-ops or return default values. + *

+ * + * @return true if SWT is running in headless mode, else + * returns false. + * + * @since 3.132 + */ +public static boolean isHeadless () { + return false; +} + int getLastEventTime () { return OS.GetMessageTime (); } From d2ec7b0dddc3f17fd594b6899b67c29ce1cb67c3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Oct 2025 06:26:07 +0000 Subject: [PATCH 3/4] Create org.eclipse.swt.headless fragment and supporting classes Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com> --- binaries/org.eclipse.swt.headless/.project | 87 ++++++++ .../META-INF/MANIFEST.MF | 25 +++ binaries/org.eclipse.swt.headless/README.md | 211 ++++++++++++++++++ .../org.eclipse.swt.headless/build.properties | 29 +++ .../forceQualifierUpdate.txt | 2 + .../fragment.properties | 12 + binaries/pom.xml | 1 + .../org/eclipse/swt/widgets/Caret.java | 129 +++++++++++ .../org/eclipse/swt/widgets/Item.java | 56 +++++ .../org/eclipse/swt/widgets/Menu.java | 148 ++++++++++++ .../org/eclipse/swt/widgets/MenuItem.java | 115 ++++++++++ .../org/eclipse/swt/widgets/ScrollBar.java | 164 ++++++++++++++ 12 files changed, 979 insertions(+) create mode 100644 binaries/org.eclipse.swt.headless/.project create mode 100644 binaries/org.eclipse.swt.headless/META-INF/MANIFEST.MF create mode 100644 binaries/org.eclipse.swt.headless/README.md create mode 100644 binaries/org.eclipse.swt.headless/build.properties create mode 100644 binaries/org.eclipse.swt.headless/forceQualifierUpdate.txt create mode 100644 binaries/org.eclipse.swt.headless/fragment.properties create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Caret.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Item.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Menu.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/MenuItem.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/ScrollBar.java diff --git a/binaries/org.eclipse.swt.headless/.project b/binaries/org.eclipse.swt.headless/.project new file mode 100644 index 00000000000..7fe95cb6c47 --- /dev/null +++ b/binaries/org.eclipse.swt.headless/.project @@ -0,0 +1,87 @@ + + + org.eclipse.swt.headless + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.api.tools.apiAnalysisBuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.pde.PluginNature + org.eclipse.pde.api.tools.apiAnalysisNature + + + + Eclipse SWT + 2 + SWT_HOST_PLUGIN/Eclipse%20SWT + + + Eclipse SWT Accessibility + 2 + SWT_HOST_PLUGIN/Eclipse%20SWT%20Accessibility + + + Eclipse SWT AWT + 2 + SWT_HOST_PLUGIN/Eclipse%20SWT%20AWT + + + Eclipse SWT Browser + 2 + SWT_HOST_PLUGIN/Eclipse%20SWT%20Browser + + + Eclipse SWT Custom Widgets + 2 + SWT_HOST_PLUGIN/Eclipse%20SWT%20Custom%20Widgets + + + Eclipse SWT Drag and Drop + 2 + SWT_HOST_PLUGIN/Eclipse%20SWT%20Drag%20and%20Drop + + + Eclipse SWT OpenGL + 2 + SWT_HOST_PLUGIN/Eclipse%20SWT%20OpenGL + + + Eclipse SWT Printing + 2 + SWT_HOST_PLUGIN/Eclipse%20SWT%20Printing + + + Eclipse SWT Program + 2 + SWT_HOST_PLUGIN/Eclipse%20SWT%20Program + + + + + SWT_HOST_PLUGIN + $%7BPARENT-2-PROJECT_LOC%7D/bundles/org.eclipse.swt + + + diff --git a/binaries/org.eclipse.swt.headless/META-INF/MANIFEST.MF b/binaries/org.eclipse.swt.headless/META-INF/MANIFEST.MF new file mode 100644 index 00000000000..5362e503eb7 --- /dev/null +++ b/binaries/org.eclipse.swt.headless/META-INF/MANIFEST.MF @@ -0,0 +1,25 @@ +Manifest-Version: 1.0 +Fragment-Host: org.eclipse.swt;bundle-version="[3.128.0,4.0.0)" +Bundle-Name: %fragmentName +Bundle-Vendor: %providerName +Bundle-SymbolicName: org.eclipse.swt.headless; singleton:=true +Bundle-Version: 3.132.0.qualifier +Bundle-ManifestVersion: 2 +Bundle-Localization: fragment +Export-Package: + org.eclipse.swt, + org.eclipse.swt.accessibility, + org.eclipse.swt.awt, + org.eclipse.swt.browser, + org.eclipse.swt.custom, + org.eclipse.swt.dnd, + org.eclipse.swt.events, + org.eclipse.swt.graphics, + org.eclipse.swt.layout, + org.eclipse.swt.opengl, + org.eclipse.swt.printing, + org.eclipse.swt.program, + org.eclipse.swt.widgets, + org.eclipse.swt.internal; x-friends:="org.eclipse.ui", + org.eclipse.swt.internal.image; x-internal:=true +Automatic-Module-Name: org.eclipse.swt.headless diff --git a/binaries/org.eclipse.swt.headless/README.md b/binaries/org.eclipse.swt.headless/README.md new file mode 100644 index 00000000000..c66dcc66456 --- /dev/null +++ b/binaries/org.eclipse.swt.headless/README.md @@ -0,0 +1,211 @@ +# SWT Headless Fragment + +## Overview + +The `org.eclipse.swt.headless` fragment provides a headless implementation of SWT (Standard Widget Toolkit) that allows applications to run without requiring a native windowing system or display. This is similar to Java AWT's headless mode (`java.awt.headless`). + +## Purpose + +The headless fragment is useful for: + +- **Server-side applications** that need to reference UI code but don't display a UI +- **Testing** UI applications without requiring a display +- **Continuous Integration** environments where no display is available +- **Headless rendering** of UI layouts for documentation or screenshots +- **Command-line tools** that use SWT libraries but don't require actual UI display + +## Key Features + +### Detection + +You can check if SWT is running in headless mode: + +```java +if (Display.isHeadless()) { + // Running in headless mode +} +``` + +### Behavior + +In headless mode: + +- **Widget creation**: Widgets can be created and configured normally +- **State management**: Widget properties (text, selection, bounds, etc.) are stored and can be retrieved +- **Layout**: Layout managers work normally and calculate sizes +- **Parent-child relationships**: Composite widgets maintain their child lists correctly +- **Events**: Event listeners can be registered (though events are not automatically triggered) +- **No rendering**: No actual native widgets are created, no drawing occurs +- **No user interaction**: Mouse and keyboard events don't occur naturally + +## Supported Widgets + +The headless implementation provides the following core widgets: + +### Containers +- `Display` - The main display object +- `Shell` - Top-level window +- `Composite` - Container for other controls +- `Canvas` - Drawing surface (no actual drawing) +- `Decorations` - Base for decorated containers + +### Controls +- `Button` - Push button, checkbox, radio, toggle, arrow +- `Label` - Text or image label +- `Control` - Base class for all controls + +### Supporting Classes +- `Widget` - Base class for all widgets +- `Scrollable` - Base for scrollable controls +- `Menu` - Menu bar and popup menus +- `MenuItem` - Individual menu items +- `Item` - Base for items +- `Caret` - Text cursor +- `ScrollBar` - Scrollbar widget + +## Limitations + +The headless implementation has the following limitations: + +### Not Implemented + +The following features are **not** currently implemented: + +1. **Native rendering**: No actual drawing to screen or images +2. **User input**: No keyboard or mouse events +3. **System integration**: + - No system tray + - No clipboard + - No drag and drop + - No native file/color/font dialogs +4. **Advanced widgets**: Many complex widgets are not yet implemented: + - Table, Tree, List + - Text, StyledText + - Browser + - And many others +5. **Graphics operations**: GC (Graphics Context) drawing operations are no-ops +6. **Images**: Image loading and manipulation is limited +7. **Fonts and colors**: System fonts and colors return defaults + +### Default Behavior + +- `Display.getDefault()` creates a headless display +- `Display.isHeadless()` returns `true` +- Widget methods that can't be implemented return default values: + - `computeSize()` returns reasonable defaults (64x64 or based on content) + - `getBounds()` returns stored bounds + - System queries return sensible defaults +- Operations that require native resources are no-ops: + - `redraw()` does nothing + - `update()` does nothing + - Event loops don't wait for user input + +## Usage Example + +```java +// Create a headless display +Display display = new Display(); +System.out.println("Headless: " + Display.isHeadless()); // true + +// Create a shell +Shell shell = new Shell(display); +shell.setText("Test Shell"); +shell.setSize(400, 300); + +// Create controls +Composite composite = new Composite(shell, SWT.NONE); +composite.setLayout(new FillLayout()); + +Button button = new Button(composite, SWT.PUSH); +button.setText("Click Me"); + +Label label = new Label(composite, SWT.NONE); +label.setText("Hello Headless SWT!"); + +// Layout works normally +shell.layout(); + +// Can query widget properties +System.out.println("Button text: " + button.getText()); +System.out.println("Label text: " + label.getText()); +System.out.println("Shell size: " + shell.getSize()); + +// Cleanup +shell.dispose(); +display.dispose(); +``` + +## Installation + +The headless fragment is automatically available when: + +1. The `org.eclipse.swt` bundle is present +2. The `org.eclipse.swt.headless` fragment is in the classpath +3. No platform-specific fragment (gtk, cocoa, win32) is available + +The OSGi framework will automatically select the headless fragment when no native platform is available. + +## Building + +The headless fragment is built as part of the normal SWT build process: + +```bash +mvn clean install +``` + +The fragment is located in `binaries/org.eclipse.swt.headless/`. + +## Contributing + +When extending the headless implementation: + +1. **Keep it simple**: Headless implementations should be minimal +2. **Store and return**: Store values set via setters, return them from getters +3. **No-op when necessary**: Operations that require native resources should be no-ops +4. **Parent-child tracking**: Maintain widget hierarchies properly +5. **Follow patterns**: Look at existing headless widgets for consistency + +### Adding New Widgets + +To add a new widget to the headless implementation: + +1. Create the widget class in `bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/` +2. Implement the public API from the platform-specific versions +3. Store state in private fields +4. Handle parent-child relationships for Composite widgets +5. Return sensible defaults for queries + +## Testing + +The headless implementation can be tested using JUnit tests that don't require a display: + +```java +@Test +public void testHeadlessButton() { + Display display = new Display(); + assertTrue(Display.isHeadless()); + + Shell shell = new Shell(display); + Button button = new Button(shell, SWT.PUSH); + button.setText("Test"); + + assertEquals("Test", button.getText()); + assertFalse(button.getSelection()); + + button.setSelection(true); + assertTrue(button.getSelection()); + + shell.dispose(); + display.dispose(); +} +``` + +## See Also + +- [Java AWT Headless Mode](https://www.oracle.com/technical-resources/articles/javase/headless.html) +- [SWT API Documentation](https://help.eclipse.org/latest/index.jsp) +- [Eclipse SWT Project](https://www.eclipse.org/swt/) + +## License + +Eclipse Public License 2.0 (EPL-2.0) diff --git a/binaries/org.eclipse.swt.headless/build.properties b/binaries/org.eclipse.swt.headless/build.properties new file mode 100644 index 00000000000..cd19887ad25 --- /dev/null +++ b/binaries/org.eclipse.swt.headless/build.properties @@ -0,0 +1,29 @@ +############################################################################### +# Copyright (c) 2025 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### +custom = true +bin.includes = .,fragment.properties +source.. = \ + ../../bundles/org.eclipse.swt/Eclipse SWT/headless,\ + ../../bundles/org.eclipse.swt/Eclipse SWT/common,\ + ../../bundles/org.eclipse.swt/Eclipse SWT/emulated/bidi,\ + ../../bundles/org.eclipse.swt/Eclipse SWT/emulated/coolbar,\ + ../../bundles/org.eclipse.swt/Eclipse SWT/emulated/taskbar,\ + ../../bundles/org.eclipse.swt/Eclipse SWT/emulated/tooltip,\ + ../../bundles/org.eclipse.swt/Eclipse SWT/emulated/expand,\ + ../../bundles/org.eclipse.swt/Eclipse SWT Accessibility/common,\ + ../../bundles/org.eclipse.swt/Eclipse SWT AWT/common,\ + ../../bundles/org.eclipse.swt/Eclipse SWT Drag and Drop/common,\ + ../../bundles/org.eclipse.swt/Eclipse SWT Printing/common,\ + ../../bundles/org.eclipse.swt/Eclipse SWT Program/common,\ + ../../bundles/org.eclipse.swt/Eclipse SWT Custom Widgets/common,\ + ../../bundles/org.eclipse.swt/Eclipse SWT Browser/common,\ + ../../bundles/org.eclipse.swt/Eclipse SWT OpenGL/common +output.. = bin/ diff --git a/binaries/org.eclipse.swt.headless/forceQualifierUpdate.txt b/binaries/org.eclipse.swt.headless/forceQualifierUpdate.txt new file mode 100644 index 00000000000..c656da0edbc --- /dev/null +++ b/binaries/org.eclipse.swt.headless/forceQualifierUpdate.txt @@ -0,0 +1,2 @@ +# To force a version qualifier update add the bug here +Bug 577498 - Initial headless implementation diff --git a/binaries/org.eclipse.swt.headless/fragment.properties b/binaries/org.eclipse.swt.headless/fragment.properties new file mode 100644 index 00000000000..33896261db6 --- /dev/null +++ b/binaries/org.eclipse.swt.headless/fragment.properties @@ -0,0 +1,12 @@ +############################################################################### +# Copyright (c) 2025 IBM Corporation and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# IBM Corporation - initial API and implementation +############################################################################### +fragmentName = Standard Widget Toolkit Headless Implementation +providerName = Eclipse.org diff --git a/binaries/pom.xml b/binaries/pom.xml index 78946f0f1b8..09d839e6372 100644 --- a/binaries/pom.xml +++ b/binaries/pom.xml @@ -40,6 +40,7 @@ org.eclipse.swt.gtk.linux.x86_64 org.eclipse.swt.win32.win32.aarch64 org.eclipse.swt.win32.win32.x86_64 + org.eclipse.swt.headless diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Caret.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Caret.java new file mode 100644 index 00000000000..226f6470d75 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Caret.java @@ -0,0 +1,129 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Caret for SWT. + */ +public class Caret extends Widget { + Canvas parent; + Image image; + Font font; + int x, y, width, height; + boolean visible; + +public Caret(Canvas parent, int style) { + super(parent, style); + this.parent = parent; + width = 1; + height = 10; +} + +public Rectangle getBounds() { + checkWidget(); + return new Rectangle(x, y, width, height); +} + +public Font getFont() { + checkWidget(); + return font; +} + +public Image getImage() { + checkWidget(); + return image; +} + +public Point getLocation() { + checkWidget(); + return new Point(x, y); +} + +public Canvas getParent() { + checkWidget(); + return parent; +} + +public Point getSize() { + checkWidget(); + return new Point(width, height); +} + +public boolean getVisible() { + checkWidget(); + return visible; +} + +public boolean isVisible() { + checkWidget(); + return visible && parent.isVisible(); +} + +public void setBounds(int x, int y, int width, int height) { + checkWidget(); + this.x = x; + this.y = y; + this.width = width; + this.height = height; +} + +public void setBounds(Rectangle rect) { + checkWidget(); + if (rect == null) error(SWT.ERROR_NULL_ARGUMENT); + setBounds(rect.x, rect.y, rect.width, rect.height); +} + +public void setFont(Font font) { + checkWidget(); + this.font = font; +} + +public void setImage(Image image) { + checkWidget(); + this.image = image; +} + +public void setLocation(int x, int y) { + checkWidget(); + this.x = x; + this.y = y; +} + +public void setLocation(Point location) { + checkWidget(); + if (location == null) error(SWT.ERROR_NULL_ARGUMENT); + setLocation(location.x, location.y); +} + +public void setSize(int width, int height) { + checkWidget(); + this.width = width; + this.height = height; +} + +public void setSize(Point size) { + checkWidget(); + if (size == null) error(SWT.ERROR_NULL_ARGUMENT); + setSize(size.x, size.y); +} + +public void setVisible(boolean visible) { + checkWidget(); + this.visible = visible; +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Item.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Item.java new file mode 100644 index 00000000000..552791f9071 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Item.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of Item for SWT. + */ +public abstract class Item extends Widget { + String text = ""; + Image image; + +public Item(Widget parent, int style) { + super(parent, style); +} + +public Image getImage() { + checkWidget(); + return image; +} + +public String getText() { + checkWidget(); + return text; +} + +public void setImage(Image image) { + checkWidget(); + this.image = image; +} + +public void setText(String string) { + checkWidget(); + if (string == null) error(SWT.ERROR_NULL_ARGUMENT); + text = string; +} + +@Override +String getNameText() { + return getText(); +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Menu.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Menu.java new file mode 100644 index 00000000000..ee5c3fd12c8 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/Menu.java @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import java.util.*; + +/** + * Headless implementation of Menu for SWT. + */ +public class Menu extends Widget { + Decorations parent; + MenuItem[] items = new MenuItem[0]; + int x, y; + boolean visible; + +public Menu(Control parent) { + this(parent, SWT.POP_UP); +} + +public Menu(Control parent, int style) { + super(parent, checkStyle(style)); +} + +public Menu(Decorations parent, int style) { + super(parent, checkStyle(style)); + this.parent = parent; +} + +public Menu(Menu parentMenu) { + this(parentMenu, SWT.DROP_DOWN); +} + +public Menu(Menu parentMenu, int style) { + super(parentMenu, checkStyle(style)); +} + +public Menu(MenuItem parentItem) { + this(parentItem.parent, SWT.DROP_DOWN); +} + +static int checkStyle(int style) { + return checkBits(style, SWT.POP_UP, SWT.BAR, SWT.DROP_DOWN, 0, 0, 0); +} + +public int getItemCount() { + checkWidget(); + return items.length; +} + +public MenuItem getItem(int index) { + checkWidget(); + if (index < 0 || index >= items.length) error(SWT.ERROR_INVALID_RANGE); + return items[index]; +} + +public MenuItem[] getItems() { + checkWidget(); + MenuItem[] result = new MenuItem[items.length]; + System.arraycopy(items, 0, result, 0, items.length); + return result; +} + +public Decorations getParent() { + checkWidget(); + return parent; +} + +public Shell getShell() { + checkWidget(); + return parent != null ? parent.getShell() : null; +} + +public boolean getVisible() { + checkWidget(); + return visible; +} + +public int indexOf(MenuItem item) { + checkWidget(); + if (item == null) error(SWT.ERROR_NULL_ARGUMENT); + for (int i = 0; i < items.length; i++) { + if (items[i] == item) return i; + } + return -1; +} + +public boolean isEnabled() { + checkWidget(); + return (state & DISABLED) == 0; +} + +public boolean isVisible() { + checkWidget(); + return getVisible(); +} + +public void setEnabled(boolean enabled) { + checkWidget(); + if (enabled) { + state &= ~DISABLED; + } else { + state |= DISABLED; + } +} + +public void setLocation(int x, int y) { + checkWidget(); + this.x = x; + this.y = y; +} + +public void setLocation(org.eclipse.swt.graphics.Point location) { + checkWidget(); + if (location == null) error(SWT.ERROR_NULL_ARGUMENT); + setLocation(location.x, location.y); +} + +public void setVisible(boolean visible) { + checkWidget(); + this.visible = visible; +} + +void addItem(MenuItem item) { + MenuItem[] newItems = new MenuItem[items.length + 1]; + System.arraycopy(items, 0, newItems, 0, items.length); + newItems[items.length] = item; + items = newItems; +} + +void removeItem(MenuItem item) { + List list = new ArrayList<>(Arrays.asList(items)); + list.remove(item); + items = list.toArray(new MenuItem[list.size()]); +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/MenuItem.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/MenuItem.java new file mode 100644 index 00000000000..2490dd5c171 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/MenuItem.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of MenuItem for SWT. + */ +public class MenuItem extends Item { + Menu parent, menu; + boolean selected; + int accelerator; + boolean enabled = true; + +public MenuItem(Menu parent, int style) { + super(parent, checkStyle(style)); + this.parent = parent; + parent.addItem(this); +} + +public MenuItem(Menu parent, int style, int index) { + super(parent, checkStyle(style)); + this.parent = parent; + parent.addItem(this); +} + +static int checkStyle(int style) { + return checkBits(style, SWT.PUSH, SWT.CHECK, SWT.RADIO, SWT.SEPARATOR, SWT.CASCADE, 0); +} + +public int getAccelerator() { + checkWidget(); + return accelerator; +} + +public boolean getEnabled() { + checkWidget(); + return enabled; +} + +public Menu getMenu() { + checkWidget(); + return menu; +} + +public Menu getParent() { + checkWidget(); + return parent; +} + +public boolean getSelection() { + checkWidget(); + if ((style & (SWT.CHECK | SWT.RADIO)) == 0) return false; + return selected; +} + +public void setAccelerator(int accelerator) { + checkWidget(); + this.accelerator = accelerator; +} + +public void setEnabled(boolean enabled) { + checkWidget(); + this.enabled = enabled; +} + +public void setImage(Image image) { + checkWidget(); + super.setImage(image); +} + +public void setMenu(Menu menu) { + checkWidget(); + if ((style & SWT.CASCADE) == 0) return; + if (menu != null) { + if (menu.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT); + if ((menu.style & SWT.DROP_DOWN) == 0) { + error(SWT.ERROR_MENU_NOT_DROP_DOWN); + } + } + this.menu = menu; +} + +public void setSelection(boolean selected) { + checkWidget(); + if ((style & (SWT.CHECK | SWT.RADIO)) == 0) return; + this.selected = selected; +} + +public void setText(String string) { + checkWidget(); + if (string == null) error(SWT.ERROR_NULL_ARGUMENT); + super.setText(string); +} + +@Override +void releaseParent() { + super.releaseParent(); + if (parent != null) parent.removeItem(this); +} + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/ScrollBar.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/ScrollBar.java new file mode 100644 index 00000000000..944c80b40d1 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/widgets/ScrollBar.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.widgets; + +import org.eclipse.swt.*; +import org.eclipse.swt.graphics.*; + +/** + * Headless implementation of ScrollBar for SWT. + */ +public class ScrollBar extends Widget { + Scrollable parent; + int selection, minimum, maximum, thumb, increment, pageIncrement; + boolean enabled = true, visible = true; + +public ScrollBar(Scrollable parent, int style) { + super(parent, checkStyle(style)); + this.parent = parent; + maximum = 100; + thumb = 10; + increment = 1; + pageIncrement = 10; +} + +static int checkStyle(int style) { + return checkBits(style, SWT.HORIZONTAL, SWT.VERTICAL, 0, 0, 0, 0); +} + +public boolean getEnabled() { + checkWidget(); + return enabled; +} + +public int getIncrement() { + checkWidget(); + return increment; +} + +public int getMaximum() { + checkWidget(); + return maximum; +} + +public int getMinimum() { + checkWidget(); + return minimum; +} + +public int getPageIncrement() { + checkWidget(); + return pageIncrement; +} + +public Scrollable getParent() { + checkWidget(); + return parent; +} + +public int getSelection() { + checkWidget(); + return selection; +} + +public Point getSize() { + checkWidget(); + return new Point(10, 10); +} + +public int getThumb() { + checkWidget(); + return thumb; +} + +public boolean getVisible() { + checkWidget(); + return visible; +} + +public boolean isEnabled() { + checkWidget(); + return getEnabled() && parent.isEnabled(); +} + +public boolean isVisible() { + checkWidget(); + return getVisible() && parent.isVisible(); +} + +public void setEnabled(boolean enabled) { + checkWidget(); + this.enabled = enabled; +} + +public void setIncrement(int value) { + checkWidget(); + if (value < 1) return; + increment = value; +} + +public void setMaximum(int value) { + checkWidget(); + if (value < 0) return; + maximum = value; +} + +public void setMinimum(int value) { + checkWidget(); + if (value < 0) return; + minimum = value; +} + +public void setPageIncrement(int value) { + checkWidget(); + if (value < 1) return; + pageIncrement = value; +} + +public void setSelection(int value) { + checkWidget(); + if (value < minimum) value = minimum; + if (value > maximum - thumb) value = maximum - thumb; + selection = value; +} + +public void setThumb(int value) { + checkWidget(); + if (value < 1) return; + thumb = value; +} + +public void setValues(int selection, int minimum, int maximum, int thumb, int increment, int pageIncrement) { + checkWidget(); + if (minimum < 0) return; + if (maximum < 0) return; + if (thumb < 1) return; + if (increment < 1) return; + if (pageIncrement < 1) return; + this.minimum = minimum; + this.maximum = maximum; + this.thumb = thumb; + this.increment = increment; + this.pageIncrement = pageIncrement; + if (selection < minimum) selection = minimum; + if (selection > maximum - thumb) selection = maximum - thumb; + this.selection = selection; +} + +public void setVisible(boolean visible) { + checkWidget(); + this.visible = visible; +} + +} From ad377a0c2992dba3f9296cde6a8a8952bac0c7be Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Oct 2025 06:30:47 +0000 Subject: [PATCH 4/4] Add Device and DeviceData classes for headless implementation Co-authored-by: laeubi <1331477+laeubi@users.noreply.github.com> --- .../org/eclipse/swt/graphics/Device.java | 270 ++++++++++++++++++ .../org/eclipse/swt/graphics/DeviceData.java | 30 ++ 2 files changed, 300 insertions(+) create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/graphics/Device.java create mode 100644 bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/graphics/DeviceData.java diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/graphics/Device.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/graphics/Device.java new file mode 100644 index 00000000000..4e8ff494490 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/graphics/Device.java @@ -0,0 +1,270 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.graphics; + +import org.eclipse.swt.*; + +/** + * Headless implementation of Device for SWT. + */ +public abstract class Device implements Drawable { + + protected static final int CHANGE_SCALEFACTOR = 1; + + public static boolean DEBUG; + boolean debug = DEBUG; + boolean tracking = DEBUG; + Error[] errors; + Object[] objects; + Object trackingLock; + + volatile boolean disposed; + + int warningLevel; + + static Device[] Devices = new Device[4]; + + static final Color COLOR_BLACK, COLOR_DARK_RED, COLOR_DARK_GREEN, COLOR_DARK_YELLOW, COLOR_DARK_BLUE; + static final Color COLOR_DARK_MAGENTA, COLOR_DARK_CYAN, COLOR_GRAY, COLOR_DARK_GRAY, COLOR_RED, COLOR_TRANSPARENT; + static final Color COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE; + + static { + COLOR_TRANSPARENT = new Color(0xFF, 0xFF, 0xFF, 0); + COLOR_BLACK = new Color(0, 0, 0); + COLOR_DARK_RED = new Color(0x80, 0, 0); + COLOR_DARK_GREEN = new Color(0, 0x80, 0); + COLOR_DARK_YELLOW = new Color(0x80, 0x80, 0); + COLOR_DARK_BLUE = new Color(0, 0, 0x80); + COLOR_DARK_MAGENTA = new Color(0x80, 0, 0x80); + COLOR_DARK_CYAN = new Color(0, 0x80, 0x80); + COLOR_GRAY = new Color(0xC0, 0xC0, 0xC0); + COLOR_DARK_GRAY = new Color(0x80, 0x80, 0x80); + COLOR_RED = new Color(0xFF, 0, 0); + COLOR_GREEN = new Color(0, 0xFF, 0); + COLOR_YELLOW = new Color(0xFF, 0xFF, 0); + COLOR_BLUE = new Color(0, 0, 0xFF); + COLOR_MAGENTA = new Color(0xFF, 0, 0xFF); + COLOR_CYAN = new Color(0, 0xFF, 0xFF); + COLOR_WHITE = new Color(0xFF, 0xFF, 0xFF); + } + + Font systemFont; + Point dpi; + + protected static Device CurrentDevice; + protected static Runnable DeviceFinder; + static { + try { + Class.forName("org.eclipse.swt.widgets.Display"); + } catch (ClassNotFoundException e) {} + } + + static synchronized Device getDevice() { + if (DeviceFinder != null) DeviceFinder.run(); + Device device = CurrentDevice; + CurrentDevice = null; + return device; + } + + public Device() { + this(null); + } + + public Device(DeviceData data) { + synchronized (Device.class) { + if (data != null) { + debug = data.debug; + tracking = data.tracking; + } + if (tracking) { + startTracking(); + } + create(data); + init(); + register(this); + } + } + + protected void checkDevice() { + if (disposed) SWT.error(SWT.ERROR_DEVICE_DISPOSED); + } + + protected void create(DeviceData data) { + } + + protected void destroy() { + } + + public void dispose() { + synchronized (Device.class) { + if (isDisposed()) return; + checkDevice(); + release(); + destroy(); + deregister(this); + disposed = true; + if (tracking) { + stopTracking(); + } + } + } + + void dispose_Object(Object object) { + for (int i = 0; i < objects.length; i++) { + if (objects[i] == object) { + objects[i] = null; + errors[i] = null; + return; + } + } + } + + static void deregister(Device device) { + synchronized (Device.class) { + for (int i = 0; i < Devices.length; i++) { + if (device == Devices[i]) Devices[i] = null; + } + } + } + + public Rectangle getBounds() { + checkDevice(); + return new Rectangle(0, 0, 1024, 768); + } + + public Rectangle getClientArea() { + return getBounds(); + } + + public int getDepth() { + return 24; + } + + public Point getDPI() { + checkDevice(); + if (dpi == null) { + dpi = new Point(96, 96); + } + return new Point(dpi.x, dpi.y); + } + + public FontData[] getFontList(String faceName, boolean scalable) { + checkDevice(); + return new FontData[0]; + } + + public Color getSystemColor(int id) { + checkDevice(); + switch (id) { + case SWT.COLOR_BLACK: return COLOR_BLACK; + case SWT.COLOR_DARK_RED: return COLOR_DARK_RED; + case SWT.COLOR_DARK_GREEN: return COLOR_DARK_GREEN; + case SWT.COLOR_DARK_YELLOW: return COLOR_DARK_YELLOW; + case SWT.COLOR_DARK_BLUE: return COLOR_DARK_BLUE; + case SWT.COLOR_DARK_MAGENTA: return COLOR_DARK_MAGENTA; + case SWT.COLOR_DARK_CYAN: return COLOR_DARK_CYAN; + case SWT.COLOR_GRAY: return COLOR_GRAY; + case SWT.COLOR_DARK_GRAY: return COLOR_DARK_GRAY; + case SWT.COLOR_RED: return COLOR_RED; + case SWT.COLOR_GREEN: return COLOR_GREEN; + case SWT.COLOR_YELLOW: return COLOR_YELLOW; + case SWT.COLOR_BLUE: return COLOR_BLUE; + case SWT.COLOR_MAGENTA: return COLOR_MAGENTA; + case SWT.COLOR_CYAN: return COLOR_CYAN; + case SWT.COLOR_WHITE: return COLOR_WHITE; + case SWT.COLOR_TRANSPARENT: return COLOR_TRANSPARENT; + } + return COLOR_BLACK; + } + + public Font getSystemFont() { + checkDevice(); + if (systemFont == null) { + systemFont = new Font(this, new FontData("Sans", 10, SWT.NORMAL)); + } + return systemFont; + } + + public int getWarnings() { + checkDevice(); + return warningLevel; + } + + protected void init() { + } + + @Override + public abstract boolean isAutoScalable(); + + public boolean isDisposed() { + synchronized (Device.class) { + return disposed; + } + } + + Object[] new_Object(int size) { + return new Object[size]; + } + + protected void release() { + if (tracking) { + synchronized (trackingLock) { + for (int i = 0; i < objects.length; i++) { + if (objects[i] != null) { + System.err.println("WARNING: Resource leaked: " + objects[i]); + } + } + objects = null; + errors = null; + } + } + } + + static synchronized void register(Device device) { + for (int i = 0; i < Devices.length; i++) { + if (Devices[i] == null) { + Devices[i] = device; + return; + } + } + Device[] newDevices = new Device[Devices.length + 4]; + System.arraycopy(Devices, 0, newDevices, 0, Devices.length); + newDevices[Devices.length] = device; + Devices = newDevices; + } + + public void setWarnings(boolean warnings) { + checkDevice(); + warningLevel = warnings ? 1 : 0; + } + + void startTracking() { + synchronized (Device.class) { + if (trackingLock == null) { + trackingLock = new Object(); + objects = new Object[128]; + errors = new Error[128]; + } + } + } + + void stopTracking() { + synchronized (Device.class) { + objects = null; + errors = null; + trackingLock = null; + } + } + +} diff --git a/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/graphics/DeviceData.java b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/graphics/DeviceData.java new file mode 100644 index 00000000000..e3e76e6b271 --- /dev/null +++ b/bundles/org.eclipse.swt/Eclipse SWT/headless/org/eclipse/swt/graphics/DeviceData.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2025 IBM Corporation and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package org.eclipse.swt.graphics; + +/** + * Headless implementation of DeviceData for SWT. + */ +public class DeviceData { + + public String display_name; + public String application_name; + public String application_class; + + public boolean debug; + public boolean tracking; + public Error[] errors; + public Object[] objects; + +}