1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package net.grinder.console.swingui;
23
24 import java.awt.Color;
25 import java.awt.Component;
26 import java.awt.Dimension;
27 import java.awt.Font;
28 import java.awt.Graphics;
29 import java.awt.SystemColor;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.MouseAdapter;
32 import java.awt.event.MouseEvent;
33 import java.io.File;
34 import javax.swing.ActionMap;
35 import javax.swing.BorderFactory;
36 import javax.swing.Icon;
37 import javax.swing.ImageIcon;
38 import javax.swing.InputMap;
39 import javax.swing.JComponent;
40 import javax.swing.JLabel;
41 import javax.swing.JOptionPane;
42 import javax.swing.JPopupMenu;
43 import javax.swing.JScrollBar;
44 import javax.swing.JScrollPane;
45 import javax.swing.JTree;
46 import javax.swing.KeyStroke;
47 import javax.swing.SwingUtilities;
48 import javax.swing.event.TreeSelectionEvent;
49 import javax.swing.event.TreeSelectionListener;
50 import javax.swing.tree.DefaultTreeCellRenderer;
51 import javax.swing.tree.TreePath;
52 import javax.swing.tree.TreeSelectionModel;
53
54 import net.grinder.console.common.ConsoleException;
55 import net.grinder.console.common.ErrorHandler;
56 import net.grinder.console.common.Resources;
57 import net.grinder.console.editor.Buffer;
58 import net.grinder.console.editor.EditorModel;
59 import net.grinder.console.model.ConsoleProperties;
60 import net.grinder.console.swingui.FileTreeModel.FileNode;
61
62
63
64
65
66
67
68
69
70
71
72
73 final class FileTree {
74
75 private final Resources m_resources;
76 private final ErrorHandler m_errorHandler;
77 private final EditorModel m_editorModel;
78 private final BufferTreeModel m_bufferTreeModel;
79 private final FileTreeModel m_fileTreeModel;
80 private final ConsoleProperties m_properties;
81
82 private final JTree m_tree;
83 private final OpenAction m_openAction;
84 private final OpenExternalAction m_openExternalAction;
85 private final SelectPropertiesAction m_selectPropertiesAction;
86 private final DeselectPropertiesAction m_deselectPropertiesAction;
87 private final JScrollPane m_scrollPane;
88
89 public FileTree(Resources resources,
90 ErrorHandler errorHandler,
91 EditorModel editorModel,
92 BufferTreeModel bufferTreeModel,
93 FileTreeModel fileTreeModel,
94 Font font,
95 JPopupMenu popupMenu,
96 ConsoleProperties properties) {
97
98 m_resources = resources;
99 m_errorHandler = errorHandler;
100 m_editorModel = editorModel;
101 m_bufferTreeModel = bufferTreeModel;
102 m_fileTreeModel = fileTreeModel;
103 m_properties = properties;
104
105 final CompositeTreeModel compositeTreeModel = new CompositeTreeModel();
106
107 compositeTreeModel.addTreeModel(m_bufferTreeModel, false);
108 compositeTreeModel.addTreeModel(m_fileTreeModel, true);
109
110 m_tree = new JTree(compositeTreeModel) {
111
112
113
114 public void updateUI() {
115 super.updateUI();
116
117
118
119
120
121
122 if (!isRootVisible()) {
123
124 setBackground(new JLabel().getBackground());
125
126 setCellRenderer(
127 new CustomTreeCellRenderer(getFont(), getBackground()));
128 }
129 }
130 };
131
132 m_tree.setBackground(new JLabel().getBackground());
133 m_tree.setFont(font);
134
135 m_tree.setRootVisible(false);
136 m_tree.setShowsRootHandles(true);
137
138 m_tree.setCellRenderer(
139 new CustomTreeCellRenderer(m_tree.getFont(), m_tree.getBackground()));
140 m_tree.getSelectionModel().setSelectionMode(
141 TreeSelectionModel.SINGLE_TREE_SELECTION);
142
143 m_tree.addMouseListener(new MouseListener(popupMenu));
144
145 m_tree.addTreeSelectionListener(new TreeSelectionListener() {
146 public void valueChanged(TreeSelectionEvent e) {
147 updateActionState();
148 }
149 });
150
151 m_openAction = new OpenAction();
152 m_openExternalAction = new OpenExternalAction();
153 m_selectPropertiesAction = new SelectPropertiesAction();
154 m_deselectPropertiesAction = new DeselectPropertiesAction();
155
156
157
158
159
160 final InputMap inputMap = m_tree.getInputMap();
161
162 inputMap.put(KeyStroke.getKeyStroke("ENTER"), "activateNode");
163 inputMap.put(KeyStroke.getKeyStroke("SPACE"), "activateNode");
164
165 final ActionMap actionMap = m_tree.getActionMap();
166 actionMap.put("activateNode",
167 new TeeAction(actionMap.get("toggle"), m_openAction));
168
169 m_scrollPane = new JScrollPane(m_tree);
170 m_scrollPane.setBorder(BorderFactory.createEtchedBorder());
171
172 m_editorModel.addListener(new EditorModelListener());
173
174 updateActionState();
175 }
176
177 private final class MouseListener extends MouseAdapter {
178 private final JPopupMenu m_popupMenu;
179
180 private boolean m_handledOnPress;
181
182 private MouseListener(JPopupMenu popupMenu) {
183 m_popupMenu = popupMenu;
184 }
185
186 public void mousePressed(MouseEvent e) {
187 m_handledOnPress = false;
188
189 if (!e.isConsumed() && SwingUtilities.isLeftMouseButton(e)) {
190 final TreePath path = m_tree.getPathForLocation(e.getX(), e.getY());
191
192 if (path == null) {
193 return;
194 }
195
196 final Object selectedNode = path.getLastPathComponent();
197
198 if (selectedNode instanceof Node) {
199 final Node node = (Node)selectedNode;
200 final int clickCount = e.getClickCount();
201
202 final boolean hasBuffer = node.getBuffer() != null;
203
204 if (clickCount == 2 || clickCount == 1 && hasBuffer) {
205 m_openAction.invoke(node);
206 m_handledOnPress = true;
207 e.consume();
208 }
209
210 if (clickCount == 2 &&
211 hasBuffer &&
212 m_selectPropertiesAction.isEnabled()) {
213 m_selectPropertiesAction.invoke();
214 m_handledOnPress = true;
215 e.consume();
216 }
217 }
218 }
219
220 if (e.isPopupTrigger()) {
221 m_popupMenu.show(e.getComponent(), e.getX(), e.getY());
222 }
223 }
224
225 public void mouseReleased(MouseEvent e) {
226 if (m_handledOnPress) {
227
228 e.consume();
229 }
230
231 if (e.isPopupTrigger()) {
232 m_popupMenu.show(e.getComponent(), e.getX(), e.getY());
233 }
234 }
235 }
236
237 private class EditorModelListener extends EditorModel.AbstractListener {
238
239 public void bufferAdded(Buffer buffer) {
240
241
242 final int rowHeight = m_tree.getRowBounds(0).height;
243 final JScrollBar verticalScrollBar = m_scrollPane.getVerticalScrollBar();
244 verticalScrollBar.setValue(verticalScrollBar.getValue() + rowHeight);
245 }
246
247 public void bufferStateChanged(Buffer buffer) {
248 final File file = buffer.getFile();
249
250 if (file != null) {
251 final FileTreeModel.FileNode oldFileNode =
252 m_fileTreeModel.findFileNode(buffer);
253
254
255
256 final FileTreeModel.Node node = m_fileTreeModel.findNode(file);
257
258 if (oldFileNode == null || !oldFileNode.equals(node)) {
259
260
261 if (oldFileNode != null) {
262 oldFileNode.setBuffer(null);
263 }
264
265 if (node instanceof FileTreeModel.FileNode) {
266 final FileTreeModel.FileNode fileNode =
267 (FileTreeModel.FileNode)node;
268
269 fileNode.setBuffer(buffer);
270 m_tree.scrollPathToVisible(treePathForFileNode(fileNode));
271 }
272 }
273 }
274
275 final FileTreeModel.Node fileNode = m_fileTreeModel.findFileNode(buffer);
276
277 if (fileNode != null) {
278 m_fileTreeModel.valueForPathChanged(fileNode.getPath(), fileNode);
279 }
280
281 m_bufferTreeModel.bufferChanged(buffer);
282
283 updateActionState();
284 }
285
286 public void bufferRemoved(Buffer buffer) {
287 final FileTreeModel.FileNode fileNode =
288 m_fileTreeModel.findFileNode(buffer);
289
290 if (fileNode != null) {
291 fileNode.setBuffer(null);
292 m_fileTreeModel.valueForPathChanged(fileNode.getPath(), fileNode);
293 }
294 }
295 }
296
297 public JComponent getComponent() {
298 return m_scrollPane;
299 }
300
301 public CustomAction[] getActions() {
302 return new CustomAction[] {
303 m_openAction,
304 m_openExternalAction,
305 m_selectPropertiesAction,
306 m_deselectPropertiesAction,
307 };
308 }
309
310
311
312
313 private final class OpenAction extends CustomAction {
314 public OpenAction() {
315 super(m_resources, "open-file");
316 }
317
318 public void actionPerformed(ActionEvent event) {
319 invoke(m_tree.getLastSelectedPathComponent());
320 }
321
322 public void invoke(Object selectedNode) {
323 if (selectedNode instanceof BufferTreeModel.BufferNode) {
324 m_editorModel.selectBuffer(
325 ((BufferTreeModel.BufferNode)selectedNode).getBuffer());
326 }
327 else if (selectedNode instanceof FileTreeModel.FileNode) {
328 final FileNode fileNode = (FileTreeModel.FileNode)selectedNode;
329
330 try {
331 fileNode.setBuffer(
332 m_editorModel.selectBufferForFile(fileNode.getFile()));
333
334
335
336
337
338
339 m_tree.setSelectionPath(treePathForFileNode(fileNode));
340 }
341 catch (ConsoleException e) {
342 m_errorHandler.handleException(
343 e, m_resources.getString("fileError.title"));
344 }
345 }
346 }
347 }
348
349
350
351
352
353
354 private final class OpenExternalAction extends CustomAction {
355 public OpenExternalAction() {
356 super(m_resources, "open-file-external");
357 }
358
359 public void actionPerformed(ActionEvent event) {
360 final Object selectedNode = m_tree.getLastSelectedPathComponent();
361
362 if (selectedNode instanceof Node) {
363 final Node node = (Node)selectedNode;
364
365 final File file = node.getFile();
366
367 if (file != null) {
368 final Buffer buffer = node.getBuffer();
369
370 if (buffer != null && buffer.isDirty() &&
371 JOptionPane.showConfirmDialog(
372 getComponent(),
373 m_resources.getString(
374 "externalEditModifiedBufferConfirmation.text"),
375 file.toString(),
376 JOptionPane.YES_NO_OPTION) == JOptionPane.NO_OPTION) {
377 return;
378 }
379
380 try {
381 m_editorModel.openWithExternalEditor(file);
382 }
383 catch (ConsoleException e) {
384 m_errorHandler.handleException(
385 e, m_resources.getString("fileError.title"));
386 }
387 }
388 }
389 }
390 }
391
392 private final class SelectPropertiesAction extends CustomAction {
393 public SelectPropertiesAction() {
394 super(m_resources, "select-properties");
395 }
396
397 public void actionPerformed(ActionEvent event) {
398 invoke();
399 }
400
401 public void invoke() {
402 final Object selectedNode = m_tree.getLastSelectedPathComponent();
403
404 if (selectedNode instanceof Node) {
405 final Node node = (Node)selectedNode;
406
407 final File file = node.getFile();
408
409 if (file.isFile()) {
410 try {
411
412 m_properties.setAndSavePropertiesFile(file);
413 }
414 catch (ConsoleException e) {
415 m_errorHandler.handleException(e);
416 return;
417 }
418
419 m_bufferTreeModel.valueForPathChanged(node.getPath(), node);
420 updateActionState();
421 }
422 }
423 }
424 }
425
426 private final class DeselectPropertiesAction extends CustomAction {
427 public DeselectPropertiesAction() {
428 super(m_resources, "deselect-properties");
429 }
430
431 public void actionPerformed(ActionEvent event) {
432 invoke();
433 }
434
435 public void invoke() {
436 try {
437 final File previousProperties = m_properties.getPropertiesFile();
438
439 m_properties.setAndSavePropertiesFile(null);
440 updateActionState();
441
442 if (previousProperties != null) {
443 final FileTreeModel.Node fileNode =
444 m_fileTreeModel.findNode(previousProperties);
445
446 if (fileNode != null) {
447 m_fileTreeModel.valueForPathChanged(fileNode.getPath(), fileNode);
448
449 m_bufferTreeModel.bufferChanged(fileNode.getBuffer());
450 }
451 }
452 }
453 catch (ConsoleException e) {
454 m_errorHandler.handleException(e);
455 }
456 }
457 }
458
459 private void updateActionState() {
460 m_deselectPropertiesAction.setEnabled(
461 m_editorModel.getSelectedPropertiesFile() != null);
462
463 if (m_tree.isEnabled()) {
464 final Object selectedNode = m_tree.getLastSelectedPathComponent();
465 if (selectedNode instanceof Node) {
466 final Node node = (Node)selectedNode;
467
468 final Buffer buffer = node.getBuffer();
469 final File file = node.getFile();
470
471 m_openAction.setEnabled(
472 node.canOpen() &&
473 (buffer == null ||
474 !buffer.equals(m_editorModel.getSelectedBuffer())));
475 m_openAction.setRelevantToSelection(node.canOpen());
476
477 m_openExternalAction.setEnabled(file != null && file.isFile());
478 m_openExternalAction.setRelevantToSelection(node.canOpen());
479
480 m_selectPropertiesAction.setEnabled(
481 m_editorModel.isPropertiesFile(file) &&
482 !file.equals(m_editorModel.getSelectedPropertiesFile()));
483
484 m_selectPropertiesAction.setRelevantToSelection(
485 m_selectPropertiesAction.isEnabled());
486
487 m_deselectPropertiesAction.setRelevantToSelection(
488 m_editorModel.isPropertiesFile(file) &&
489 !m_selectPropertiesAction.isEnabled());
490
491 return;
492 }
493 }
494
495 m_openAction.setEnabled(false);
496 m_openAction.setRelevantToSelection(false);
497 m_openExternalAction.setEnabled(false);
498 m_openExternalAction.setRelevantToSelection(false);
499 m_selectPropertiesAction.setEnabled(false);
500 m_selectPropertiesAction.setRelevantToSelection(false);
501 m_deselectPropertiesAction.setRelevantToSelection(false);
502 }
503
504
505
506
507 private final class CustomTreeCellRenderer extends DefaultTreeCellRenderer {
508 private final DefaultTreeCellRenderer m_defaultRenderer =
509 new DefaultTreeCellRenderer();
510
511 private final Font m_boldFont;
512 private final Font m_boldItalicFont;
513 private final ImageIcon m_propertiesIcon =
514 m_resources.getImageIcon("file.properties.image");
515 private final ImageIcon m_markedPropertiesIcon =
516 m_resources.getImageIcon("file.selectedproperties.image");
517 private final ImageIcon m_scriptIcon =
518 m_resources.getImageIcon("file.script.image");
519 private final ImageIcon m_selectedScriptIcon =
520 m_resources.getImageIcon("file.selectedscript.image");
521
522 private boolean m_active;
523
524 CustomTreeCellRenderer(Font baseFont, Color background) {
525 m_boldFont = baseFont.deriveFont(Font.BOLD);
526 m_boldItalicFont = m_boldFont.deriveFont(Font.BOLD | Font.ITALIC);
527 m_defaultRenderer.setBackgroundNonSelectionColor(background);
528 }
529
530 public Component getTreeCellRendererComponent(
531 JTree tree, Object value, boolean selected, boolean expanded,
532 boolean leaf, int row, boolean hasFocus) {
533
534 if (value instanceof Node) {
535 final Node node = (Node)value;
536
537 final File file = node.getFile();
538
539 if (file != null && !file.isFile()) {
540 return m_defaultRenderer.getTreeCellRendererComponent(
541 tree, value, selected, expanded, leaf, row, hasFocus);
542 }
543
544 final Icon icon;
545
546 if (file != null &&
547 file.equals(m_editorModel.getSelectedPropertiesFile())) {
548 icon = m_markedPropertiesIcon;
549 }
550 else if (m_editorModel.isSelectedScript(file)) {
551 icon = m_selectedScriptIcon;
552 }
553 else if (m_editorModel.isPropertiesFile(file)) {
554 icon = m_propertiesIcon;
555 }
556 else if (m_editorModel.isScriptFile(file)) {
557 icon = m_scriptIcon;
558 }
559 else {
560 icon = m_defaultRenderer.getLeafIcon();
561 }
562
563 setLeafIcon(icon);
564
565 final Buffer buffer = node.getBuffer();
566
567
568 setTextNonSelectionColor(
569 buffer == null && m_editorModel.isBoringFile(file) ?
570 SystemColor.textInactiveText :
571 m_defaultRenderer.getTextNonSelectionColor());
572
573 if (buffer != null) {
574
575 setFont(buffer.isDirty() ? m_boldItalicFont : m_boldFont);
576 m_active = buffer.equals(m_editorModel.getSelectedBuffer());
577 }
578 else {
579 setFont(m_defaultRenderer.getFont());
580 m_active = false;
581 }
582
583 return super.getTreeCellRendererComponent(
584 tree, value, selected, expanded, leaf, row, hasFocus);
585 }
586 else {
587 return m_defaultRenderer.getTreeCellRendererComponent(
588 tree, value, selected, expanded, leaf, row, hasFocus);
589 }
590 }
591
592
593
594
595
596
597 public Dimension getPreferredSize() {
598 final Dimension result = super.getPreferredSize();
599
600 return result != null ?
601 new Dimension(result.width + 3, result.height) : null;
602 }
603
604 public void paint(Graphics g) {
605
606 final Color backgroundColour;
607
608
609
610
611 if (m_active) {
612 backgroundColour = Colours.FAINT_YELLOW;
613 setTextSelectionColor(SystemColor.textText);
614 setTextNonSelectionColor(SystemColor.textText);
615 }
616 else if (selected) {
617 backgroundColour = m_defaultRenderer.getBackgroundSelectionColor();
618 setTextSelectionColor(m_defaultRenderer.getTextSelectionColor());
619 }
620 else {
621 backgroundColour = m_defaultRenderer.getBackgroundNonSelectionColor();
622 setTextNonSelectionColor(m_defaultRenderer.getTextNonSelectionColor());
623 }
624
625 if (backgroundColour != null) {
626 g.setColor(backgroundColour);
627 g.fillRect(0, 0, getWidth() - 1, getHeight());
628 }
629
630
631
632
633
634
635 final boolean oldHasFocus = hasFocus;
636 final boolean oldSelected = selected;
637 final Color oldBackgroundNonSelectionColour =
638 getBackgroundNonSelectionColor();
639
640 try {
641 hasFocus = false;
642 selected = false;
643 setBackgroundNonSelectionColor(backgroundColour);
644
645 super.paint(g);
646 }
647 finally {
648 hasFocus = oldHasFocus;
649 selected = oldSelected;
650 setBackgroundNonSelectionColor(oldBackgroundNonSelectionColour);
651 }
652
653
654 final Color borderColour;
655
656 if (m_active) {
657 borderColour = getTextNonSelectionColor();
658 }
659 else if (hasFocus) {
660 borderColour = getBorderSelectionColor();
661 }
662 else {
663 borderColour = null;
664 }
665
666 if (borderColour != null) {
667 g.setColor(borderColour);
668 g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
669 }
670 }
671 }
672
673
674
675
676
677
678 private TreePath treePathForFileNode(FileTreeModel.FileNode fileNode) {
679 final Object[] original = fileNode.getPath().getPath();
680 final Object[] result = new Object[original.length + 1];
681 System.arraycopy(original, 0, result, 1, original.length);
682
683 result[0] = m_tree.getModel().getRoot();
684
685 return new TreePath(result);
686 }
687
688
689
690
691 interface Node {
692
693
694
695
696 Buffer getBuffer();
697
698
699
700
701 File getFile();
702
703 TreePath getPath();
704
705 boolean canOpen();
706 }
707 }