All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.diffplug.common.swt.SwtMisc Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 DiffPlug
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.diffplug.common.swt;


import com.diffplug.common.base.Box;
import com.diffplug.common.base.Errors;
import com.diffplug.common.base.Preconditions;
import com.diffplug.common.collect.Lists;
import com.diffplug.common.rx.Rx;
import com.diffplug.common.swt.os.OS;
import com.diffplug.common.tree.TreeDef;
import com.diffplug.common.tree.TreeIterable;
import com.diffplug.common.tree.TreeQuery;
import com.diffplug.common.tree.TreeStream;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import javax.annotation.Nullable;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Widget;

/** Miscellaneous SWT functions. */
public class SwtMisc {
	/////////////////////
	// True miscellany //
	/////////////////////
	/** Returns true if `flag` is set in `style`. */
	public static boolean flagIsSet(int flag, int style) {
		return (style & flag) == flag;
	}

	/** Returns true if `flag` is set in the style value of `widget`. */
	public static boolean flagIsSet(int flag, Widget widget) {
		return flagIsSet(flag, widget.getStyle());
	}

	/** Sets whether the flag is set for the given style. */
	public static int setFlag(int flag, boolean isSet, int style) {
		return isSet ? (style | flag) : (style & ~flag);
	}

	/** Converts a {@link Runnable} into a {@link Listener}. */
	public static Listener asListener(Runnable runnable) {
		return e -> runnable.run();
	}

	/** Returns the Display instance, asserting that the method was called from the UI thread. */
	public static Display assertUI() {
		// returns the system display, creating it if necessary
		synchronized (Device.class) {
			// Display.getDefault() and display.getThread() both synchronize on
			// Device.class.  By synchronizing ourselves, we minimize contention
			Display display = Display.getDefault();
			Preconditions.checkArgument(display.getThread() == Thread.currentThread(), "Must be called only from UI thread");
			return display;
		}
	}

	/** Asserts that the user didn't call this from the UI thread. */
	public static void assertNotUI() {
		Display current = Display.getCurrent();
		Preconditions.checkArgument(current == null, "Must not be called from the UI thread.");
	}

	/** Returns the given system color.  SWT.COLOR_* */
	public static Color getSystemColor(int code) {
		return assertUI().getSystemColor(code);
	}

	/** Returns the given system color.  SWT.CURSOR_* */
	public static Cursor getSystemCursor(int cursor) {
		return assertUI().getSystemCursor(cursor);
	}

	/** Returns the given system icon.  SWT.ICON_* */
	public static Image getSystemIcon(int icon) {
		return assertUI().getSystemImage(icon);
	}

	/**
	 * @deprecated As of SWT 4.6 (Neon), this functionality is {@link Control#requestLayout()}.
	 */
	@Deprecated
	public static void requestLayout(Control control) {
		if (control instanceof Shell) {
			((Shell) control).layout(null, SWT.DEFER);
		} else {
			control.getShell().layout(new Control[]{control}, SWT.DEFER);
		}
	}

	/**
	 * Performs an asynchronous layout on the given composite.
	 * 

* Oftentimes, a layout will not be successful unless it is performed in * a {@link Display#asyncExec(Runnable)} call, because the current list of events must be * processed before the layout can take place. */ public static void asyncLayout(Composite cmp) { SwtExec.async().guardOn(cmp).execute(() -> cmp.layout(true, true)); } /** * Performs an asynchronous layout on the given composite anytime that it is resized. *

* This can often fix graphical glitches with resize-to-fit layouts, such as a `TableColumnLayout`. */ public static void asyncLayoutOnResize(Composite cmp) { cmp.addListener(SWT.Resize, e -> asyncLayout(cmp)); // trigger the first one by hand asyncLayout(cmp); } /** Disposes all children of the given composite, and sets the layout to null. */ public static void clean(Composite cmp) { for (Control child : cmp.getChildren()) { child.dispose(); } cmp.setLayout(null); } /** Throws an {@link IllegalArgumentException} iff the given `Composite` has any children or a non-null layout. */ public static void assertClean(Composite cmp) { Preconditions.checkArgument(cmp.getChildren().length == 0, "The composite should have no children, this had %s.", cmp.getChildren().length); Preconditions.checkArgument(cmp.getLayout() == null, "The composite should have no layout, this had %s.", cmp.getLayout()); } /** Returns a deep copy of the given SWT event. */ public static Event copyEvent(Event event) { Event copy = new Event(); copy.display = event.display; copy.widget = event.widget; copy.type = event.type; copy.detail = event.detail; copy.item = event.item; copy.index = event.index; copy.gc = event.gc; copy.x = event.x; copy.y = event.y; copy.width = event.width; copy.height = event.height; copy.count = event.count; copy.time = event.time; copy.button = event.button; copy.character = event.character; copy.keyCode = event.keyCode; copy.keyLocation = event.keyLocation; copy.stateMask = event.stateMask; copy.start = event.start; copy.end = event.end; copy.text = event.text; copy.segments = event.segments; copy.segmentsChars = event.segmentsChars; copy.doit = event.doit; copy.data = event.data; copy.touches = event.touches; copy.xDirection = event.xDirection; copy.yDirection = event.yDirection; copy.magnification = event.magnification; copy.rotation = event.rotation; return copy; } /** Runs some function using a temporary GC. */ public static void withGcRun(Consumer consumer) { withGcCompute(gc -> { consumer.accept(gc); return null; }); } /** Computes some function using a temporary GC. */ public static T withGcCompute(Function function) { // create a tiny image to bind our GC to (not that it can't be size 0) Image dummyImg = new Image(assertUI(), 1, 1); GC gc = new GC(dummyImg); try { return function.apply(gc); } finally { gc.dispose(); dummyImg.dispose(); } } //////////////////////////////////////// // Run the SWT display loop until ... // //////////////////////////////////////// /** Runs the display loop until the given `Predicate` returns false. */ public static void loopUntil(Predicate until) { Display display = assertUI(); while (!until.test(display)) { try { if (!display.readAndDispatch()) { display.sleep(); } } catch (Throwable e) { throw Errors.asRuntime(e); } } } /** Runs the display loop until the `widget` has been disposed. */ public static void loopUntilDisposed(Widget widget) { loopUntil(display -> widget.isDisposed()); } /** Runs the display loop until the given future has returned. */ public static T loopUntilGet(CompletionStage future) { Box.Nullable result = Box.Nullable.ofNull(); Box.Nullable error = Box.Nullable.ofNull(); Rx.subscribe(future, Rx.onValueOnFailure(result::set, error::set)); CompletableFuture actualFuture = future.toCompletableFuture(); loopUntil(display -> actualFuture.isDone()); if (error.get() != null) { throw Errors.asRuntime(error.get()); } else { return result.get(); } } //////////////////////////////////////// // Thread-safe blocking notifications // //////////////////////////////////////// /** Blocks to notify about a success. Can be called from any thread. */ public static void blockForSuccess(String title, String msg, @Nullable Shell parent) { blockForMessageBox(title, msg, parent, SWT.ICON_INFORMATION | SWT.OK); } /** Blocks to notify about a success. Can be called from any thread. */ public static void blockForSuccess(String title, String msg) { blockForSuccess(title, msg, null); } /** Blocks to notify about an error. Can be called from any thread. */ public static void blockForError(String title, String msg, @Nullable Shell parent) { blockForMessageBox(title, msg, parent, SWT.ICON_ERROR | SWT.OK); } /** Blocks to notify about an error. Can be called from any thread. */ public static void blockForError(String title, String msg) { blockForError(title, msg, null); } /** Blocks to ask a yes/no question. Can be called from any thread. */ public static boolean blockForQuestion(String title, String message, @Nullable Shell parent) { return blockForMessageBox(title, message, parent, SWT.ICON_QUESTION | SWT.YES | SWT.NO) == SWT.YES; } /** Blocks to ask a yes/no question. Can be called from any thread. */ public static boolean blockForQuestion(String title, String message) { return blockForQuestion(title, message, null); } /** Blocks to ask an Ok/Cancel question. Can be called from any thread. */ public static boolean blockForOkCancel(String title, String message, @Nullable Shell parent) { return blockForMessageBox(title, message, parent, SWT.ICON_QUESTION | SWT.OK | SWT.CANCEL) == SWT.OK; } /** Blocks to ask an Ok/Cancel question. Can be called from any thread. */ public static boolean blockForOkCancel(String title, String message) { return blockForOkCancel(title, message, null); } /** * Opens a message box with the given title, message, and style and returns its result. * Can be called from any thread. * * @param title The title of the box. * @param message The message in the box. * @param parent The parent shell. Null means it will be on the current active shell. * @param style An OR'ed combination of SWT.YES/NO, SWT.OK/CANCEL, and SWT.ICON_* * @return The button that was pressed (e.g. SWT.YES, SWT.OK, etc) */ public static int blockForMessageBox(String title, String message, @Nullable Shell parent, int style) { return SwtExec.blocking().get(() -> { Display display = assertUI(); Shell parentInternal; if (parent == null) { // null equals display.getActiveShell() parentInternal = display.getActiveShell(); } else { parentInternal = parent; } boolean parentWasNull = parentInternal == null; if (parentWasNull) { // if there wasn't an active shell, we'll create one, because // MessageBox requires a parent parentInternal = new Shell(Display.getCurrent(), SWT.APPLICATION_MODAL); } MessageBox questionBox = new MessageBox(parentInternal, style); questionBox.setMessage(message); questionBox.setText(title); int result = questionBox.open(); if (parentWasNull) { // if we made an invisible parent, clean it up afterwards parentInternal.dispose(); } return result; }); } /** * Opens a message box with the given title, message, and style and returns its result. * Can be called from any thread. * * @param title The title of the box. * @param message The message in the box. * @param style An OR'ed combination of SWT.YES/NO, SWT.OK/CANCEL, and SWT.ICON_* * @return The button that was pressed (e.g. SWT.YES, SWT.OK, etc) */ public static int blockForMessageBox(String title, String message, int style) { return blockForMessageBox(title, message, null, style); } /////////////////// // Scale by font // /////////////////// /** The cached height of the system font. */ static int systemFontHeight = 0; static int systemFontWidth = 0; /** Populates the height and width of the system font. */ private static void populateSystemFont() { // create a tiny image to bind our GC to (not that it can't be size 0) Image dummyImg = new Image(assertUI(), 1, 1); GC gc = new GC(dummyImg); FontMetrics metrics = gc.getFontMetrics(); systemFontHeight = metrics.getHeight(); systemFontWidth = metrics.getAverageCharWidth(); if (OS.getNative().isMac()) { // add 20% width on Mac systemFontWidth = (systemFontWidth * 12) / 10; } gc.dispose(); dummyImg.dispose(); } /** Returns the height of the system font. */ public static int systemFontHeight() { if (systemFontHeight == 0) { populateSystemFont(); } return systemFontHeight; } /** Returns the width of the system font. */ public static int systemFontWidth() { if (systemFontWidth == 0) { populateSystemFont(); } return systemFontWidth; } /** Returns a distance which is a snug fit for a line of text in the system font. */ public static int systemFontSnug() { return systemFontHeight() + Layouts.defaultMargin(); } /** Returns the default width of a button, scaled for the system font. */ public static int defaultButtonWidth() { return systemFontWidth() * " Cancel ".length(); } /** Returns the default width of a dialog. */ public static int defaultDialogWidth() { return 50 * systemFontWidth(); } /** Returns a size which is scaled by the system font's height. */ public static Point scaleByFontHeight(int cols, int rows) { return new Point(cols * systemFontHeight(), rows * systemFontHeight()); } /** Returns a dimension which is scaled by the system font's height. */ public static int scaleByFontHeight(int rows) { return rows * systemFontHeight(); } /** Returns a point that represents the size of a (cols x rows) grid of characters printed in the standard system font. */ public static Point scaleByFont(int cols, int rows) { return new Point(cols * systemFontWidth(), rows * systemFontHeight()); } /** Returns a dimension which is guaranteed to be comfortable for the given string. */ public static Point scaleByFont(String str) { List lines = splitLines(str); int maxLength = lines.stream().mapToInt(String::length).max().getAsInt(); return scaleByFont(maxLength + 2, lines.size()); } /** Splits a string into a list of lines. Null returns an empty list. */ private static List splitLines(String orig) { if (orig == null) { return Collections.emptyList(); } else { return Arrays.asList(orig.replace("\\r\\n", "\n").split("\\n")); } } ///////////////////// // Geometric stuff // ///////////////////// /** Returns the bounds of the given control in global coordinates. */ public static Rectangle globalBounds(Control control) { Point size = control.getSize(); Point topLeft = control.toDisplay(0, 0); return new Rectangle(topLeft.x, topLeft.y, size.x, size.y); } /** Returns the global bounds of the given ControlWrapper. */ public static Rectangle globalBounds(ControlWrapper control) { return globalBounds(control.getRootControl()); } /** Converts a rectangle to global coordinates using the given control as a reference frame. */ public static Rectangle toDisplay(Control control, Rectangle rect) { Point topLeft = control.toDisplay(rect.x, rect.y); return new Rectangle(topLeft.x, topLeft.y, rect.width, rect.height); } /** Returns the monitor (if any) which contains the given point. */ public static Optional monitorFor(Point p) { Monitor[] monitors = assertUI().getMonitors(); for (Monitor monitor : monitors) { Rectangle bounds = monitor.getBounds(); if (bounds.contains(p)) { return Optional.of(monitor); } } return Optional.empty(); } ////////////////////// // Tree-based stuff // ////////////////////// /** Sets the enabled status of every child, grandchild, etc. of the given composite. Skips plain-jane Composites. */ public static void setEnabledDeep(Composite root, boolean enabled) { TreeStream.depthFirst(treeDefControl(), root) // skip plain-jane Composites .filter(ctl -> ctl.getClass().equals(Composite.class)) // set the enabled flag .forEach(ctl -> ctl.setEnabled(enabled)); } /** Calls the given consumer on the given composite and all of its children, recursively. */ public static void forEachDeep(Composite root, Consumer ctlSetter) { TreeIterable.depthFirst(treeDefControl(), root).forEach(ctlSetter); } /** Sets the foreground color of the given composite and all of its children, recursively. */ public static void setForegroundDeep(Composite root, Color foreground) { forEachDeep(root, ctl -> ctl.setForeground(foreground)); } /** Sets the background color of the given composite and all of its children, recursively. */ public static void setBackgroundDeep(Composite root, Color background) { forEachDeep(root, ctl -> ctl.setBackground(background)); } /** Returns the root shell of the given control. */ public static Shell rootShell(Control ctl) { Shell shell; if (ctl instanceof Shell) { shell = (Shell) ctl; } else { shell = ctl.getShell(); } return TreeQuery.root(SwtMisc.treeDefShell(), shell); } /** {@link TreeDef} for {@link Composite}s. */ public static TreeDef.Parented treeDefComposite() { return COMPOSITE_TREE_DEF; } /** {@link TreeDef} for {@link Control}s. */ public static TreeDef.Parented treeDefControl() { return CONTROL_TREE_DEF; } /** {@link TreeDef} for {@link Shell}s. */ public static TreeDef.Parented treeDefShell() { return SHELL_TREE_DEF; } private static final TreeDef.Parented COMPOSITE_TREE_DEF = new TreeDef.Parented() { @Override public List childrenOf(Composite node) { Control[] rawChildren = node.getChildren(); List children = Lists.newArrayListWithCapacity(rawChildren.length); for (Control child : rawChildren) { if (child instanceof Composite) { children.add((Composite) child); } } return children; } @Override public Composite parentOf(Composite node) { return node.getParent(); } }; private static final TreeDef.Parented CONTROL_TREE_DEF = new TreeDef.Parented() { @Override public List childrenOf(Control node) { if (node instanceof Composite) { return Arrays.asList(((Composite) node).getChildren()); } else { return Collections.emptyList(); } } @Override public Control parentOf(Control node) { return node.getParent(); } }; private static final TreeDef.Parented SHELL_TREE_DEF = new TreeDef.Parented() { @Override public List childrenOf(Shell node) { return Arrays.asList(node.getShells()); } @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST_OF_RETURN_VALUE", justification = "Parent of a shell is guaranteed to be either a Shell or null.") @Override public Shell parentOf(Shell node) { return (Shell) node.getParent(); } }; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy