source: java/main/src/main/java/com/framsticks/gui/tree/TreeModel.java @ 193

Last change on this file since 193 was 193, checked in by Maciej Komosinski, 10 years ago

Set svn:eol-style native for all textual files

  • Property svn:eol-style set to native
File size: 12.4 KB
Line 
1package com.framsticks.gui.tree;
2
3import java.util.Enumeration;
4import java.util.Iterator;
5import java.util.LinkedList;
6import java.util.List;
7
8import javax.annotation.Nullable;
9import javax.swing.event.TreeModelEvent;
10import javax.swing.event.TreeModelListener;
11import javax.swing.tree.TreePath;
12
13import org.apache.logging.log4j.Logger;
14import org.apache.logging.log4j.LogManager;
15
16import com.framsticks.gui.Frame;
17import com.framsticks.params.Access;
18import com.framsticks.params.CompositeParam;
19import com.framsticks.params.EventListener;
20import com.framsticks.params.ListAccess;
21import com.framsticks.params.PrimitiveParam;
22import com.framsticks.params.ParamsUtil;
23import com.framsticks.params.ValueParam;
24import com.framsticks.params.types.EventParam;
25import com.framsticks.structure.Node;
26import com.framsticks.structure.Path;
27import com.framsticks.structure.SideNoteKey;
28import com.framsticks.structure.TreeOperations;
29import com.framsticks.structure.messages.ListChange;
30import com.framsticks.structure.messages.ValueChange;
31import com.framsticks.util.Misc;
32import com.framsticks.util.FramsticksUnsupportedOperationException;
33import com.framsticks.util.dispatching.Future;
34import com.framsticks.util.lang.Casting;
35
36import static com.framsticks.structure.TreeOperations.*;
37
38public class TreeModel implements javax.swing.tree.TreeModel {
39        private static final Logger log = LogManager.getLogger(TreeModel.class);
40
41
42        protected List<TreeModelListener> listeners = new LinkedList<>();
43
44        protected final Frame frame;
45
46        /**
47         * @param frame
48         */
49        public TreeModel(Frame frame) {
50                this.frame = frame;
51        }
52
53        @Override
54        public void addTreeModelListener(TreeModelListener listener) {
55                listeners.add(listener);
56        }
57
58        @Override
59        public Object getChild(Object parent, int number) {
60                return Casting.throwCast(AbstractNode.class, parent).getChild(number);
61        }
62
63        @Override
64        public int getChildCount(Object parent) {
65                return Casting.throwCast(AbstractNode.class, parent).getChildCount();
66        }
67
68        @Override
69        public int getIndexOfChild(Object parent, Object child) {
70                if ((parent == null) || (child == null)) {
71                        return -1;
72                }
73                return Casting.throwCast(AbstractNode.class, parent).getIndexOfChild(child);
74        }
75
76        @Override
77        public MetaNode getRoot() {
78                return frame.getRootNode();
79        }
80
81        @Override
82        public boolean isLeaf(Object node) {
83                return Casting.throwCast(AbstractNode.class, node).isLeaf();
84        }
85
86        @Override
87        public void removeTreeModelListener(TreeModelListener listener) {
88                listeners.remove(listener);
89        }
90
91        @Override
92        public void valueForPathChanged(TreePath path, Object value) {
93                throw new FramsticksUnsupportedOperationException().msg("changing value of tree node");
94        }
95
96
97        protected boolean changing = false;
98
99        public void treeNodesInserted(TreeModelEvent event) {
100                assert frame.isActive();
101                try {
102                        for (TreeModelListener listener : listeners) {
103                                listener.treeNodesInserted(event);
104                        }
105                } catch (ArrayIndexOutOfBoundsException e) {
106                }
107        }
108
109        public void treeNodesRemoved(TreeModelEvent event) {
110                assert frame.isActive();
111                try {
112                        for (TreeModelListener listener : listeners) {
113                                listener.treeNodesRemoved(event);
114                        }
115                } catch (ArrayIndexOutOfBoundsException e) {
116                }
117        }
118
119        public void treeNodesChanged(TreeModelEvent event) {
120                try {
121                        for (TreeModelListener listener : listeners) {
122                                listener.treeNodesChanged(event);
123                        }
124                } catch (ArrayIndexOutOfBoundsException e) {
125                }
126        }
127
128        public TreeModelEvent prepareModelEvent(TreePath treePath, int number, TreeNode node) {
129                return new TreeModelEvent(this, treePath, new int[] {number}, new Object[] { node });
130        }
131
132
133        public TreeModelEvent prepareModelEventRegarding(Access access, String id, TreePath treeListPath) {
134
135                int number = ParamsUtil.getNumberOfCompositeParamChild(access, access.get(id, Object.class));
136                if (number == -1) {
137                        log.debug("encountered minor tree inconsistency in {}", treeListPath);
138                        return null;
139                }
140                TreeNode node = Casting.throwCast(TreeNode.class, Casting.throwCast(TreeNode.class, treeListPath.getLastPathComponent()).getChild(number));
141                return prepareModelEvent(treeListPath, number, node);
142        }
143
144        public void treeStructureChanged(TreePath treePath) {
145
146                if (treePath == null) {
147                        return;
148                }
149                assert frame.isActive();
150
151                changing = true;
152                log.debug("changing structure: {}", treePath);
153                Enumeration<TreePath> expanded = frame.getJtree().getExpandedDescendants(treePath);
154                TreePath selection = frame.getJtree().getSelectionPath();
155
156                try {
157                        for (TreeModelListener listener : listeners) {
158                                listener.treeStructureChanged(new TreeModelEvent(this, treePath));
159                        }
160                } catch (ArrayIndexOutOfBoundsException e) {
161                }
162
163
164                if (expanded != null) {
165                        while (expanded.hasMoreElements()) {
166                                TreePath expansion = expanded.nextElement();
167                                // log.info("reexpanding: {}", expansion);
168                                frame.getJtree().expandPath(expansion);
169                        }
170                }
171
172                if (selection != null) {
173                        frame.getJtree().setSelectionPath(selection);
174                }
175                changing = false;
176        }
177
178        /**
179         *
180         * This method may return null on conversion failure, which may happen in highload situations.
181         */
182        public @Nullable Path convertToPath(TreePath treePath) {
183                final Object[] components = treePath.getPath();
184                assert components[0] == frame.getRootNode();
185                if (components.length == 1) {
186                        return null;
187                }
188                Path.PathBuilder builder = Path.build();
189                builder.tree(Casting.assertCast(TreeNode.class, components[1]).getTree());
190                List<Node> nodes = new LinkedList<>();
191                for (int i = 1; i < components.length; ++i) {
192                        TreeNode treeNode = Casting.tryCast(TreeNode.class, components[i]);
193                        if (treeNode == null) {
194                                return null;
195                        }
196                        Node node = treeNode.tryCreateNode();
197                        if (node == null) {
198                                return null;
199                                // throw new FramsticksException().msg("failed to recreate path").arg("treePath", treePath);
200                        }
201                        nodes.add(node);
202                }
203                builder.buildUpTo(nodes, null);
204
205                return builder.finish();
206        }
207
208        public TreePath convertToTreePath(Path path) {
209                assert frame.isActive();
210
211                List<Object> accumulator = new LinkedList<Object>();
212                accumulator.add(getRoot());
213
214                for (Object r : getRoot().getChildren()) {
215                        if (r instanceof TreeNode) {
216                                TreeNode root = (TreeNode) r;
217                                if (root.getTree() == path.getTree()) {
218                                        Iterator<Node> n = path.getNodes().iterator();
219                                        TreeNode treeNode = root;
220                                        accumulator.add(root);
221                                        n.next();
222                                        while (n.hasNext()) {
223                                                Node node = n.next();
224                                                treeNode = treeNode.prepareTreeNodeForChild(Path.build().tree(path.getTree()).buildUpTo(path.getNodes(), node).finish());
225                                                if (treeNode == null) {
226                                                        break;
227                                                }
228                                                accumulator.add(treeNode);
229                                        }
230                                        break;
231                                }
232                        }
233                }
234                return new TreePath(accumulator.toArray());
235        }
236
237        /**
238         * @return the listeners
239         */
240        public List<TreeModelListener> getListeners() {
241                return listeners;
242        }
243
244        /**
245         * @return the changing
246         */
247        public boolean isChanging() {
248                return changing;
249        }
250
251        public void loadChildren(Path path, boolean reload) {
252                if (path == null) {
253                        return;
254                }
255                Access access = TreeOperations.bindAccess(path);
256
257                int count = access.getCompositeParamCount();
258                for (int i = 0; i < count; ++i) {
259                        Path childPath = path.appendParam(access.getCompositeParam(i)).tryFindResolution();
260                        loadPath(childPath, reload);
261                }
262        }
263
264        public void loadPath(Path path, boolean reload) {
265                if (path == null) {
266                        return;
267                }
268                if (!reload && path.isResolved() && isMarked(path.getTree(), path.getTopObject(), FETCHED_MARK, false)) {
269                        return;
270                }
271                path.getTree().get(path, new Future<Path>(frame) {
272                        @Override
273                        protected void result(Path result) {
274                                final TreePath treePath = convertToTreePath(result);
275
276
277                                if (treePath != null) {
278                                        treeStructureChanged(treePath);
279                                        frame.updatePanelIfIsLeadSelection(result);
280                                }
281                        }
282                });
283        }
284
285        public void expandTreeNode(TreePath treePath) {
286                assert frame.isActive();
287                if (treePath == null) {
288                        return;
289                }
290                if (isChanging()) {
291                        return;
292                }
293                Path path = convertToPath(treePath);
294                if (path == null) {
295                        return;
296                }
297                loadChildren(path.assureResolved(), false);
298        }
299
300        public void chooseTreeNode(final TreePath treePath) {
301                assert frame.isActive();
302                if (treePath == null) {
303                        return;
304                }
305                if (isChanging()) {
306                        return;
307                }
308
309                Path path = convertToPath(treePath);
310                if (path == null) {
311                        return;
312                }
313                path = path.assureResolved();
314
315                log.debug("choosing {}", path);
316                frame.showPanelForTreePath(treePath);
317                loadPath(path, false);
318
319        }
320
321
322        protected void registerForEventParam(final TreeNode treeNode, Path path, final EventParam eventParam, final ValueParam valueParam) {
323                /** TODO make this listener not bind hold the reference to this TreeNode, maybe hold WeakReference internally */
324                if (valueParam instanceof PrimitiveParam) {
325
326                        treeNode.tryAddListener(path, eventParam, Object.class, new EventListener<Object>() {
327                                @Override
328                                public void action(Object argument) {
329                                        assert treeNode.getTree().isActive();
330                                        if (argument instanceof ValueChange) {
331                                                ValueChange valueChange = (ValueChange) argument;
332                                                Path path = treeNode.assurePath();
333                                                bindAccess(path).set(valueParam, valueChange.value);
334                                                frame.updatePanelIfIsLeadSelection(path);
335                                        } else {
336                                                loadPath(treeNode.assurePath(), true);
337                                        }
338                                }
339                        });
340
341                } else if (valueParam instanceof CompositeParam) {
342
343                        final CompositeParam compositeParam = (CompositeParam) valueParam;
344
345                        treeNode.tryAddListener(path, eventParam, ListChange.class, new EventListener<ListChange>() {
346                                @Override
347                                public void action(ListChange listChange) {
348                                        assert treeNode.getTree().isActive();
349
350                                        Path parentPath = treeNode.assurePath();
351                                        final Path listPath = parentPath.appendParam(compositeParam).tryFindResolution();
352                                        if (!listPath.isResolved()) {
353                                                /** that situation is quietly ignored - it may happen if first event comes before the container was resolved */
354                                                return;
355                                        }
356
357                                        log.debug("reacting to change {} in {}", listChange, listPath);
358                                        final TreePath treeListPath = convertToTreePath(listPath);
359
360                                        if ((listChange.getAction().equals(ListChange.Action.Modify)) && (listChange.getPosition() == -1)) {
361                                                // get(listPath, future);
362                                                // treeModel.nodeStructureChanged(treePath);
363                                                // frame.updatePanelIfIsLeadSelection(treePath, result);
364                                                return;
365                                        }
366                                        final String id = listChange.getBestIdentifier();
367
368                                        final ListAccess access = (ListAccess) bindAccess(listPath);
369                                        switch (listChange.getAction()) {
370                                                case Add: {
371                                                        Path childPath = listPath.appendParam(access.prepareParamFor(id)).tryFindResolution();
372                                                        if (!childPath.isResolved()) {
373                                                                childPath = create(childPath);
374
375                                                                TreeModelEvent event = prepareModelEventRegarding(access, id, treeListPath);
376                                                                if (event != null) {
377                                                                        treeNodesInserted(event);
378                                                                } else {
379                                                                        treeStructureChanged(treeListPath);
380                                                                }
381                                                                frame.updatePanelIfIsLeadSelection(listPath);
382                                                        }
383
384                                                        listPath.getTree().get(childPath, new Future<Path>(frame) {
385                                                                @Override
386                                                                protected void result(Path result) {
387                                                                        if (!result.isResolved()) {
388                                                                                log.warn("inconsistency after addition list change: {}", result);
389                                                                        }
390                                                                        assert frame.isActive();
391                                                                        final TreePath treePath = Misc.throwIfNull(frame.getTreeModel().convertToTreePath(result));
392
393                                                                        // treeModel.nodeStructureChanged(treePath);
394                                                                        frame.updatePanelIfIsLeadSelection(result);
395
396                                                                        log.debug("added {}({}) updated {}", id, result, treePath);
397                                                                }
398                                                        });
399                                                        break;
400                                                }
401                                                case Remove: {
402
403                                                        TreeModelEvent event = prepareModelEventRegarding(access, id, treeListPath);
404                                                        access.set(id, null);
405                                                        if (event != null) {
406                                                                treeNodesRemoved(event);
407                                                        } else {
408                                                                treeStructureChanged(treeListPath);
409                                                        }
410
411                                                        frame.updatePanelIfIsLeadSelection(listPath);
412
413                                                        break;
414                                                }
415                                                case Modify: {
416                                                        Path childPath = listPath.appendParam(access.prepareParamFor(id)).tryResolveIfNeeded();
417                                                        listPath.getTree().get(childPath, new Future<Path>(frame) {
418                                                                @Override
419                                                                protected void result(Path result) {
420                                                                        assert frame.isActive();
421                                                                        // final TreePath treePath = frame.getTreeModel().convertToTreePath(result, true);
422
423                                                                        TreeModelEvent event = prepareModelEventRegarding(access, id, treeListPath);
424                                                                        if (event != null) {
425                                                                                treeNodesChanged(event);
426                                                                        } else {
427                                                                                treeStructureChanged(treeListPath);
428                                                                        }
429
430                                                                        frame.updatePanelIfIsLeadSelection(listPath);
431                                                                        frame.updatePanelIfIsLeadSelection(result);
432                                                                }
433                                                        });
434                                                        break;
435                                                }
436                                        }
437                                }
438                        });
439                }
440
441        }
442
443
444
445        protected final SideNoteKey<Boolean> createdTag = SideNoteKey.make(Boolean.class);
446
447
448
449}
Note: See TracBrowser for help on using the repository browser.