4b1554c864c19c0fd68477276d70c97859d2718f
[simantics/platform.git] / bundles / org.simantics.ui / src / org / simantics / ui / SimanticsUI.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.ui;
13
14 import org.eclipse.jface.resource.ImageDescriptor;
15 import org.eclipse.jface.viewers.ISelection;
16 import org.eclipse.swt.widgets.Display;
17 import org.eclipse.swt.widgets.Widget;
18 import org.eclipse.ui.PlatformUI;
19 import org.simantics.DatabaseJob;
20 import org.simantics.db.Resource;
21 import org.simantics.db.Session;
22 import org.simantics.db.common.primitiverequest.Adapter;
23 import org.simantics.db.common.utils.Logger;
24 import org.simantics.db.common.utils.RequestUtil;
25 import org.simantics.db.exception.DatabaseException;
26 import org.simantics.db.management.ISessionContext;
27 import org.simantics.db.management.ISessionContextProvider;
28 import org.simantics.db.management.ISessionContextProviderSource;
29 import org.simantics.project.IProject;
30 import org.simantics.project.ProjectKeys;
31 import org.simantics.utils.datastructures.Arrays;
32 import org.simantics.utils.ui.BundleUtils;
33 import org.simantics.utils.ui.ISelectionUtils;
34 import org.simantics.utils.ui.SWTUtils;
35
36 /**
37  */
38 public class SimanticsUI {
39
40     public static final String                   PLUGIN_ID      = "org.simantics.ui";
41
42     /**
43      * The maximum amount of time in milliseconds to wait for the execution of a
44      * database request to start when the request is executed synchronously in
45      * the UI thread. The timeout counting starts from the moment the request is
46      * first scheduled into the database {@link Session}. The purpose is to
47      * prevent synchronous UI thread database requests from locking the whole UI
48      * thread up.
49      *
50      * <p>
51      * The default value is 20. The default value can be customized at class
52      * load time by setting the system property
53      * <code>simantics.ui.request.start.timeout</code> to the desired value at
54      * JVM startup.
55      * 
56      * @see RequestUtil
57      */
58     public static final long UI_THREAD_REQUEST_START_TIMEOUT;
59     /**
60      * The maximum amount of time in milliseconds to wait for the execution of a
61      * database request to complete when the request is executed synchronously
62      * in the UI thread. The timeout counting starts from the moment the request
63      * execution is scheduled. The purpose is to prevent synchronous UI thread
64      * database requests from locking the whole UI thread up.
65      *
66      * <p>
67      * The default value is 50. The default value can be customized at class
68      * load time by setting the system property
69      * <code>simantics.ui.request.execution.timeout</code> to the desired value
70      * at JVM startup.
71      * 
72      * @see RequestUtil
73      */
74     public static final long UI_THREAD_REQUEST_EXECUTION_TIMEOUT;
75     /**
76      *
77      * <p>
78      * The default value is 100. The default value can be customized at class
79      * load time by setting the system property
80      * <code>simantics.ui.request.execution.timeout.long</code> to the desired
81      * value at JVM startup.
82      * 
83      * @see RequestUtil
84      */
85     public static final long UI_THREAD_REQUEST_EXECUTION_TIMEOUT_LONG;
86
87     static {
88         UI_THREAD_REQUEST_START_TIMEOUT = parseLongProperty("simantics.ui.request.start.timeout", 500L);
89         UI_THREAD_REQUEST_EXECUTION_TIMEOUT = parseLongProperty("simantics.ui.request.exec.timeout", 50L);
90         UI_THREAD_REQUEST_EXECUTION_TIMEOUT_LONG = parseLongProperty("simantics.ui.request.exec.timeout.long", 100L);
91     }
92
93     /**
94      * Information of the currently open database session for the Simantics UI.
95      * Contains just the vital information to connect to the database. Is
96      * <code>null</code> when there is no open database session.
97      */
98     private static ISessionContextProviderSource providerSource = null;
99
100 //    /**
101 //     * TODO: support different contexts
102 //     * @deprecated no replacement
103 //     */
104 //    @Deprecated
105 //    public static void undo() {
106 //        try {
107 //            PlatformUI.getWorkbench().getOperationSupport().getOperationHistory().undo(
108 //                    IOperationHistory.GLOBAL_UNDO_CONTEXT, null, null);
109 //        } catch (ExecutionException e) {
110 //            // TODO Auto-generated catch block
111 //            e.printStackTrace();
112 //        }
113 //    }
114 //
115 //    /**
116 //     * TODO: support different contexts
117 //     * @deprecated no replacement
118 //     */
119 //    @Deprecated
120 //    public static void redo() {
121 //        try {
122 //            PlatformUI.getWorkbench().getOperationSupport().getOperationHistory().redo(
123 //                    IOperationHistory.GLOBAL_UNDO_CONTEXT, null, null);
124 //        } catch (ExecutionException e) {
125 //            // TODO Auto-generated catch block
126 //            e.printStackTrace();
127 //        }
128 //    }
129
130     /**
131      * Only for use in application startup code such as the workbench window
132      * advisor. Must be invoked before calling any other methods in this class.
133      * 
134      * @param manager the ISessionManager to be used by the application
135      * @throw IllegalArgumentException if manager is <code>null</code>
136      */
137     public static void setSessionContextProviderSource(ISessionContextProviderSource source) {
138         if (source == null)
139             throw new IllegalArgumentException("null provider source");
140         providerSource = source;
141     }
142
143     /**
144      * Asserts that the current context provider source has been initialized
145      * before allowing access to it.
146      * 
147      * @return current context provider source
148      */
149     public static ISessionContextProviderSource getProviderSource() {
150         if (providerSource == null)
151             throw new IllegalStateException(
152             "providerSource must be initialized by the application before using SimanticsUI");
153         return providerSource;
154     }
155
156     /**
157      * Close and remove the current session contexts of the UI. Afterwards
158      * getSessionContext will return <code>null</code>.
159      * 
160      * Not for client use, only for internal purposes.
161      */
162     public static synchronized void closeSessions() {
163         ISessionContextProviderSource source = providerSource;
164         if (source == null)
165             return;
166         for (ISessionContextProvider p : source.getAll()) {
167             ISessionContext ctx = p.setSessionContext(null);
168             if (ctx != null) {
169                 ctx.dispose();
170             }
171         }
172     }
173
174     /**
175      * @return <code>true</code> if the session manager contains the specified
176      *         session context
177      */
178     public static synchronized boolean isInUse(ISessionContext ctx) {
179         for (ISessionContextProvider p : getProviderSource().getAll()) {
180             if (p.getSessionContext() == ctx)
181                 return true;
182         }
183         return false;
184     }
185
186     /**
187      * @param project the project to check
188      * @param excluding
189      * @return <code>true</code> if the session manager contains an
190      *         ISessionContext that contains a reference to the specified
191      *         project, disregarding the excluded ISessionContexts listed
192      */
193     public static synchronized boolean isInUse(IProject project, ISessionContext... excluding) {
194         for (ISessionContextProvider p : getProviderSource().getAll()) {
195             ISessionContext ctx = p.getSessionContext();
196             if (ctx != null) {
197                 if (Arrays.indexOf(excluding, ctx) == -1) {
198                     if (ctx.getHint(ProjectKeys.KEY_PROJECT) == project)
199                         return true;
200                 }
201             }
202         }
203         return false;
204     }
205
206 //    /**
207 //     * Looks if there is an ISessionContextProvider within the Simantics workbench
208 //     * that is currently using a ProCore database server at the specified
209 //     * address.
210 //     * 
211 //     * @param address the address to look for connections to
212 //     * @return <code>null</code> if there is currently no session in use to the
213 //     *         specified address.
214 //     */
215 //    public static synchronized ISessionContext findSessionTo(ServerAddress address) {
216 //        if (address == null)
217 //            throw new IllegalArgumentException("null address");
218 //        for (ISessionContextProvider provider : getProviderSource().getAll()) {
219 //            ISessionContext ctx = provider.getSessionContext();
220 //            if (ctx != null) {
221 //                ServerAddress addr = ctx.getAddress();
222 //                if (address.equals(addr))
223 //                    return ctx;
224 //            }
225 //        }
226 //        return null;
227 //    }
228
229     /**
230      * Returns the session context provider of the curretly active workbench
231      * window. This method will always return a valid session context provider.
232      * 
233      * @return a valid ISessionContextProvider
234      */
235     public static ISessionContextProvider getSessionContextProvider() {
236         return getProviderSource().getActive();
237     }
238
239     /**
240      * Returns the session context provider for the specified handle if one
241      * exists. Workbench windows (IWorkbenchWindow) are currently used as
242      * handles.
243      * 
244      * @param handle the handle associated with the requested session context
245      *        provider
246      * @return <code>null</code> if there is no session associated to the
247      *         specified handle
248      */
249     public static ISessionContextProvider getSessionContextProvider(Object handle) {
250         return getProviderSource().get(handle);
251     }
252
253     /**
254      * Returns the database session context associated with the currently active
255      * workbench window. This method should be used to retrieve session contexts
256      * only when the client is sure that the correct workbench window has focus.
257      * 
258      * <p>
259      * If the client knows the workbench window it is working with, but it isn't
260      * sure that the correct workbench window has focus, use
261      * {@link #getSessionContext(Object)} instead.
262      * </p>
263      * 
264      * @return the session context associated with the currently active
265      *         workbench window or <code>null</code> if the active window has no
266      *         session context
267      */
268     public static ISessionContext getSessionContext() {
269         ISessionContextProvider provider = getSessionContextProvider();
270         return provider != null ? provider.getSessionContext() : null;
271     }
272
273     /**
274      * Returns the database session context associated with the specified
275      * handle. Workbench windows (IWorkbenchWindow) are currently used as
276      * handles. This method should be used to retrieve session contexts in cases
277      * where the workbench window is known, but the thread of execution is such
278      * that the client cannot be certain that the same workbench window has
279      * focus.
280      * 
281      * @return the session context associated with the specified handle
282      *         (IWorkbenchWindow)
283      */
284     public static ISessionContext getSessionContext(Object handle) {
285         return getSessionContextProvider(handle).getSessionContext();
286     }
287
288     /**
289      * Associates the specified ISessionContext with the currently active
290      * workbench window. To remove an ISessionContext association from the
291      * active workbench window, specify <code>null</code> as ctx.
292      * 
293      * <p>
294      * After invoking this method you should be able to retrieve the same
295      * ISessionContext through {@link #getSessionContext()}, provided that the
296      * same workbench window has focus at that time.
297      * </p>
298      * 
299      * @param ctx the new UI database session context or <code>null</code> to
300      *        replace the current UI session with no session.
301      * @return The previous session context if one existed, otherwise
302      *         <code>null</code>. If the specified <code>ctx</code> matched the
303      *         current session context (<code>null</code> or
304      *         <code>non-null</code>), null is also returned and nothing is
305      *         done.
306      */
307     public static synchronized ISessionContext setSessionContext(ISessionContext ctx) {
308         return getSessionContextProvider().setSessionContext(ctx);
309     }
310
311     /**
312      * Associates the specified ISessionContext with the specified handle
313      * object.
314      * 
315      * <p>
316      * Currently IWorkbenchWindow's are used as handles. This implies
317      * that each workbench window can only have one active ISessionContext bound
318      * to it. After invoking this method with a valid workbench window handle
319      * you should be able to retrieve the same ISessionContext through
320      * {@link #getSessionContext(Object)} with the same workbench window
321      * specified as the handle.
322      * </p>
323      * 
324      * @param handle the handle to associate the specified ISessionContext with.
325      * @param ctx the new UI database session context or <code>null</code> to
326      *        replace the current UI session with no session.
327      * @return The previous session context if one existed, otherwise
328      *         <code>null</code>. If the specified <code>ctx</code> matched the
329      *         current session context (<code>null</code> or
330      *         <code>non-null</code>), null is also returned and nothing is
331      *         done.
332      */
333     public static synchronized ISessionContext setSessionContext(Object handle, ISessionContext ctx) {
334         ISessionContextProvider provider = getProviderSource().get(handle);
335         if (provider != null)
336             return provider.setSessionContext(ctx);
337         return null;
338     }
339
340     /**
341      * Returns the database Session bound to the currently active workbench
342      * window.
343      * 
344      * <p>
345      * This method should only be invoked in cases where it is certain that the
346      * correct workbench window has focus or it is the latest of all workbench
347      * windows to have had focus. Basically any invocation from the SWT UI
348      * thread is safe, since because in those cases the currently active
349      * workbench window is generally known. Instead invocations from any other
350      * thread should be carefully considered. The rule of thumb is that if you
351      * cannot be sure that the correct workbench window has focus, you should
352      * always get a hold of the Session to be used in some other manner.
353      * </p>
354      * 
355      * <p>
356      * The method always returns a non-null Session or produces an
357      * IllegalStateException if a Session was not attainable.
358      * </p>
359      * 
360      * @return the Session bound to the currently active workbench window
361      * @throws IllegalStateException if no Session was available
362      */
363     public static Session getSession() {
364         ISessionContext ctx = getSessionContext();
365         if (ctx == null)
366             throw new IllegalStateException("Session unavailable, no database session open");
367         return ctx.getSession();
368     }
369         
370     /**
371      * Returns the database Session bound to the currently active workbench
372      * window. Differently from {@link #getSession()}, this method returns
373      * <code>null</code> if there is no current Session available.
374      * 
375      * <p>
376      * This method should only be invoked from the SWT UI thread. Check the
377      * explanations given in {@link #getSession()}. The same applies to this
378      * method also.
379      * </p>
380      * 
381      * @return the Session bound to the currently active workbench window or
382      *         <code>null</code>
383      */
384     public static Session peekSession() {
385         ISessionContext ctx = getSessionContext();
386         return ctx == null ? null : ctx.peekSession();
387     }
388
389     /**
390      * @return the currently open and active project as an IProject or
391      *         <code>null</code> if there is no active session or project
392      */
393     public static IProject peekProject() {
394         ISessionContext ctx = getSessionContext();
395         return ctx == null ? null : (org.simantics.project.IProject) ctx.getHint(ProjectKeys.KEY_PROJECT);
396     }
397
398     /**
399      * @return the currently open and active project for the specified database
400      *         session or <code>null</code> if there is no current project
401      */
402     public static IProject peekProject(ISessionContext ctx) {
403         if (ctx == null)
404             return null;
405         return ctx.getHint(ProjectKeys.KEY_PROJECT);
406     }
407
408     /**
409      * @return the currently open and active project as an IProject
410      * @throws IllegalStateException if there is no currently active database
411      *         session, which also means there is no active project at the
412      *         moment
413      */
414     public static IProject getProject() {
415         ISessionContext ctx = getSessionContext();
416         if (ctx == null)
417             throw new IllegalStateException("No current database session");
418         return ctx.getHint(ProjectKeys.KEY_PROJECT);
419     }
420
421     /**
422      * TODO: refactor this out of here
423      * 
424      * @param imageFilePath
425      * @return
426      */
427     public static ImageDescriptor getImageDescriptor(String imageFilePath) {
428         return BundleUtils.getImageDescriptorFromPlugin(PLUGIN_ID, imageFilePath);
429     }
430
431     /**
432      * TODO: [Tuukka] I'm really unsure this belongs here.
433      * 
434      * @param <T>
435      * @param sel
436      * @param assignableFrom
437      * @return
438      */
439     public static <T> T filterSingleSelection(ISelection sel, Class<T> assignableFrom) {
440
441         T result = ISelectionUtils.filterSingleSelection(sel, assignableFrom);
442         if (result != null)
443             return result;
444
445         Resource resource = ISelectionUtils.filterSingleSelection(sel, Resource.class);
446         if(resource == null) return null;
447         
448         try {
449             return getSession().syncRequest(new Adapter<T>(resource, assignableFrom));
450         } catch (DatabaseException e) {
451             Logger.defaultLogError(e);
452             return null;
453         }
454         
455     }
456     
457     public static <T> T filterSingleWorkbenchSelection(Class<T> assignableFrom) {
458         return filterSingleSelection(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().getSelection(), assignableFrom);
459     }
460
461
462     public static void asyncExecSWT(final Widget widget, final Runnable runnable) {
463         SWTUtils.asyncExec(widget, delayedExecSWT(null, widget, runnable));
464     }
465
466     public static void asyncExecSWT(final Display display, final Runnable runnable) {
467         SWTUtils.asyncExec(display, delayedExecSWT(display, null, runnable));
468     }
469
470     private static Runnable delayedExecSWT(final Display display, final Widget widget, final Runnable runnable) {
471         if (display == null && widget == null)
472             throw new IllegalArgumentException("both display and widget are null");
473
474         return new Runnable() {
475             @Override
476             public void run() {
477                 if (display != null && display.isDisposed())
478                     return;
479                 if (widget != null && widget.isDisposed())
480                     return;
481                 if (DatabaseJob.inProgress()) {
482                     Display d = display != null ? display : widget.getDisplay();
483                     d.timerExec(50, this);
484                     return;
485                 }
486                 runnable.run();
487             }
488         };
489     }
490
491     private static long parseLongProperty(String propertyName, long defaultValue) {
492         String value = System.getProperty(propertyName, null);
493         try {
494             return value != null ? Long.parseLong(value) : defaultValue;
495         } catch (NumberFormatException e) {
496             return defaultValue;
497         }
498     }
499     
500     public static boolean isLinuxGTK() {
501         String ws = System.getProperty("osgi.ws");
502         return ws != null && "gtk".equals(ws);
503     }
504
505 }