Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/demo/share/jfc/Notepad/ElementTreePanel.java
41149 views
1
/*
2
* Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved.
3
*
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions
6
* are met:
7
*
8
* - Redistributions of source code must retain the above copyright
9
* notice, this list of conditions and the following disclaimer.
10
*
11
* - Redistributions in binary form must reproduce the above copyright
12
* notice, this list of conditions and the following disclaimer in the
13
* documentation and/or other materials provided with the distribution.
14
*
15
* - Neither the name of Oracle nor the names of its
16
* contributors may be used to endorse or promote products derived
17
* from this software without specific prior written permission.
18
*
19
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
20
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
23
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
24
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
27
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
*/
31
32
/*
33
* This source code is provided to illustrate the usage of a given feature
34
* or technique and has been deliberately simplified. Additional steps
35
* required for a production-quality application, such as security checks,
36
* input validation and proper error handling, might not be present in
37
* this sample code.
38
*/
39
40
41
42
import java.awt.BorderLayout;
43
import java.awt.Dimension;
44
import java.awt.Font;
45
import java.beans.PropertyChangeEvent;
46
import java.beans.PropertyChangeListener;
47
import java.util.*;
48
import javax.swing.JLabel;
49
import javax.swing.JPanel;
50
import javax.swing.JScrollPane;
51
import javax.swing.JTree;
52
import javax.swing.SwingConstants;
53
import javax.swing.event.CaretEvent;
54
import javax.swing.event.CaretListener;
55
import javax.swing.event.DocumentEvent;
56
import javax.swing.event.DocumentListener;
57
import javax.swing.event.TreeSelectionEvent;
58
import javax.swing.event.TreeSelectionListener;
59
import javax.swing.text.AttributeSet;
60
import javax.swing.text.Document;
61
import javax.swing.text.Element;
62
import javax.swing.text.JTextComponent;
63
import javax.swing.text.StyleConstants;
64
import javax.swing.tree.DefaultMutableTreeNode;
65
import javax.swing.tree.DefaultTreeCellRenderer;
66
import javax.swing.tree.DefaultTreeModel;
67
import javax.swing.tree.TreeModel;
68
import javax.swing.tree.TreeNode;
69
import javax.swing.tree.TreePath;
70
71
72
/**
73
* Displays a tree showing all the elements in a text Document. Selecting
74
* a node will result in reseting the selection of the JTextComponent.
75
* This also becomes a CaretListener to know when the selection has changed
76
* in the text to update the selected item in the tree.
77
*
78
* @author Scott Violet
79
*/
80
@SuppressWarnings("serial")
81
public class ElementTreePanel extends JPanel implements CaretListener,
82
DocumentListener, PropertyChangeListener, TreeSelectionListener {
83
84
/** Tree showing the documents element structure. */
85
protected JTree tree;
86
/** Text component showing elemenst for. */
87
protected JTextComponent editor;
88
/** Model for the tree. */
89
protected ElementTreeModel treeModel;
90
/** Set to true when updatin the selection. */
91
protected boolean updatingSelection;
92
93
@SuppressWarnings("LeakingThisInConstructor")
94
public ElementTreePanel(JTextComponent editor) {
95
this.editor = editor;
96
97
Document document = editor.getDocument();
98
99
// Create the tree.
100
treeModel = new ElementTreeModel(document);
101
tree = new JTree(treeModel) {
102
103
@Override
104
public String convertValueToText(Object value, boolean selected,
105
boolean expanded, boolean leaf,
106
int row, boolean hasFocus) {
107
// Should only happen for the root
108
if (!(value instanceof Element)) {
109
return value.toString();
110
}
111
112
Element e = (Element) value;
113
AttributeSet as = e.getAttributes().copyAttributes();
114
String asString;
115
116
if (as != null) {
117
StringBuilder retBuffer = new StringBuilder("[");
118
Enumeration<?> names = as.getAttributeNames();
119
120
while (names.hasMoreElements()) {
121
Object nextName = names.nextElement();
122
123
if (nextName != StyleConstants.ResolveAttribute) {
124
retBuffer.append(" ");
125
retBuffer.append(nextName);
126
retBuffer.append("=");
127
retBuffer.append(as.getAttribute(nextName));
128
}
129
}
130
retBuffer.append(" ]");
131
asString = retBuffer.toString();
132
} else {
133
asString = "[ ]";
134
}
135
136
if (e.isLeaf()) {
137
return e.getName() + " [" + e.getStartOffset() + ", " + e.
138
getEndOffset() + "] Attributes: " + asString;
139
}
140
return e.getName() + " [" + e.getStartOffset() + ", " + e.
141
getEndOffset() + "] Attributes: " + asString;
142
}
143
};
144
tree.addTreeSelectionListener(this);
145
tree.setDragEnabled(true);
146
// Don't show the root, it is fake.
147
tree.setRootVisible(false);
148
// Since the display value of every node after the insertion point
149
// changes every time the text changes and we don't generate a change
150
// event for all those nodes the display value can become off.
151
// This can be seen as '...' instead of the complete string value.
152
// This is a temporary workaround, increase the needed size by 15,
153
// hoping that will be enough.
154
tree.setCellRenderer(new DefaultTreeCellRenderer() {
155
156
@Override
157
public Dimension getPreferredSize() {
158
Dimension retValue = super.getPreferredSize();
159
if (retValue != null) {
160
retValue.width += 15;
161
}
162
return retValue;
163
}
164
});
165
// become a listener on the document to update the tree.
166
document.addDocumentListener(this);
167
168
// become a PropertyChangeListener to know when the Document has
169
// changed.
170
editor.addPropertyChangeListener(this);
171
172
// Become a CaretListener
173
editor.addCaretListener(this);
174
175
// configure the panel and frame containing it.
176
setLayout(new BorderLayout());
177
add(new JScrollPane(tree), BorderLayout.CENTER);
178
179
// Add a label above tree to describe what is being shown
180
JLabel label = new JLabel("Elements that make up the current document",
181
SwingConstants.CENTER);
182
183
label.setFont(new Font("Dialog", Font.BOLD, 14));
184
add(label, BorderLayout.NORTH);
185
186
setPreferredSize(new Dimension(400, 400));
187
}
188
189
/**
190
* Resets the JTextComponent to <code>editor</code>. This will update
191
* the tree accordingly.
192
*/
193
public void setEditor(JTextComponent editor) {
194
if (this.editor == editor) {
195
return;
196
}
197
198
if (this.editor != null) {
199
Document oldDoc = this.editor.getDocument();
200
201
oldDoc.removeDocumentListener(this);
202
this.editor.removePropertyChangeListener(this);
203
this.editor.removeCaretListener(this);
204
}
205
this.editor = editor;
206
if (editor == null) {
207
treeModel = null;
208
tree.setModel(null);
209
} else {
210
Document newDoc = editor.getDocument();
211
212
newDoc.addDocumentListener(this);
213
editor.addPropertyChangeListener(this);
214
editor.addCaretListener(this);
215
treeModel = new ElementTreeModel(newDoc);
216
tree.setModel(treeModel);
217
}
218
}
219
220
// PropertyChangeListener
221
/**
222
* Invoked when a property changes. We are only interested in when the
223
* Document changes to reset the DocumentListener.
224
*/
225
public void propertyChange(PropertyChangeEvent e) {
226
if (e.getSource() == getEditor() && e.getPropertyName().equals(
227
"document")) {
228
Document oldDoc = (Document) e.getOldValue();
229
Document newDoc = (Document) e.getNewValue();
230
231
// Reset the DocumentListener
232
oldDoc.removeDocumentListener(this);
233
newDoc.addDocumentListener(this);
234
235
// Recreate the TreeModel.
236
treeModel = new ElementTreeModel(newDoc);
237
tree.setModel(treeModel);
238
}
239
}
240
241
// DocumentListener
242
/**
243
* Gives notification that there was an insert into the document. The
244
* given range bounds the freshly inserted region.
245
*
246
* @param e the document event
247
*/
248
public void insertUpdate(DocumentEvent e) {
249
updateTree(e);
250
}
251
252
/**
253
* Gives notification that a portion of the document has been
254
* removed. The range is given in terms of what the view last
255
* saw (that is, before updating sticky positions).
256
*
257
* @param e the document event
258
*/
259
public void removeUpdate(DocumentEvent e) {
260
updateTree(e);
261
}
262
263
/**
264
* Gives notification that an attribute or set of attributes changed.
265
*
266
* @param e the document event
267
*/
268
public void changedUpdate(DocumentEvent e) {
269
updateTree(e);
270
}
271
272
// CaretListener
273
/**
274
* Messaged when the selection in the editor has changed. Will update
275
* the selection in the tree.
276
*/
277
public void caretUpdate(CaretEvent e) {
278
if (!updatingSelection) {
279
int selBegin = Math.min(e.getDot(), e.getMark());
280
int end = Math.max(e.getDot(), e.getMark());
281
List<TreePath> paths = new ArrayList<TreePath>();
282
TreeModel model = getTreeModel();
283
Object root = model.getRoot();
284
int rootCount = model.getChildCount(root);
285
286
// Build an array of all the paths to all the character elements
287
// in the selection.
288
for (int counter = 0; counter < rootCount; counter++) {
289
int start = selBegin;
290
291
while (start <= end) {
292
TreePath path = getPathForIndex(start, root,
293
(Element) model.getChild(root, counter));
294
Element charElement = (Element) path.getLastPathComponent();
295
296
paths.add(path);
297
if (start >= charElement.getEndOffset()) {
298
start++;
299
} else {
300
start = charElement.getEndOffset();
301
}
302
}
303
}
304
305
// If a path was found, select it (them).
306
int numPaths = paths.size();
307
308
if (numPaths > 0) {
309
TreePath[] pathArray = new TreePath[numPaths];
310
311
paths.toArray(pathArray);
312
updatingSelection = true;
313
try {
314
getTree().setSelectionPaths(pathArray);
315
getTree().scrollPathToVisible(pathArray[0]);
316
} finally {
317
updatingSelection = false;
318
}
319
}
320
}
321
}
322
323
// TreeSelectionListener
324
/**
325
* Called whenever the value of the selection changes.
326
* @param e the event that characterizes the change.
327
*/
328
public void valueChanged(TreeSelectionEvent e) {
329
330
if (!updatingSelection && tree.getSelectionCount() == 1) {
331
TreePath selPath = tree.getSelectionPath();
332
Object lastPathComponent = selPath.getLastPathComponent();
333
334
if (!(lastPathComponent instanceof DefaultMutableTreeNode)) {
335
Element selElement = (Element) lastPathComponent;
336
337
updatingSelection = true;
338
try {
339
getEditor().select(selElement.getStartOffset(),
340
selElement.getEndOffset());
341
} finally {
342
updatingSelection = false;
343
}
344
}
345
}
346
}
347
348
// Local methods
349
/**
350
* @return tree showing elements.
351
*/
352
protected JTree getTree() {
353
return tree;
354
}
355
356
/**
357
* @return JTextComponent showing elements for.
358
*/
359
protected JTextComponent getEditor() {
360
return editor;
361
}
362
363
/**
364
* @return TreeModel implementation used to represent the elements.
365
*/
366
public DefaultTreeModel getTreeModel() {
367
return treeModel;
368
}
369
370
/**
371
* Updates the tree based on the event type. This will invoke either
372
* updateTree with the root element, or handleChange.
373
*/
374
protected void updateTree(DocumentEvent event) {
375
updatingSelection = true;
376
try {
377
TreeModel model = getTreeModel();
378
Object root = model.getRoot();
379
380
for (int counter = model.getChildCount(root) - 1; counter >= 0;
381
counter--) {
382
updateTree(event, (Element) model.getChild(root, counter));
383
}
384
} finally {
385
updatingSelection = false;
386
}
387
}
388
389
/**
390
* Creates TreeModelEvents based on the DocumentEvent and messages
391
* the treemodel. This recursively invokes this method with children
392
* elements.
393
* @param event indicates what elements in the tree hierarchy have
394
* changed.
395
* @param element Current element to check for changes against.
396
*/
397
protected void updateTree(DocumentEvent event, Element element) {
398
DocumentEvent.ElementChange ec = event.getChange(element);
399
400
if (ec != null) {
401
Element[] removed = ec.getChildrenRemoved();
402
Element[] added = ec.getChildrenAdded();
403
int startIndex = ec.getIndex();
404
405
// Check for removed.
406
if (removed != null && removed.length > 0) {
407
int[] indices = new int[removed.length];
408
409
for (int counter = 0; counter < removed.length; counter++) {
410
indices[counter] = startIndex + counter;
411
}
412
getTreeModel().nodesWereRemoved((TreeNode) element, indices,
413
removed);
414
}
415
// check for added
416
if (added != null && added.length > 0) {
417
int[] indices = new int[added.length];
418
419
for (int counter = 0; counter < added.length; counter++) {
420
indices[counter] = startIndex + counter;
421
}
422
getTreeModel().nodesWereInserted((TreeNode) element, indices);
423
}
424
}
425
if (!element.isLeaf()) {
426
int startIndex = element.getElementIndex(event.getOffset());
427
int elementCount = element.getElementCount();
428
int endIndex = Math.min(elementCount - 1,
429
element.getElementIndex(event.getOffset()
430
+ event.getLength()));
431
432
if (startIndex > 0 && startIndex < elementCount && element.
433
getElement(startIndex).getStartOffset() == event.getOffset()) {
434
// Force checking the previous element.
435
startIndex--;
436
}
437
if (startIndex != -1 && endIndex != -1) {
438
for (int counter = startIndex; counter <= endIndex; counter++) {
439
updateTree(event, element.getElement(counter));
440
}
441
}
442
} else {
443
// Element is a leaf, assume it changed
444
getTreeModel().nodeChanged((TreeNode) element);
445
}
446
}
447
448
/**
449
* Returns a TreePath to the element at <code>position</code>.
450
*/
451
protected TreePath getPathForIndex(int position, Object root,
452
Element rootElement) {
453
TreePath path = new TreePath(root);
454
Element child = rootElement.getElement(rootElement.getElementIndex(
455
position));
456
457
path = path.pathByAddingChild(rootElement);
458
path = path.pathByAddingChild(child);
459
while (!child.isLeaf()) {
460
child = child.getElement(child.getElementIndex(position));
461
path = path.pathByAddingChild(child);
462
}
463
return path;
464
}
465
466
467
/**
468
* ElementTreeModel is an implementation of TreeModel to handle displaying
469
* the Elements from a Document. AbstractDocument.AbstractElement is
470
* the default implementation used by the swing text package to implement
471
* Element, and it implements TreeNode. This makes it trivial to create
472
* a DefaultTreeModel rooted at a particular Element from the Document.
473
* Unfortunately each Document can have more than one root Element.
474
* Implying that to display all the root elements as a child of another
475
* root a fake node has be created. This class creates a fake node as
476
* the root with the children being the root elements of the Document
477
* (getRootElements).
478
* <p>This subclasses DefaultTreeModel. The majority of the TreeModel
479
* methods have been subclassed, primarily to special case the root.
480
*/
481
public static class ElementTreeModel extends DefaultTreeModel {
482
483
protected Element[] rootElements;
484
485
public ElementTreeModel(Document document) {
486
super(new DefaultMutableTreeNode("root"), false);
487
rootElements = document.getRootElements();
488
}
489
490
/**
491
* Returns the child of <I>parent</I> at index <I>index</I> in
492
* the parent's child array. <I>parent</I> must be a node
493
* previously obtained from this data source. This should
494
* not return null if <i>index</i> is a valid index for
495
* <i>parent</i> (that is <i>index</i> >= 0 && <i>index</i>
496
* < getChildCount(<i>parent</i>)).
497
*
498
* @param parent a node in the tree, obtained from this data source
499
* @return the child of <I>parent</I> at index <I>index</I>
500
*/
501
@Override
502
public Object getChild(Object parent, int index) {
503
if (parent == root) {
504
return rootElements[index];
505
}
506
return super.getChild(parent, index);
507
}
508
509
/**
510
* Returns the number of children of <I>parent</I>. Returns 0
511
* if the node is a leaf or if it has no children.
512
* <I>parent</I> must be a node previously obtained from this
513
* data source.
514
*
515
* @param parent a node in the tree, obtained from this data source
516
* @return the number of children of the node <I>parent</I>
517
*/
518
@Override
519
public int getChildCount(Object parent) {
520
if (parent == root) {
521
return rootElements.length;
522
}
523
return super.getChildCount(parent);
524
}
525
526
/**
527
* Returns true if <I>node</I> is a leaf. It is possible for
528
* this method to return false even if <I>node</I> has no
529
* children. A directory in a filesystem, for example, may
530
* contain no files; the node representing the directory is
531
* not a leaf, but it also has no children.
532
*
533
* @param node a node in the tree, obtained from this data source
534
* @return true if <I>node</I> is a leaf
535
*/
536
@Override
537
public boolean isLeaf(Object node) {
538
if (node == root) {
539
return false;
540
}
541
return super.isLeaf(node);
542
}
543
544
/**
545
* Returns the index of child in parent.
546
*/
547
@Override
548
public int getIndexOfChild(Object parent, Object child) {
549
if (parent == root) {
550
for (int counter = rootElements.length - 1; counter >= 0;
551
counter--) {
552
if (rootElements[counter] == child) {
553
return counter;
554
}
555
}
556
return -1;
557
}
558
return super.getIndexOfChild(parent, child);
559
}
560
561
/**
562
* Invoke this method after you've changed how node is to be
563
* represented in the tree.
564
*/
565
@Override
566
public void nodeChanged(TreeNode node) {
567
if (listenerList != null && node != null) {
568
TreeNode parent = node.getParent();
569
570
if (parent == null && node != root) {
571
parent = root;
572
}
573
if (parent != null) {
574
int anIndex = getIndexOfChild(parent, node);
575
576
if (anIndex != -1) {
577
int[] cIndexs = new int[1];
578
579
cIndexs[0] = anIndex;
580
nodesChanged(parent, cIndexs);
581
}
582
}
583
}
584
}
585
586
/**
587
* Returns the path to a particluar node. This is recursive.
588
*/
589
@Override
590
protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
591
TreeNode[] retNodes;
592
593
/* Check for null, in case someone passed in a null node, or
594
they passed in an element that isn't rooted at root. */
595
if (aNode == null) {
596
if (depth == 0) {
597
return null;
598
} else {
599
retNodes = new TreeNode[depth];
600
}
601
} else {
602
depth++;
603
if (aNode == root) {
604
retNodes = new TreeNode[depth];
605
} else {
606
TreeNode parent = aNode.getParent();
607
608
if (parent == null) {
609
parent = root;
610
}
611
retNodes = getPathToRoot(parent, depth);
612
}
613
retNodes[retNodes.length - depth] = aNode;
614
}
615
return retNodes;
616
}
617
}
618
}
619
620