1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * ident "%Z%%M% %I% %E% SMI"
24 *
25 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
26 * Use is subject to license terms.
27 */
28 package com.sun.dhcpmgr.client;
29
30 import javax.swing.*;
31 import javax.swing.event.*;
32 import javax.swing.table.*;
33 import javax.swing.tree.*;
34 import java.awt.*;
35 import java.awt.event.*;
36 import java.util.*;
37 import java.text.*;
38
39 import com.sun.dhcpmgr.ui.*;
40 import com.sun.dhcpmgr.server.*;
41 import com.sun.dhcpmgr.data.*;
42 import com.sun.dhcpmgr.bridge.BridgeException;
43
44 // Background thread for doing retrieval while keeping GUI live
45 class MacroLoader extends com.sun.dhcpmgr.ui.SwingWorker {
46 public Object construct() {
47 try {
48 return DataManager.get().getMacros(true);
49 } catch (final BridgeException e) {
50 // Since we're in a background thread, ask Swing to run ASAP.
51 SwingUtilities.invokeLater(new Runnable() {
52 Object [] args = new Object[] { e.getMessage() };
53 public void run() {
54 MessageFormat form = new MessageFormat(
55 ResourceStrings.getString("error_loading_macros"));
56 JOptionPane.showMessageDialog(null, form.format(args),
57 ResourceStrings.getString("server_error_title"),
58 JOptionPane.ERROR_MESSAGE);
59 }
60 });
61 }
62 return null;
63 }
64
65 public void finished() {
66 Macro [] macros = (Macro [])get();
67 if (macros == null) {
68 macros = new Macro[0];
69 }
70
71 MacroView.macroTreeModel.getRootNode().setMacros(macros);
72 }
73 }
74
75 /**
76 * Implements a display of the macro hierarchy contained in the dhcptab
77 */
78 public class MacroView implements View {
79
80 // A node in the tree of macros
81 class MacroTreeNode extends DefaultMutableTreeNode {
82 private boolean scannedIncludes = false;
83 private boolean isScanning = false;
84
85 public MacroTreeNode(Object o) {
86 super(o);
87 }
88
89 public String toString() {
90 Macro m = (Macro)getUserObject();
91 return m.getKey();
92 }
93
94 public int getChildCount() {
95 /*
96 * scannedIncludes is used so that we only build the tree once;
97 * isScanning prevents us from recursing infinitely because add()
98 * ends up coming right back here
99 */
100 if (!scannedIncludes && !isScanning) {
101 isScanning = true;
102 Macro m = (Macro)getUserObject();
103 Enumeration e = m.elements();
104 while (e.hasMoreElements()) {
105 OptionValue o = (OptionValue)e.nextElement();
106 if (o instanceof IncludeOptionValue) {
107 Macro m2 =
108 macroTreeModel.getRootNode().getMacro(
109 (String)o.getValue());
110 if (m2 != null) {
111 add(new MacroTreeNode(m2));
112 }
113 }
114 }
115 scannedIncludes = true;
116 isScanning = false;
117 }
118 return super.getChildCount();
119 }
120
121 public Macro getMacro() {
122 return (Macro)getUserObject();
123 }
124 }
125
126 /*
127 * Special class for the root node; this handles retrieving the data from
128 * the server
129 */
130 class MacroTreeRootNode extends MacroTreeNode {
131 private Macro [] macros = null;
132
133 public MacroTreeRootNode() {
134 super(new Macro());
135 }
136
137 public MacroTreeRootNode(Object o) {
138 super(o);
139 }
140
141 protected void setMacros(Macro [] newmacros) {
142 macros = newmacros;
143 if (newmacros != null) {
144 for (int i = 0; i < macros.length; ++i) {
145 add(new MacroTreeNode(macros[i]));
146 }
147 }
148 macroTreeModel.reload();
149 reloadCompleted();
150 }
151
152 public String toString() {
153 return ResourceStrings.getString("macros");
154 }
155
156 public int getChildCount() {
157 if (macros == null) {
158 return 0;
159 }
160 return super.getChildCount();
161 }
162
163 public void load() {
164 // Display starting message and clear tree
165 MainFrame.setStatusText(
166 ResourceStrings.getString("loading_macros"));
167 removeAllChildren();
168 macroTreeModel.reload();
169 // Kick off background loader
170 MacroLoader loader = new MacroLoader();
171 }
172
173 public Macro getMacro() {
174 return null;
175 }
176
177 // Find a particular macro by name
178 public Macro getMacro(String name) {
179 if (macros != null) {
180 for (int i = 0; i < macros.length; ++i) {
181 if (name.equals(macros[i].getKey())) {
182 return macros[i];
183 }
184 }
185 }
186 return null;
187 }
188 }
189
190 // Model for data contained in macro tree
191 class MacroTreeModel extends DefaultTreeModel {
192 public MacroTreeModel(MacroTreeRootNode n) {
193 super(n);
194 }
195
196 public void load() {
197 getRootNode().load();
198 }
199
200 public Macro getMacro(String name) {
201 return getRootNode().getMacro(name);
202 }
203
204 public MacroTreeRootNode getRootNode() {
205 return (MacroTreeRootNode)super.getRoot();
206 }
207 }
208
209 // Model for table displaying contents of a macro
210 class MacroTableModel extends AbstractTableModel {
211 private Macro macro = null;
212
213 public MacroTableModel() {
214 super();
215 }
216
217 public void display(Macro m) {
218 macro = m;
219 fireTableDataChanged();
220 }
221
222 public int getRowCount() {
223 if (macro == null) {
224 return 0;
225 } else {
226 return macro.optionCount();
227 }
228 }
229
230 public int getColumnCount() {
231 return 2;
232 }
233
234 public Object getValueAt(int row, int column) {
235 if (macro == null) {
236 return null;
237 }
238 switch (column) {
239 case 0:
240 return macro.getOptionAt(row).getName();
241 case 1:
242 return macro.getOptionAt(row).getValue();
243 default:
244 return null;
245 }
246 }
247
248 public Class getColumnClass(int column) {
249 switch (column) {
250 case 0:
251 case 1:
252 return String.class;
253 default:
254 super.getColumnClass(column);
255 }
256 return null;
257 }
258
259 public String getColumnName(int column) {
260 switch (column) {
261 case 0:
262 return ResourceStrings.getString("option_column");
263 case 1:
264 return ResourceStrings.getString("value_column");
265 default:
266 super.getColumnName(column);
267 }
268 return null;
269 }
270 }
271
272 // Recipient of update messages sent when the macro editing dialogs exit
273 class DialogListener implements ActionListener {
274 public void actionPerformed(ActionEvent e) {
275 if (!e.getActionCommand().equals(DialogActions.CANCEL)) {
276 reload();
277 }
278 }
279 }
280
281 private JTree macroTree;
282 protected static MacroTreeModel macroTreeModel = null;
283 private AutosizingTable macroTable;
284 private JScrollPane treePane;
285 private JScrollPane macroTablePane;
286 private JSplitPane splitPane;
287 private boolean firstActivation = true;
288 private MacroTableModel macroTableModel;
289 private JCheckBoxMenuItem showGrid;
290 private JMenuItem macroHelp;
291 private Vector[] menuItems;
292 private Frame myFrame;
293 private boolean firstview = true;
294 private Vector selectionListeners = new Vector();
295
296 public MacroView() {
297 // Create tree for macro display
298 macroTreeModel = new MacroTreeModel(new MacroTreeRootNode());
299 macroTree = new JTree(macroTreeModel);
300 // Listen for selection events, load selected macro into table
301 macroTree.addTreeSelectionListener(new TreeSelectionListener() {
302 public void valueChanged(TreeSelectionEvent e) {
303 TreePath selPath = macroTree.getSelectionPath();
304 if (selPath != null) {
305 TreeNode node = (TreeNode)selPath.getLastPathComponent();
306 if (node instanceof MacroTreeNode) {
307 macroTableModel.display(
308 ((MacroTreeNode)node).getMacro());
309 } else {
310 macroTableModel.display(null);
311 }
312 } else {
313 macroTableModel.display(null);
314 }
315 // Notify listeners that our selection state may have changed
316 notifySelectionListeners();
317 }
318 });
319 // Single selection for now
320 macroTree.getSelectionModel().setSelectionMode(
321 TreeSelectionModel.SINGLE_TREE_SELECTION);
322 // Make a scroll pane for it
323 treePane = new JScrollPane();
324 treePane.getViewport().add(macroTree);
325 // Make double-clicks the same as Edit->Properties
326 macroTree.addMouseListener(new MouseAdapter() {
327 public void mouseClicked(MouseEvent e) {
328 if (e.getClickCount() == 2) {
329 // Don't do anything if it's the root node
330 TreePath selPath =
331 macroTree.getClosestPathForLocation(e.getX(), e.getY());
332 macroTree.addSelectionPath(selPath);
333 if (selPath.getPathCount() != 1) {
334 handleProperties();
335 }
336 }
337 }
338 });
339
340 // Create table to display in data area
341 macroTableModel = new MacroTableModel();
342 macroTable = new AutosizingTable(macroTableModel);
343
344 // Can't mess with the column order, or select any data
345 macroTable.getTableHeader().setReorderingAllowed(false);
346 macroTable.setRowSelectionAllowed(false);
347 macroTable.setColumnSelectionAllowed(false);
348
349 // Now wrap it with scrollbars
350 macroTablePane = new JScrollPane(macroTable);
351
352 // Create split pane containing the tree & table, side-by-side
353 splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treePane,
354 macroTablePane);
355
356 // Create menu items
357 Mnemonic mnShowGrid =
358 new Mnemonic(ResourceStrings.getString("show_grid"));
359 showGrid = new JCheckBoxMenuItem(mnShowGrid.getString(),
360 true);
361 showGrid.setMnemonic(mnShowGrid.getMnemonic());
362 showGrid.addActionListener(new ActionListener() {
363 public void actionPerformed(ActionEvent e) {
364 macroTable.setShowGrid(showGrid.getState());
365 }
366 });
367
368 Mnemonic mnOnMacros =
369 new Mnemonic(ResourceStrings.getString("on_macros_item"));
370 macroHelp = new JMenuItem(mnOnMacros.getString());
371 macroHelp.setMnemonic(mnOnMacros.getMnemonic());
372 macroHelp.addActionListener(new ActionListener() {
373 public void actionPerformed(ActionEvent e) {
374 DhcpmgrApplet.showHelp("macros_reference");
375 }
376 });
377
378
379 /*
380 * Construct lists of menu items custom to this view.
381 */
382 menuItems = new Vector[MainFrame.MENU_COUNT];
383 for (int i = 0; i < menuItems.length; ++i) {
384 menuItems[i] = new Vector();
385 }
386 menuItems[MainFrame.VIEW_MENU].addElement(showGrid);
387 menuItems[MainFrame.HELP_MENU].addElement(macroHelp);
388 }
389
390 public String getName() {
391 return ResourceStrings.getString("macro_view_name");
392 }
393
394 // Return menus that we wish to add to MainFrame, in our case none
395 public Enumeration menus() {
396 return null;
397 }
398
399 // Return menu items for each menu as requested by MainFrame
400 public Enumeration menuItems(int menu) {
401 return menuItems[menu].elements();
402 }
403
404 public Component getDisplay() {
405 return splitPane;
406 }
407
408 private void reload() {
409 macroTreeModel.load();
410 }
411
412 // Callback from model when loading completed to update display
413 private void reloadCompleted() {
414 // Doesn't display correctly without this
415 splitPane.resetToPreferredSizes();
416 String s = MessageFormat.format(
417 ResourceStrings.getString("macro_status_message"),
418 macroTreeModel.getRootNode().getChildCount());
419 MainFrame.setStatusText(s);
420 macroTree.clearSelection();
421 macroTable.clearSelection();
422 /*
423 * Check for syntax errors.
424 */
425 MacroTreeRootNode rootNode = macroTreeModel.getRootNode();
426 Vector errs = new Vector();
427 for (int i = 0; i < rootNode.macros.length; ++i) {
428 try {
429 rootNode.macros[i].validate();
430 } catch (ValidationException e) {
431 errs.addElement(rootNode.macros[i].getKey());
432 }
433 }
434 if (errs.size() != 0) {
435 // Found some errors; warn user
436 Object [] objs = new Object[2];
437 objs[0] = ResourceStrings.getString("macro_validation_warning");
438 JList macroList = new JList(errs);
439 JScrollPane scrollPane = new JScrollPane(macroList);
440 macroList.setVisibleRowCount(4);
441 objs[1] = scrollPane;
442 JOptionPane.showMessageDialog(macroTable, objs,
443 ResourceStrings.getString("warning"),
444 JOptionPane.WARNING_MESSAGE);
445 }
446 macroTree.setSelectionRow(1);
447 }
448
449 public void setActive(boolean state) {
450 if (state) {
451 // We only do an automatic load the first time we're displayed
452 if (firstview) {
453 myFrame = (Frame)SwingUtilities.getAncestorOfClass(
454 MainFrame.class, macroTree);
455 reload();
456 firstview = false;
457 }
458 }
459 }
460
461 /*
462 * Handle a find operation.
463 * Algorithm used here searches nodes in the order they appear in the
464 * displayed tree. This requires traversing the entire tree starting at
465 * an arbitrary point with some special twists.
466 */
467 public void find(String s) {
468 // Clear status if we had an old message lying there
469 MainFrame.setStatusText("");
470 MacroTreeNode startNode =
471 (MacroTreeNode)macroTree.getLastSelectedPathComponent();
472 if (startNode == null) {
473 // Nothing selected so start at root
474 startNode = macroTreeModel.getRootNode();
475 }
476 // Start by searching children of selected node
477 MacroTreeNode result = searchUnderNode(startNode, s);
478 if (result != null) {
479 selectNode(result); // Found one, select it and return
480 return;
481 }
482 // Get all ancestor nodes of selected
483 TreeNode [] path = macroTreeModel.getPathToRoot(startNode);
484 MacroTreeNode pathNode = null;
485
486 // Walk up towards root of tree, at each level search all children
487 for (int i = path.length - 1; i >= 0; --i) {
488 result = searchNodeLevel((MacroTreeNode)path[i], startNode, s);
489 if (result != null) {
490 selectNode(result); // Found one, select it and return
491 return;
492 }
493 // Move up a level
494 startNode = (MacroTreeNode)path[i];
495 /*
496 * If it's not the root node and it matches, remember it. We don't
497 * return immediately because this is actually about last in the
498 * display order.
499 */
500 if (startNode.getMacro() != null) {
501 if (startNode.getMacro().getKey().indexOf(s) != -1) {
502 pathNode = startNode;
503 }
504 }
505 }
506 // We found one on the path to the root, select and return
507 if (pathNode != null) {
508 selectNode(pathNode);
509 return;
510 }
511 // Nothing found; show an error
512 MessageFormat form = null;
513 Object [] args = new Object[1];
514 form = new MessageFormat(ResourceStrings.getString("find_macro_error"));
515 args[0] = s;
516 MainFrame.setStatusText(form.format(args));
517 }
518
519 // Search a particular level of a tree in the order a user would expect
520 private MacroTreeNode searchNodeLevel(MacroTreeNode n,
521 MacroTreeNode startNode, String s) {
522 // Skip all children prior to the startNode in display order
523 Enumeration e = n.children();
524 while (e.hasMoreElements()) {
525 MacroTreeNode cn = (MacroTreeNode)e.nextElement();
526 if (cn == startNode) {
527 break;
528 }
529 }
530 // Now search those after the startNode in the display order
531 while (e.hasMoreElements()) {
532 MacroTreeNode cn = (MacroTreeNode)e.nextElement();
533 MacroTreeNode result = searchNode(cn, s);
534 if (result != null) {
535 return result;
536 }
537 }
538 // Got to the end of this level and didn't find it, so wrap to beginning
539 e = n.children();
540 while (e.hasMoreElements()) {
541 MacroTreeNode cn = (MacroTreeNode)e.nextElement();
542 if (cn == startNode) {
543 break;
544 }
545 MacroTreeNode result = searchNode(cn, s);
546 if (result != null) {
547 return result;
548 }
549 }
550 return null;
551 }
552
553 // Search a node and all its children for a particular string
554 private MacroTreeNode searchNode(MacroTreeNode n, String s) {
555 if (n.getMacro().getKey().indexOf(s) != -1
556 || n.getMacro().getValue().indexOf(s) != -1) {
557 return n;
558 }
559 return searchUnderNode(n, s);
560 }
561
562 // Search all children, recursively, of a node for a particular string
563 private MacroTreeNode searchUnderNode(MacroTreeNode n, String s) {
564 Enumeration e = n.children();
565 while (e.hasMoreElements()) {
566 MacroTreeNode cn = (MacroTreeNode)e.nextElement();
567 if (cn.getMacro().getKey().indexOf(s) != -1
568 || cn.getMacro().getValue().indexOf(s) != -1) {
569 return cn;
570 }
571 MacroTreeNode result = searchUnderNode(cn, s);
572 if (result != null) {
573 return result;
574 }
575 }
576 return null;
577 }
578
579 // Select a node and make sure it's visible
580 private void selectNode(MacroTreeNode n) {
581 macroTree.clearSelection();
582 TreePath selPath = new TreePath(macroTreeModel.getPathToRoot(n));
583 macroTree.addSelectionPath(selPath);
584 macroTree.scrollPathToVisible(selPath);
585 }
586
587 // Return the macro currently selected
588 private Macro getSelectedMacro() {
589 TreePath selPath = macroTree.getSelectionPath();
590 if (selPath != null) {
591 TreeNode node = (TreeNode)selPath.getLastPathComponent();
592 if (node instanceof MacroTreeNode) {
593 return ((MacroTreeNode)node).getMacro();
594 }
595 }
596 return null;
597 }
598
599 public void handleCreate() {
600 CreateMacroDialog d = new CreateMacroDialog(myFrame,
601 CreateMacroDialog.CREATE);
602 d.addActionListener(new DialogListener());
603 d.pack();
604 d.setVisible(true);
605 }
606
607 public void handleDelete() {
608 Macro m = getSelectedMacro();
609 if (m == null) {
610 return;
611 }
612 DeleteMacroDialog d = new DeleteMacroDialog(myFrame, m);
613 d.addActionListener(new DialogListener());
614 d.pack();
615 d.setVisible(true);
616 }
617
618 public void handleDuplicate() {
619 Macro m = getSelectedMacro();
620 if (m == null) {
621 return;
622 }
623 CreateMacroDialog d = new CreateMacroDialog(myFrame,
624 CreateMacroDialog.DUPLICATE);
625 d.addActionListener(new DialogListener());
626 d.setMacro((Macro)m.clone());
627 d.pack();
628 d.setVisible(true);
629 }
630
631 public void handleProperties() {
632 Macro m = getSelectedMacro();
633 if (m == null) {
634 return;
635 }
636 CreateMacroDialog d = new CreateMacroDialog(myFrame,
637 CreateMacroDialog.EDIT);
638 d.addActionListener(new DialogListener());
639 d.setMacro((Macro)m.clone());
640 d.pack();
641 d.setVisible(true);
642 }
643
644 public void handleUpdate() {
645 reload();
646 }
647
648 public void addSelectionListener(SelectionListener listener) {
649 selectionListeners.addElement(listener);
650 }
651
652 public void removeSelectionListener(SelectionListener listener) {
653 selectionListeners.removeElement(listener);
654 }
655
656 private void notifySelectionListeners() {
657 Enumeration en = selectionListeners.elements();
658 while (en.hasMoreElements()) {
659 SelectionListener l = (SelectionListener)en.nextElement();
660 l.valueChanged();
661 }
662 }
663
664 public boolean isSelectionEmpty() {
665 TreePath path = macroTree.getSelectionPath();
666 // If empty or the root of the tree is selected, then we call it empty
667 return ((path == null) || (path.getPathCount() == 1));
668 }
669
670 public boolean isSelectionMultiple() {
671 return false; // We don't allow multiple selection
672 }
673 }