1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.ui;
\r
14 import org.eclipse.jface.resource.ImageDescriptor;
\r
15 import org.eclipse.jface.viewers.ISelection;
\r
16 import org.eclipse.swt.widgets.Display;
\r
17 import org.eclipse.swt.widgets.Widget;
\r
18 import org.eclipse.ui.PlatformUI;
\r
19 import org.simantics.DatabaseJob;
\r
20 import org.simantics.db.Resource;
\r
21 import org.simantics.db.Session;
\r
22 import org.simantics.db.common.primitiverequest.Adapter;
\r
23 import org.simantics.db.common.utils.Logger;
\r
24 import org.simantics.db.common.utils.RequestUtil;
\r
25 import org.simantics.db.exception.DatabaseException;
\r
26 import org.simantics.db.management.ISessionContext;
\r
27 import org.simantics.db.management.ISessionContextProvider;
\r
28 import org.simantics.db.management.ISessionContextProviderSource;
\r
29 import org.simantics.project.IProject;
\r
30 import org.simantics.project.ProjectKeys;
\r
31 import org.simantics.utils.datastructures.Arrays;
\r
32 import org.simantics.utils.ui.BundleUtils;
\r
33 import org.simantics.utils.ui.ISelectionUtils;
\r
34 import org.simantics.utils.ui.SWTUtils;
\r
38 public class SimanticsUI {
\r
40 public static final String PLUGIN_ID = "org.simantics.ui";
\r
43 * The maximum amount of time in milliseconds to wait for the execution of a
\r
44 * database request to start when the request is executed synchronously in
\r
45 * the UI thread. The timeout counting starts from the moment the request is
\r
46 * first scheduled into the database {@link Session}. The purpose is to
\r
47 * prevent synchronous UI thread database requests from locking the whole UI
\r
51 * The default value is 20. The default value can be customized at class
\r
52 * load time by setting the system property
\r
53 * <code>simantics.ui.request.start.timeout</code> to the desired value at
\r
58 public static final long UI_THREAD_REQUEST_START_TIMEOUT;
\r
60 * The maximum amount of time in milliseconds to wait for the execution of a
\r
61 * database request to complete when the request is executed synchronously
\r
62 * in the UI thread. The timeout counting starts from the moment the request
\r
63 * execution is scheduled. The purpose is to prevent synchronous UI thread
\r
64 * database requests from locking the whole UI thread up.
\r
67 * The default value is 50. The default value can be customized at class
\r
68 * load time by setting the system property
\r
69 * <code>simantics.ui.request.execution.timeout</code> to the desired value
\r
74 public static final long UI_THREAD_REQUEST_EXECUTION_TIMEOUT;
\r
78 * The default value is 100. The default value can be customized at class
\r
79 * load time by setting the system property
\r
80 * <code>simantics.ui.request.execution.timeout.long</code> to the desired
\r
81 * value at JVM startup.
\r
85 public static final long UI_THREAD_REQUEST_EXECUTION_TIMEOUT_LONG;
\r
88 UI_THREAD_REQUEST_START_TIMEOUT = parseLongProperty("simantics.ui.request.start.timeout", 500L);
\r
89 UI_THREAD_REQUEST_EXECUTION_TIMEOUT = parseLongProperty("simantics.ui.request.exec.timeout", 50L);
\r
90 UI_THREAD_REQUEST_EXECUTION_TIMEOUT_LONG = parseLongProperty("simantics.ui.request.exec.timeout.long", 100L);
\r
94 * Information of the currently open database session for the Simantics UI.
\r
95 * Contains just the vital information to connect to the database. Is
\r
96 * <code>null</code> when there is no open database session.
\r
98 private static ISessionContextProviderSource providerSource = null;
\r
101 // * TODO: support different contexts
\r
102 // * @deprecated no replacement
\r
105 // public static void undo() {
\r
107 // PlatformUI.getWorkbench().getOperationSupport().getOperationHistory().undo(
\r
108 // IOperationHistory.GLOBAL_UNDO_CONTEXT, null, null);
\r
109 // } catch (ExecutionException e) {
\r
110 // // TODO Auto-generated catch block
\r
111 // e.printStackTrace();
\r
116 // * TODO: support different contexts
\r
117 // * @deprecated no replacement
\r
120 // public static void redo() {
\r
122 // PlatformUI.getWorkbench().getOperationSupport().getOperationHistory().redo(
\r
123 // IOperationHistory.GLOBAL_UNDO_CONTEXT, null, null);
\r
124 // } catch (ExecutionException e) {
\r
125 // // TODO Auto-generated catch block
\r
126 // e.printStackTrace();
\r
131 * Only for use in application startup code such as the workbench window
\r
132 * advisor. Must be invoked before calling any other methods in this class.
\r
134 * @param manager the ISessionManager to be used by the application
\r
135 * @throw IllegalArgumentException if manager is <code>null</code>
\r
137 public static void setSessionContextProviderSource(ISessionContextProviderSource source) {
\r
138 if (source == null)
\r
139 throw new IllegalArgumentException("null provider source");
\r
140 providerSource = source;
\r
144 * Asserts that the current context provider source has been initialized
\r
145 * before allowing access to it.
\r
147 * @return current context provider source
\r
149 public static ISessionContextProviderSource getProviderSource() {
\r
150 if (providerSource == null)
\r
151 throw new IllegalStateException(
\r
152 "providerSource must be initialized by the application before using SimanticsUI");
\r
153 return providerSource;
\r
157 * Close and remove the current session contexts of the UI. Afterwards
\r
158 * getSessionContext will return <code>null</code>.
\r
160 * Not for client use, only for internal purposes.
\r
162 public static synchronized void closeSessions() {
\r
163 ISessionContextProviderSource source = providerSource;
\r
164 if (source == null)
\r
166 for (ISessionContextProvider p : source.getAll()) {
\r
167 ISessionContext ctx = p.setSessionContext(null);
\r
175 * @return <code>true</code> if the session manager contains the specified
\r
178 public static synchronized boolean isInUse(ISessionContext ctx) {
\r
179 for (ISessionContextProvider p : getProviderSource().getAll()) {
\r
180 if (p.getSessionContext() == ctx)
\r
187 * @param project the project to check
\r
189 * @return <code>true</code> if the session manager contains an
\r
190 * ISessionContext that contains a reference to the specified
\r
191 * project, disregarding the excluded ISessionContexts listed
\r
193 public static synchronized boolean isInUse(IProject project, ISessionContext... excluding) {
\r
194 for (ISessionContextProvider p : getProviderSource().getAll()) {
\r
195 ISessionContext ctx = p.getSessionContext();
\r
197 if (Arrays.indexOf(excluding, ctx) == -1) {
\r
198 if (ctx.getHint(ProjectKeys.KEY_PROJECT) == project)
\r
207 // * Looks if there is an ISessionContextProvider within the Simantics workbench
\r
208 // * that is currently using a ProCore database server at the specified
\r
211 // * @param address the address to look for connections to
\r
212 // * @return <code>null</code> if there is currently no session in use to the
\r
213 // * specified address.
\r
215 // public static synchronized ISessionContext findSessionTo(ServerAddress address) {
\r
216 // if (address == null)
\r
217 // throw new IllegalArgumentException("null address");
\r
218 // for (ISessionContextProvider provider : getProviderSource().getAll()) {
\r
219 // ISessionContext ctx = provider.getSessionContext();
\r
220 // if (ctx != null) {
\r
221 // ServerAddress addr = ctx.getAddress();
\r
222 // if (address.equals(addr))
\r
230 * Returns the session context provider of the curretly active workbench
\r
231 * window. This method will always return a valid session context provider.
\r
233 * @return a valid ISessionContextProvider
\r
235 public static ISessionContextProvider getSessionContextProvider() {
\r
236 return getProviderSource().getActive();
\r
240 * Returns the session context provider for the specified handle if one
\r
241 * exists. Workbench windows (IWorkbenchWindow) are currently used as
\r
244 * @param handle the handle associated with the requested session context
\r
246 * @return <code>null</code> if there is no session associated to the
\r
249 public static ISessionContextProvider getSessionContextProvider(Object handle) {
\r
250 return getProviderSource().get(handle);
\r
254 * Returns the database session context associated with the currently active
\r
255 * workbench window. This method should be used to retrieve session contexts
\r
256 * only when the client is sure that the correct workbench window has focus.
\r
259 * If the client knows the workbench window it is working with, but it isn't
\r
260 * sure that the correct workbench window has focus, use
\r
261 * {@link #getSessionContext(Object)} instead.
\r
264 * @return the session context associated with the currently active
\r
265 * workbench window or <code>null</code> if the active window has no
\r
268 public static ISessionContext getSessionContext() {
\r
269 ISessionContextProvider provider = getSessionContextProvider();
\r
270 return provider != null ? provider.getSessionContext() : null;
\r
274 * Returns the database session context associated with the specified
\r
275 * handle. Workbench windows (IWorkbenchWindow) are currently used as
\r
276 * handles. This method should be used to retrieve session contexts in cases
\r
277 * where the workbench window is known, but the thread of execution is such
\r
278 * that the client cannot be certain that the same workbench window has
\r
281 * @return the session context associated with the specified handle
\r
282 * (IWorkbenchWindow)
\r
284 public static ISessionContext getSessionContext(Object handle) {
\r
285 return getSessionContextProvider(handle).getSessionContext();
\r
289 * Associates the specified ISessionContext with the currently active
\r
290 * workbench window. To remove an ISessionContext association from the
\r
291 * active workbench window, specify <code>null</code> as ctx.
\r
294 * After invoking this method you should be able to retrieve the same
\r
295 * ISessionContext through {@link #getSessionContext()}, provided that the
\r
296 * same workbench window has focus at that time.
\r
299 * @param ctx the new UI database session context or <code>null</code> to
\r
300 * replace the current UI session with no session.
\r
301 * @return The previous session context if one existed, otherwise
\r
302 * <code>null</code>. If the specified <code>ctx</code> matched the
\r
303 * current session context (<code>null</code> or
\r
304 * <code>non-null</code>), null is also returned and nothing is
\r
307 public static synchronized ISessionContext setSessionContext(ISessionContext ctx) {
\r
308 return getSessionContextProvider().setSessionContext(ctx);
\r
312 * Associates the specified ISessionContext with the specified handle
\r
316 * Currently IWorkbenchWindow's are used as handles. This implies
\r
317 * that each workbench window can only have one active ISessionContext bound
\r
318 * to it. After invoking this method with a valid workbench window handle
\r
319 * you should be able to retrieve the same ISessionContext through
\r
320 * {@link #getSessionContext(Object)} with the same workbench window
\r
321 * specified as the handle.
\r
324 * @param handle the handle to associate the specified ISessionContext with.
\r
325 * @param ctx the new UI database session context or <code>null</code> to
\r
326 * replace the current UI session with no session.
\r
327 * @return The previous session context if one existed, otherwise
\r
328 * <code>null</code>. If the specified <code>ctx</code> matched the
\r
329 * current session context (<code>null</code> or
\r
330 * <code>non-null</code>), null is also returned and nothing is
\r
333 public static synchronized ISessionContext setSessionContext(Object handle, ISessionContext ctx) {
\r
334 ISessionContextProvider provider = getProviderSource().get(handle);
\r
335 if (provider != null)
\r
336 return provider.setSessionContext(ctx);
\r
341 * Returns the database Session bound to the currently active workbench
\r
345 * This method should only be invoked in cases where it is certain that the
\r
346 * correct workbench window has focus or it is the latest of all workbench
\r
347 * windows to have had focus. Basically any invocation from the SWT UI
\r
348 * thread is safe, since because in those cases the currently active
\r
349 * workbench window is generally known. Instead invocations from any other
\r
350 * thread should be carefully considered. The rule of thumb is that if you
\r
351 * cannot be sure that the correct workbench window has focus, you should
\r
352 * always get a hold of the Session to be used in some other manner.
\r
356 * The method always returns a non-null Session or produces an
\r
357 * IllegalStateException if a Session was not attainable.
\r
360 * @return the Session bound to the currently active workbench window
\r
361 * @throws IllegalStateException if no Session was available
\r
363 public static Session getSession() {
\r
364 ISessionContext ctx = getSessionContext();
\r
366 throw new IllegalStateException("Session unavailable, no database session open");
\r
367 return ctx.getSession();
\r
371 * Returns the database Session bound to the currently active workbench
\r
372 * window. Differently from {@link #getSession()}, this method returns
\r
373 * <code>null</code> if there is no current Session available.
\r
376 * This method should only be invoked from the SWT UI thread. Check the
\r
377 * explanations given in {@link #getSession()}. The same applies to this
\r
381 * @return the Session bound to the currently active workbench window or
\r
382 * <code>null</code>
\r
384 public static Session peekSession() {
\r
385 ISessionContext ctx = getSessionContext();
\r
386 return ctx == null ? null : ctx.peekSession();
\r
390 * @return the currently open and active project as an IProject or
\r
391 * <code>null</code> if there is no active session or project
\r
393 public static IProject peekProject() {
\r
394 ISessionContext ctx = getSessionContext();
\r
395 return ctx == null ? null : (org.simantics.project.IProject) ctx.getHint(ProjectKeys.KEY_PROJECT);
\r
399 * @return the currently open and active project for the specified database
\r
400 * session or <code>null</code> if there is no current project
\r
402 public static IProject peekProject(ISessionContext ctx) {
\r
405 return ctx.getHint(ProjectKeys.KEY_PROJECT);
\r
409 * @return the currently open and active project as an IProject
\r
410 * @throws IllegalStateException if there is no currently active database
\r
411 * session, which also means there is no active project at the
\r
414 public static IProject getProject() {
\r
415 ISessionContext ctx = getSessionContext();
\r
417 throw new IllegalStateException("No current database session");
\r
418 return ctx.getHint(ProjectKeys.KEY_PROJECT);
\r
422 * TODO: refactor this out of here
\r
424 * @param imageFilePath
\r
427 public static ImageDescriptor getImageDescriptor(String imageFilePath) {
\r
428 return BundleUtils.getImageDescriptorFromPlugin(PLUGIN_ID, imageFilePath);
\r
432 * TODO: [Tuukka] I'm really unsure this belongs here.
\r
436 * @param assignableFrom
\r
439 public static <T> T filterSingleSelection(ISelection sel, Class<T> assignableFrom) {
\r
441 T result = ISelectionUtils.filterSingleSelection(sel, assignableFrom);
\r
442 if (result != null)
\r
445 Resource resource = ISelectionUtils.filterSingleSelection(sel, Resource.class);
\r
446 if(resource == null) return null;
\r
449 return getSession().syncRequest(new Adapter<T>(resource, assignableFrom));
\r
450 } catch (DatabaseException e) {
\r
451 Logger.defaultLogError(e);
\r
457 public static <T> T filterSingleWorkbenchSelection(Class<T> assignableFrom) {
\r
458 return filterSingleSelection(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getSelectionService().getSelection(), assignableFrom);
\r
462 public static void asyncExecSWT(final Widget widget, final Runnable runnable) {
\r
463 SWTUtils.asyncExec(widget, delayedExecSWT(null, widget, runnable));
\r
466 public static void asyncExecSWT(final Display display, final Runnable runnable) {
\r
467 SWTUtils.asyncExec(display, delayedExecSWT(display, null, runnable));
\r
470 private static Runnable delayedExecSWT(final Display display, final Widget widget, final Runnable runnable) {
\r
471 if (display == null && widget == null)
\r
472 throw new IllegalArgumentException("both display and widget are null");
\r
474 return new Runnable() {
\r
476 public void run() {
\r
477 if (display != null && display.isDisposed())
\r
479 if (widget != null && widget.isDisposed())
\r
481 if (DatabaseJob.inProgress()) {
\r
482 Display d = display != null ? display : widget.getDisplay();
\r
483 d.timerExec(50, this);
\r
491 private static long parseLongProperty(String propertyName, long defaultValue) {
\r
492 String value = System.getProperty(propertyName, null);
\r
494 return value != null ? Long.parseLong(value) : defaultValue;
\r
495 } catch (NumberFormatException e) {
\r
496 return defaultValue;
\r
500 public static boolean isLinuxGTK() {
\r
501 String ws = System.getProperty("osgi.ws");
\r
502 return ws != null && "gtk".equals(ws);
\r