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 }