]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/ISelectionUtils.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 / ISelectionUtils.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 /*
13  * 25.8.2006
14  */
15 package org.simantics.utils.ui;
16
17 import java.util.ArrayList;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25
26 import org.eclipse.core.runtime.Assert;
27 import org.eclipse.core.runtime.IAdaptable;
28 import org.eclipse.jface.viewers.ISelection;
29 import org.eclipse.jface.viewers.IStructuredSelection;
30 import org.eclipse.jface.viewers.StructuredSelection;
31 import org.simantics.utils.datastructures.hints.IHintContext;
32 import org.simantics.utils.datastructures.hints.IHintContext.Key;
33
34 /**
35  * ISelectionUtils contains static utility methods for dealing with the
36  * ISelection interface.
37  * 
38  * @author Toni Kalajainen
39  */
40 public class ISelectionUtils<T> {
41
42     /**
43      * This method converts selection sel for to collection of instances of T.
44      * All elements of the selection are assumed to be instances of T.
45      * 
46      * @param <T> type of interest
47      * @param sel selection
48      * @return list
49      * @throws ClassCastException if the selection contains elements not
50      *         instance of T
51      */
52     @SuppressWarnings("unchecked")
53     public static <T> List<T> convertSelection(ISelection sel) throws ClassCastException
54     {
55         if (sel == null || sel.isEmpty() || (!(sel instanceof IStructuredSelection)))
56             return Collections.emptyList();
57
58         List<T> result = new ArrayList<T>();
59         //Class<T> tClass = (Class<T>)result.getClass().getTypeParameters()[0].getBounds()[0];
60         IStructuredSelection ss = (IStructuredSelection) sel;
61         for (Object o : ss.toArray())
62             //if (tClass.isAssignableFrom(o.getClass()))
63             result.add((T)o);
64         return result;
65     }
66
67     /**
68      * This method reads selection sel for all instances of T.
69      * All elements of the selection are assumed to be instances of T.
70      * 
71      * @param <T> type of interest
72      * @param sel selection
73      * @return set
74      * @throws ClassCastException if the selection contains elements not
75      *         instance of T
76      */
77     @SuppressWarnings("unchecked")
78     public static <T> Set<T> convertSetSelection(ISelection sel) throws ClassCastException
79     {
80         if (sel.isEmpty() || (!(sel instanceof IStructuredSelection)))
81             return Collections.emptySet();
82
83         Set<T> result = new HashSet<T>();
84         //Class<T> tClass = (Class<T>)result.getClass().getTypeParameters()[0].getBounds()[0];
85         IStructuredSelection ss = (IStructuredSelection) sel;
86         for (Object o : ss.toArray())
87             //if (tClass.isAssignableFrom(o.getClass()))
88             result.add((T)o);
89         return result;
90     }
91
92
93     @SuppressWarnings("unchecked")
94     public static <T> T convertSingleSelection(ISelection sel)
95     {
96         if (sel.isEmpty() || (!(sel instanceof IStructuredSelection)))
97             return null;
98         IStructuredSelection ss = (IStructuredSelection) sel;
99         return (ss.size() == 1) ? (T) ss.getFirstElement() : null;
100     }
101
102     /**
103      * This method converts selection sel to collection of instances of T
104      * @param <T> type of interest
105      * @param sel selection
106      * @return list
107      */
108     @SuppressWarnings("unchecked")
109     public static <T> List<T> filterSelection(ISelection sel, Class<T> assignableFrom)
110     {
111         if (sel == null || sel.isEmpty() || (!(sel instanceof IStructuredSelection)))
112             return Collections.emptyList();
113
114         List<T> result = new ArrayList<T>();
115         IStructuredSelection ss = (IStructuredSelection) sel;
116         for (Object o : ss.toArray()) {
117             if (o != null && assignableFrom.isAssignableFrom(o.getClass())) {
118                 result.add((T)o);
119             } else if (o instanceof IAdaptable) {
120                 T t = (T) ((IAdaptable) o).getAdapter(assignableFrom);
121                 if (t != null)
122                     result.add(t);
123             }
124         }
125         return result;
126     }
127
128     /**
129      * This method reads selection sel for all instances of T.
130      * 
131      * @param <T> type of interest
132      * @param sel selection
133      * @return all instances of T in sel
134      */
135     public static <T> Set<T> filterSetSelection(Object sel, Class<T> assignableFrom)
136     {
137         if (sel instanceof IStructuredSelection) {
138             IStructuredSelection ss = (IStructuredSelection) sel;
139             if (!ss.isEmpty())
140                 return filterCollection(ss.toList(), assignableFrom, new HashSet<T>());
141         } else if (sel instanceof Collection<?>) {
142             Collection<?> c = (Collection<?>) sel;
143             if (!c.isEmpty())
144                 return filterCollection(c, assignableFrom, new HashSet<T>());
145         }
146
147         return Collections.emptySet();
148     }
149
150     @SuppressWarnings("unchecked")
151     public static <T> T filterSingleSelection(Object sel, Class<T> assignableTo)
152     {
153         if (sel == null) {
154                 return null;
155         } else if (sel instanceof IStructuredSelection) {
156             IStructuredSelection ss = (IStructuredSelection) sel;
157             if (ss.size() == 1)
158                 return tryAdapt(ss.getFirstElement(), assignableTo);
159         } else if (sel instanceof Collection<?>) {
160             Collection<?> c = (Collection<?>) sel;
161             if (c.size() == 1)
162                 return tryAdapt(c.iterator().next(), assignableTo);
163         } else if (sel.getClass().isArray()) {
164                 Object[] os = (Object[])sel;
165                 Object result = null;
166                 for(Object o : os) {
167                         Object r = tryAdapt(o, assignableTo);
168                         if(r != null) {
169                                 if(result != null) return null;
170                                 result = r;
171                         }
172                 }
173                 return (T)result;
174         }
175         return null;
176     }
177
178     public static <T> T findFirstAdaptable(Object sel, Class<T> assignableTo)
179     {
180         if (sel instanceof IStructuredSelection) {
181             IStructuredSelection ss = (IStructuredSelection) sel;
182             if (!ss.isEmpty())
183                 return findFirstAdaptable_(ss.toList(), assignableTo);
184         } else if (sel instanceof Collection<?>) {
185             Collection<?> c = (Collection<?>) sel;
186             if (!c.isEmpty())
187                 return findFirstAdaptable_(c, assignableTo);
188         }
189         return null;
190     }
191
192     private static <T> T findFirstAdaptable_(Collection<?> objects, Class<T> assignableTo) {
193         for (Object o : objects) {
194             T t = tryAdapt(o, assignableTo);
195             if (t != null)
196                 return t;
197         }
198         return null;
199     }
200
201     private static <T, C extends Collection<T>> C filterCollection(Collection<?> objects, Class<T> assignableTo, C result) {
202         for (Object o : objects) {
203             T t = tryAdapt(o, assignableTo);
204             if (t != null)
205                 result.add(t);
206         }
207         return result;
208     }
209
210     @SuppressWarnings("unchecked")
211     private static <T> T tryAdapt(Object o, Class<T> assignableTo) {
212         if (o != null && assignableTo.isAssignableFrom(o.getClass())) {
213             return (T) o;
214         } else if (o instanceof IAdaptable) {
215             T t = (T) ((IAdaptable) o).getAdapter(assignableTo);
216             if (t != null) {
217                 return t;
218             }
219         }
220         return null;
221     }
222
223     /**
224      * Try to extract a single object that is an instance of the specified class
225      * out of the provided selection object.
226      * 
227      * This tries everything even remotely plausible - and then some.
228      * 
229      * This method works as follows:
230      * <ul>
231      * <li>Supported input selection objects: IStructuredSelection and
232      * Collection&lt;?&gt;, IHintContext, IAdaptable, direct instances of the
233      * requested class</li>
234      * <li>Selection elements are assumed to be either instances of
235      * IHintContext, IAdaptable or direct instances of the requested class</li>
236      * <li>If an IHintContext is found, the result object is searched within it
237      * with the specified key.</li>
238      * <li>Searching involves testing whether the hint value is an instance of
239      * clazz or adaptable to it through {@link IAdaptable}.</li>
240      * </ul>
241      * 
242      * @param selection
243      * @param key
244      * @param clazz desired class of the objects to look for in the selection
245      * @return a single objects matching the search criteria. If there are no or
246      *         several matches, <code>null</code> is returned
247      */
248     public static <T> T getSinglePossibleKey(Object object, Key key, Class<T> clazz) {
249         return getSinglePossibleKey(object, key, clazz, true);
250     }
251
252     /**
253      * Try to extract a single object that is an instance of the specified class
254      * out of the provided selection object.
255      * 
256      * This tries everything even remotely plausible - and then some.
257      * 
258      * This method works as follows:
259      * <ul>
260      * <li>Supported input selection objects: IStructuredSelection and
261      * Collection&lt;?&gt;, IHintContext, IAdaptable, direct instances of the
262      * requested class</li>
263      * <li>Selection elements are assumed to be either instances of
264      * IHintContext, IAdaptable or direct instances of the requested class</li>
265      * <li>If an IHintContext is found, the result object is searched within it
266      * with the specified key.</li>
267      * <li>Searching involves testing whether the hint value is an instance of
268      * clazz or adaptable to it through {@link IAdaptable}.</li>
269      * </ul>
270      * 
271      * 
272      * 
273      * @param selection
274      * @param key
275      * @param clazz desired class of the objects to look for in the selection
276      * @return a single objects matching the search criteria. If there are no or
277      *         several matches, <code>null</code> is returned
278      */
279     @SuppressWarnings("unchecked")
280     public static <T> T getSinglePossibleKey(Object object, Key key, Class<T> clazz, boolean allowDirectMatch) {
281         if (object == null)
282             return null;
283
284         // Direct match is returned without key analysis
285         if (allowDirectMatch && clazz.isInstance(object))
286             return (T) object;
287
288         if (object instanceof ISelection) {
289             ISelection sel = (ISelection) object;
290
291             if (sel.isEmpty() || (!(sel instanceof IStructuredSelection)))
292                 return null;
293
294             IStructuredSelection ss = (IStructuredSelection) sel;
295             if (ss.size() != 1)
296                 return null;
297
298             // The result must now be in the first and only element
299             return getSinglePossibleKey(ss.getFirstElement(), key, clazz, allowDirectMatch);
300         }
301
302         if (object instanceof Collection<?>) {
303             Collection<?> c = (Collection<?>) object;
304             if (c.size() != 1)
305                 return null;
306
307             return getSinglePossibleKey(c.iterator().next(), key, clazz, allowDirectMatch);
308         }
309
310         // If there was no success so far now we must take the Key into account
311         if (object instanceof IHintContext) {
312             IHintContext context = (IHintContext) object;
313             Object hint = context.getHint(key);
314
315             // We had a hint context and a matching key was not available - now try single hint - TODO: this somewhat questionable
316             if (hint == null) {
317                 // NOTE: Removed because of a bug:
318                 // https://www.simantics.org/redmine/issues/3061
319                 // Hope this doesn't break existing code.
320                 Map<Key, Object> hints = context.getHints();
321                 // There are multiple hints, thats it, there is no result
322                 if(hints.size() != 1) return null;
323                 hint = hints.values().iterator().next();
324
325                 T t = getSinglePossibleKey(hint, key, clazz);
326 //                if (t != null) {
327 //                    System.out.println("******************** GEEZ: " + t);
328 //                    new Exception().printStackTrace();
329 //                }
330                 return t;
331 //                return null;
332             }
333
334             if (clazz.isInstance(hint)) {
335                 return (T) hint;
336             } else if (hint instanceof IAdaptable) {
337                 T adapter = (T) ((IAdaptable) hint).getAdapter(clazz);
338                 if (adapter != null)
339                     return adapter;
340             } else {
341                 return getSinglePossibleKey(hint, key, clazz);
342             }
343         }
344
345         if (object instanceof IAdaptable)
346             return getSinglePossibleKey(((IAdaptable) object).getAdapter(IHintContext.class), key, clazz, allowDirectMatch);
347
348         return null;
349
350     }
351
352     /**
353      * Try to extract the possible objects that are instances of the specified
354      * class out of the provided selection object.
355      * 
356      * This method works as follows:
357      * <ul>
358      * <li>Supported input selection objects: IStructuredSelection and
359      * Collection&lt;?&gt;</li>
360      * <li>Selection elements are assumed to be IHintContext instances</li>
361      * <li>The result objects are searched for in the IHintContexts with the
362      * specified key.</li>
363      * <li>Searching involves testing whether the hint value is an instance of
364      * clazz or adaptable to it through {@link IAdaptable}.</li>
365      * </ul>
366      * 
367      * All objects that pass through this filter are returned as the result.
368      * 
369      * @param selection
370      * @param key
371      * @param clazz desired class of the objects to look for in the selection
372      * @return list of the criteria matching elements in the selection or empty
373      *         list if nothing suitable was found
374      */
375     public static <T> List<T> getPossibleKeys(Object selection, Key key, Class<T> clazz) {
376         if (selection == null)
377             return Collections.emptyList();
378
379         if (selection instanceof IStructuredSelection) {
380             IStructuredSelection ss = (IStructuredSelection) selection;
381             if (ss.isEmpty())
382                 return Collections.emptyList();
383             return extractPossibleKeys(ss.toList(), key, clazz);
384         }
385
386         if (selection instanceof Collection<?>) {
387             Collection<?> c = (Collection<?>) selection;
388             return extractPossibleKeys(c, key, clazz);
389         }
390
391         if (selection.getClass().isArray()) {
392                 Object[] c = (Object[]) selection;
393             return extractPossibleKeys(c, key, clazz);
394         }
395
396         return extractPossibleKeys(Collections.singleton(selection), key, clazz);
397     }
398
399     @SuppressWarnings("unchecked")
400     private static <T> List<T> extractPossibleKeys(Collection<?> objects, Key key, Class<T> clazz) {
401         if (objects.isEmpty())
402             return Collections.emptyList();
403
404         ArrayList<T> result = new ArrayList<T>(objects.size());
405         for (Object o : objects) {
406             if (o instanceof IHintContext) {
407                 IHintContext context = (IHintContext) o;
408                 Object object = context.getHint(key);
409                 if (object != null) {
410                     if (clazz.isInstance(object)) {
411                         result.add((T) object);
412                     } else if (object instanceof IAdaptable) {
413                         Object adapter = ((IAdaptable) object).getAdapter(clazz);
414                         if (adapter != null)
415                             result.add((T) adapter);
416                     }
417                 }
418             }
419         }
420         return result;
421     }
422
423     @SuppressWarnings("unchecked")
424     private static <T> List<T> extractPossibleKeys(Object[] objects, Key key, Class<T> clazz) {
425         if (objects.length==0)
426             return Collections.emptyList();
427
428         ArrayList<T> result = new ArrayList<T>(objects.length);
429         for (Object o : objects) {
430             if (o instanceof IHintContext) {
431                 IHintContext context = (IHintContext) o;
432                 Object object = context.getHint(key);
433                 if (object != null) {
434                     if (clazz.isInstance(object)) {
435                         result.add((T) object);
436                     } else if (object instanceof IAdaptable) {
437                         Object adapter = ((IAdaptable) object).getAdapter(clazz);
438                         if (adapter != null)
439                             result.add((T) adapter);
440                     }
441                 }
442             }
443         }
444         return result;
445     }
446
447     /**
448      * This method creates selection from set of objects
449      * 
450      * @param objects objects
451      * @return selection
452      */
453     public static ISelection createSelection(Object ... objects)
454     {
455         return new StructuredSelection(objects);
456     }
457
458
459     /**
460      * A testcase.
461      * 
462      * @param args
463      */
464     public static void main(String[] args) {
465         Object o1 = new Integer(9);
466         Object o2 = new HashMap<Object, Object>();
467         Object o3 = new Long(1);
468
469         ISelection s1 = createSelection(o1, o2, o3);
470
471         List<Number> f1 = convertSelection(s1);
472         List<Number> f2 = filterSelection(s1, Number.class);
473
474         System.out.println(f1.toString());
475         System.out.println(f2.toString());
476
477         Assert.isTrue(f2.contains(o1) && f2.contains(o3) && !f2.contains(o2));
478         Assert.isTrue(f1.contains(o1) && f1.contains(o3) && !f1.contains(o2));
479     }
480
481 }