]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTUtils.java
Fixed context menu popup location for HiDPI displays with display zoom
[simantics/platform.git] / bundles / org.simantics.utils.ui / src / org / simantics / utils / ui / SWTUtils.java
1 package org.simantics.utils.ui;
2
3 import java.util.concurrent.Semaphore;
4 import java.util.concurrent.TimeUnit;
5
6 import org.eclipse.swt.SWTException;
7 import org.eclipse.swt.custom.CTabFolder;
8 import org.eclipse.swt.custom.CTabItem;
9 import org.eclipse.swt.widgets.Composite;
10 import org.eclipse.swt.widgets.Control;
11 import org.eclipse.swt.widgets.Display;
12 import org.eclipse.swt.widgets.TabFolder;
13 import org.eclipse.swt.widgets.TabItem;
14 import org.eclipse.swt.widgets.Widget;
15 import org.simantics.utils.threads.IThreadWorkQueue;
16 import org.simantics.utils.threads.ThreadUtils;
17
18 public class SWTUtils {
19
20     /**
21      * When scheduling a runnable to be executed asynchronously in the SWT
22      * thread it is not possible to do this in a safe manner based on a
23      * {@link Display} instance. When invoking
24      * {@link Display#asyncExec(Runnable)} from a non-SWT thread, it may throw
25      * {@link SWTException} even if you've first checked
26      * {@link Display#isDisposed()}.
27      * 
28      * This method will run {@link Display#asyncExec(Runnable)} using the
29      * display returned by the specified widget if the widget is or does not
30      * become disposed while trying to get the display from it.
31      * 
32      * @param display the display to asyncExec with
33      * @param runnable the runnable to execute with
34      *        {@link Display#asyncExec(Runnable)}
35      * @return <code>true</code> if the executable was scheduled, false
36      *         otherwise
37      */
38     public static boolean asyncExec(Display display, Runnable runnable) {
39         try {
40             if (display.isDisposed())
41                 return false;
42             display.asyncExec(runnable);
43             return true;
44         } catch (SWTException e) {
45             // widget was disposed between isDisposed and getDisplay.
46             return false;
47         }
48     }
49
50         /**
51          * When scheduling a runnable to be executed asynchronously in the SWT
52          * thread it is not possible to do this in a safe manner based on a
53          * {@link Widget} instance. When invoking {@link Widget#getDisplay()} from a
54          * non-SWT thread, it may throw {@link SWTException} even if you've first
55          * checked {@link Widget#isDisposed()}.
56          * 
57          * This method will run {@link Display#asyncExec(Runnable)} using the
58          * display returned by the specified widget if the widget is or does not
59          * become disposed while trying to get the display from it.
60          * 
61          * @param widget the widget to get {@link Display} from
62          * @param runnable the runnable to execute with {@link Display#asyncExec(Runnable)}
63          * @return <code>true</code> if the executable was scheduled, false otherwise
64          */
65         public static boolean asyncExec(Widget widget, Runnable runnable) {
66                 try {
67                         if (widget.isDisposed())
68                                 return false;
69                         widget.getDisplay().asyncExec(runnable);
70                         return true;
71                 } catch (SWTException e) {
72                         // widget was disposed between isDisposed and getDisplay.
73                         return false;
74                 }
75         }
76
77         /**
78          * Invokes a runnable in a specified thread and dispatches SWT events while
79          * waiting for the runnable to complete.
80          * 
81          * <p>
82          * The runnable must not perform any operations that may cause more SWT
83          * events to be dispatched.
84          * 
85          * <p>
86          * To be invoked from SWT thread only.
87          * 
88          * @param control
89          * @param inThread
90          * @param invoke
91          * @throws InterruptedException
92          */
93         public static void invokeAndDispatchEvents(Display display, IThreadWorkQueue inThread, final Runnable invoke) throws InterruptedException {
94                 if (display.isDisposed())
95                         throw new IllegalArgumentException("display is disposed");
96                 final Semaphore sem = new Semaphore(0);
97                 ThreadUtils.asyncExec(inThread, new Runnable() {
98                         @Override
99                         public void run() {
100                                 try {
101                                         invoke.run();
102                                 } finally {
103                                         sem.release();
104                                 }
105                         }
106                 });
107                 boolean done = false;
108                 while (!done) {
109                         done = sem.tryAcquire(10, TimeUnit.MILLISECONDS);
110                         while (!done && display.readAndDispatch()) {
111                                 /*
112                                  * Note: readAndDispatch can cause this to be disposed.
113                                  */
114                                 done = sem.tryAcquire();
115                         }
116                 }
117         }
118
119         /**
120          * Look for an object starting from an SWT (composite) control with a
121          * specific filter that can inspect the control freely.
122          * 
123          * @return the first T returned by the control filter or <code>null</code>
124          *         if the filter returned no results.
125          */
126         public static <T> T tryGetObject(Control control, ControlFilter<T> filter) {
127                 if (control == null || control.isDisposed())
128                         return null;
129                 T t = filter.accept(control);
130                 if (t != null)
131                         return t;
132                 if (control instanceof Composite) {
133                         if (control instanceof TabFolder) {
134                                 TabFolder tf = (TabFolder) control;
135                                 for (TabItem item : tf.getSelection()) {
136                                         t = tryGetObject(item.getControl(), filter);
137                                         if (t != null)
138                                                 return t;
139                                 }
140                         } else if (control instanceof CTabFolder) {
141                                 CTabFolder tf = (CTabFolder) control;
142                                 CTabItem item = tf.getSelection();
143                                 if (item != null) {
144                                         t = tryGetObject(item.getControl(), filter);
145                                         if (t != null)
146                                                 return t;
147                                 }
148                         } else {
149                                 Composite c = (Composite) control;
150                                 for (Control child : c.getChildren()) {
151                                         t = tryGetObject(child, filter);
152                                         if (t != null)
153                                                 return t;
154                                 }
155                         }
156                 }
157                 return null;
158         }
159
160         @FunctionalInterface
161         public interface ControlFilter<T> {
162                 T accept(Control control);
163         }
164
165 }