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 }