Fix column width issues on HiDPI displays. KeyTiSelection fixes.
[simantics/platform.git] / bundles / org.simantics.browsing.ui.nattable / src / org / simantics / browsing / ui / nattable / TreeNode.java
1 package org.simantics.browsing.ui.nattable;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashSet;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Set;
9
10 import org.eclipse.core.runtime.IAdaptable;
11 import org.eclipse.jface.resource.ColorDescriptor;
12 import org.eclipse.jface.resource.FontDescriptor;
13 import org.eclipse.jface.resource.ImageDescriptor;
14 import org.eclipse.nebula.widgets.nattable.style.CellStyleAttributes;
15 import org.eclipse.nebula.widgets.nattable.style.Style;
16 import org.eclipse.swt.graphics.Color;
17 import org.eclipse.swt.graphics.Font;
18 import org.eclipse.swt.graphics.Image;
19 import org.simantics.browsing.ui.BuiltinKeys;
20 import org.simantics.browsing.ui.NodeContext;
21 import org.simantics.browsing.ui.common.internal.GENodeQueryManager;
22 import org.simantics.browsing.ui.content.ImageDecorator;
23 import org.simantics.browsing.ui.content.Imager;
24 import org.simantics.browsing.ui.content.LabelDecorator;
25 import org.simantics.browsing.ui.content.Labeler;
26 import org.simantics.browsing.ui.nattable.NatTableGraphExplorer.GECache2;
27 import org.simantics.browsing.ui.nattable.NatTableGraphExplorer.GeViewerContext;
28 import org.simantics.browsing.ui.swt.ViewerRowReference;
29 import org.simantics.utils.datastructures.BijectionMap;
30
31 public class TreeNode implements IAdaptable {
32         private static boolean DEBUG = false;
33         
34         private NodeContext context;
35         GENodeQueryManager manager;
36         GeViewerContext explorerContext;
37         
38         TreeNode parent;
39         List<TreeNode> children = new ArrayList<TreeNode>();
40         boolean expanded;
41         boolean autoExpanded = false;
42         
43         public TreeNode(NodeContext context, GeViewerContext explorerContext) {
44                 this.context = context;
45                 this.explorerContext = explorerContext;
46                 this.expanded = false;
47                 manager = new GENodeQueryManager(explorerContext, null, null, ViewerRowReference.create(this));
48                 explorerContext.getContextToNodeMap().add(context, this);
49         }
50         
51         public int getDepth() {
52                 if (parent == null)
53                         return 0;
54                 return parent.getDepth() + 1;
55         }
56         
57         int listIndex = -1;
58         
59         public int getListIndex() {
60                 return listIndex;
61         }
62         
63         public void setListIndex(int listIndex) {
64                 this.listIndex = listIndex;
65         }
66         
67         List<TreeNode> getChildren() {
68                 return children;
69         }
70         
71         public TreeNode getParent() {
72                 return parent;
73         }
74         
75         public void setExpanded(boolean expanded) {
76                 this.expanded = expanded;
77         }
78         
79         public boolean isExpanded() {
80                 return expanded;
81         }
82         
83         public boolean isHidden() {
84                 TreeNode n = parent;
85                 while (n != null) {
86                         if (!n.isExpanded())
87                                 return true;
88                         n = n.getParent();
89                 }
90                 return false;
91         }
92         
93         public TreeNode getCollapsedAncestor() {
94                 TreeNode collapsed = null;
95                 TreeNode n = parent;
96                 while (n != null) {
97                         if (!n.isExpanded())
98                                 collapsed = n;
99                         n = n.getParent();
100                 }
101                 return collapsed;
102         }
103         
104         public NodeContext getContext() {
105                 return context;
106         }
107         
108         private Labeler labeler;
109         private Imager imager;
110         Collection<LabelDecorator> labelDecorators;
111         Collection<ImageDecorator> imageDecorators;
112          
113         Map<String, String> labels;
114         Map<String, String> runtimeLabels;
115         
116         public String getValueString(int column) {
117                 if (labels == null)
118                         initData();
119                 if (labeler != null) {
120                         String key = explorerContext.getGe().getColumns()[column].getKey();
121                         return getValue(key);
122                 }
123                 return null;
124         }
125         
126         private String getValue(String key) {
127                 String s = null;
128                 if (runtimeLabels != null)
129                         s = runtimeLabels.get(key);
130                 if (s == null)
131                         s = labels.get(key);
132                 if (labelDecorators != null && !labelDecorators.isEmpty()) {
133                         int index = 0;
134                         for (LabelDecorator ld : labelDecorators) {
135                                 String ds = ld.decorateLabel(s, key, index);
136                                 if (ds != null)
137                                         s = ds;
138                         }
139                 }
140                 return s;
141         }
142         
143         public String getValueString(String key) {
144                 if (labels == null)
145                         initData();
146                 if (labeler != null) {
147                         return getValue(key);
148                 }
149                 return null;
150         }
151         
152         public Image getImage(int column) {
153                 String key = explorerContext.getGe().getColumns()[column].getKey();
154                 if (imager != null) {
155                         Object descOrImage = null;
156                         boolean hasUncachedImages = false;
157
158                         ImageDescriptor desc = imager.getImage(key);
159                         if (desc != null) {
160                                 int index = 0;
161                                 // Attempt to decorate the label
162                                 if (!imageDecorators.isEmpty()) {
163                                         for (ImageDecorator id : imageDecorators) {
164                                                 ImageDescriptor ds = id.decorateImage(desc, key, index);
165                                                 if (ds != null)
166                                                         desc = ds;
167                                         }
168                                 }
169
170                                 // Try resolving only cached images here and now
171                                 Object img = explorerContext.getGe().localResourceManager.find(desc);
172                                 if (img == null)
173                                         img = explorerContext.getGe().resourceManager.find(desc);
174
175                                 descOrImage = img != null ? img : desc;
176                                 hasUncachedImages |= img == null;
177                         }
178
179                         if (!hasUncachedImages) {
180                                 return (Image) descOrImage;
181                         } else {
182                                 // Schedule loading to another thread to refrain from
183                                 // blocking
184                                 // the UI with database operations.
185                                 explorerContext.getGe().queueImageTask(this, new ImageTask(this, descOrImage));
186                                 return null;
187                         }
188                 } else {
189                         return null;
190                 }
191         }
192         
193         public void getStyle(int column, Style style) {
194                 String key = explorerContext.getGe().getColumns()[column].getKey();
195                 FontDescriptor font = explorerContext.getGe().originalFont;
196                 ColorDescriptor bg = explorerContext.getGe().originalBackground;
197                 ColorDescriptor fg = explorerContext.getGe().originalForeground;
198                 
199                 // Attempt to decorate the label
200                 if (labelDecorators != null && !labelDecorators.isEmpty()) {
201                         int index = 0;
202                         for (LabelDecorator ld : labelDecorators) {
203
204                                 FontDescriptor dfont = ld.decorateFont(font, key, index);
205                                 if (dfont != null)
206                                         font = dfont;
207
208                                 ColorDescriptor dbg = ld.decorateBackground(bg, key, index);
209                                 if (dbg != null)
210                                         bg = dbg;
211
212                                 ColorDescriptor dfg = ld.decorateForeground(fg, key, index);
213                                 if (dfg != null)
214                                         fg = dfg;
215                         }
216                 }
217
218                 if (font != explorerContext.getGe().originalFont) {
219                         // System.out.println("set font: " + index + ": " +
220                         // font);
221                         style.setAttributeValue(CellStyleAttributes.FONT,(Font) explorerContext.getGe().localResourceManager.get(font));
222                 } else {
223                         style.setAttributeValue(CellStyleAttributes.FONT,(Font) (explorerContext.getGe().originalFont != null ? explorerContext.getGe().localResourceManager.get(explorerContext.getGe().originalFont) : null));
224                 }
225                 if (bg != explorerContext.getGe().originalBackground)
226                         style.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR,(Color) explorerContext.getGe().localResourceManager.get(bg));
227                 else
228                         style.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR,(Color) (explorerContext.getGe().originalBackground != null ? explorerContext.getGe().localResourceManager.get(explorerContext.getGe().originalBackground) : null));
229                 if (fg != explorerContext.getGe().originalForeground)
230                         style.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR,(Color) explorerContext.getGe().localResourceManager.get(fg));
231                 else
232                         style.setAttributeValue(CellStyleAttributes.FOREGROUND_COLOR,(Color) (explorerContext.getGe().originalForeground != null ? explorerContext.getGe().localResourceManager.get(explorerContext.getGe().originalForeground) : null));
233
234         }
235         
236         public void initData() {
237                 labeler = manager.query(context, BuiltinKeys.SELECTED_LABELER);
238                 imager = manager.query(context, BuiltinKeys.SELECTED_IMAGER);
239                 labelDecorators = manager.query(context, BuiltinKeys.LABEL_DECORATORS);
240                 imageDecorators = manager.query(context, BuiltinKeys.IMAGE_DECORATORS);
241                 
242                 if (labeler != null) {
243                 labels = labeler.getLabels();
244                         runtimeLabels = labeler.getRuntimeLabels();
245         } else {
246                 labels = null;
247                 runtimeLabels = null;
248         }
249         }
250         
251         public TreeNode addChild(NodeContext context, GeViewerContext explorerContext) {
252                 TreeNode child = new TreeNode(context, explorerContext);
253                 child.parent = this;
254                 children.add(child);
255                 if (DEBUG) System.out.println("Add " + this  + " -> " + child);
256                 return child;
257         }
258         
259         public TreeNode addChild(int index, NodeContext context,GeViewerContext explorerContext) {
260                 
261                 TreeNode child = new TreeNode(context, explorerContext);
262                 child.parent = this;
263                 children.add(index,child);
264                 if (DEBUG) System.out.println("Add " + this  + " -> " + child + " at " + index);
265                 return child;
266         }
267         
268         public TreeNode setChild(int index, NodeContext context, GeViewerContext explorerContext) {
269                 
270                 TreeNode child = new TreeNode(context, explorerContext);
271                 child.parent = this;
272                 children.set(index,child);
273                 if (DEBUG) System.out.println("Set " + this  + " -> " + child + " at " + index);
274                 return child;
275         }
276         
277         public void dispose() {
278                 if (parent != null)
279                         parent.children.remove(this);
280                 dispose2();
281         }
282         
283         public void dispose2() {
284                 if (DEBUG)      System.out.println("dispose " + this);
285                 parent = null;
286                 for (TreeNode n : children) {
287                         n.dispose2();
288                 }
289                 clearCache();
290                 children.clear();
291                 explorerContext.getContextToNodeMap().remove(context, this);
292                 context = null;
293                 explorerContext = null;
294                 manager.dispose();
295                 manager = null; 
296         }
297         
298         private void clearCache() {
299                 if (explorerContext != null) {
300                         GECache2 cache = explorerContext.cache;
301                         
302                         if (cache != null) {
303                                 cache.dispose(context);
304                         }
305                 }
306         }
307         
308         public boolean updateChildren() {
309                 if (context == null)
310                         throw new IllegalStateException("Node is disposed.");
311                 
312                 NodeContext[] childContexts = manager.query(context, BuiltinKeys.FINAL_CHILDREN);
313                 
314                 if (DEBUG) System.out.println("updateChildren " + childContexts.length + " " + this);
315                 
316                 
317                 boolean modified = false;
318                 synchronized (children) {
319                         
320                         int oldCount = children.size();
321                         BijectionMap<Integer, Integer> indexes = new BijectionMap<Integer, Integer>();
322                         Set<Integer> mapped = new HashSet<Integer>();
323                         boolean reorder = false;
324                         // locate matching pairs form old and new children
325                         for (int i = 0; i < oldCount; i++) {
326                                 NodeContext oldCtx = children.get(i).context;
327                                 for (int j = 0; j <childContexts.length; j++) {
328                                         if (mapped.contains(j))
329                                                 continue;
330                                         if (oldCtx.equals(childContexts[j])) {
331                                                 indexes.map(i, j);
332                                                 mapped.add(j);
333                                                 if (i != j)
334                                                         reorder = true;
335                                                 break;
336                                         }
337                                 }
338                         }
339                         // update children if required
340                         if (childContexts.length != oldCount || reorder || childContexts.length != indexes.size()) {
341                                 modified = true;
342                                 List<TreeNode> oldChildren = new ArrayList<TreeNode>(oldCount);
343                                 oldChildren.addAll(children);
344                                 if (childContexts.length >= oldCount) {
345                         for (int i = 0; i < oldCount; i++) {
346                                 Integer oldIndex = indexes.getLeft(i);
347                                 if (oldIndex == null) {
348                                         setChild(i, childContexts[i], explorerContext);
349                                 } else {
350                                         TreeNode n = oldChildren.get(oldIndex);
351                                         children.set(i, n);
352                                 }
353                                 
354                         }
355                         for (int i = oldCount; i < childContexts.length; i++) {
356                                 Integer oldIndex = indexes.getLeft(i);
357                                 if (oldIndex == null) {
358                                         addChild(childContexts[i], explorerContext);
359                                 } else {
360                                         TreeNode n = oldChildren.get(oldIndex);
361                                         children.add(n);
362                                 }
363                         }
364                         } else {
365                                 for (int i = 0; i < childContexts.length; i++) {
366                                         Integer oldIndex = indexes.getLeft(i);
367                                 if (oldIndex == null) {
368                                         setChild(i, childContexts[i], explorerContext);
369                                 } else {
370                                         TreeNode n = oldChildren.get(oldIndex);
371                                         children.set(i, n);
372                                 }
373                         }
374                                 for (int i = oldCount -1; i >= childContexts.length; i--) {
375                                         children.remove(i);
376                                 }
377                         }
378                                 for (int i = 0; i < oldChildren.size(); i++) {
379                                         if (!indexes.containsLeft(i)) {
380                                                 oldChildren.get(i).dispose2();
381                                         }
382                                 }
383                                 
384                         }
385                 
386                 }
387                 return modified;
388         }
389         
390         public boolean isDisposed() {
391                 return context == null;
392         }
393         
394         public GENodeQueryManager getManager() {
395                 return manager;
396         }
397         
398         @SuppressWarnings("rawtypes")
399         @Override
400         public Object getAdapter(Class adapter) {
401                 if (adapter == NodeContext.class)
402                         return context;
403                 
404                 return context.getAdapter(adapter);
405         }
406         
407         @Override
408         public String toString() {
409                 return "TreeNode: " + listIndex + " " + (expanded ? "(+)" : "(-)") + " " + context ;
410         }
411
412 }