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 2002 by Sun Microsystems, Inc.  All rights reserved.
  26  * Use is subject to license terms.
  27  */
  28 package com.sun.dhcpmgr.ui;
  29 
  30 import java.awt.*;
  31 import java.awt.event.ActionListener;
  32 import java.awt.event.ActionEvent;
  33 import javax.swing.*;
  34 import javax.swing.event.*;
  35 import java.util.ArrayList;
  36 import java.util.Arrays;
  37 
  38 /*
  39  * This class is a hack to get around the fact that clearSelection()
  40  * in DefaultListSelectionModel does not always fire an event to its listeners.
  41  * We rely on such an event to enable & disable the arrow buttons which
  42  * move data items between lists.  See bug 4177723.
  43  */
  44 class FixedSelectionModel extends DefaultListSelectionModel {
  45     public void clearSelection() {
  46         super.clearSelection();
  47         fireValueChanged(getMinSelectionIndex(), getMaxSelectionIndex(), false);
  48     }
  49 }
  50 
  51 /*
  52  * We implement our own list model rather than use the default so
  53  * that we can take advantage of some of the more advanced collection
  54  * features not supported by DefaultListModel
  55  */
  56 class OurListModel implements ListModel {
  57     ArrayList data = new ArrayList();
  58     EventListenerList listenerList = new EventListenerList();
  59 
  60     public OurListModel(Object [] data) {
  61         if (data != null) {
  62             this.data.addAll(Arrays.asList(data));
  63         }
  64     }
  65 
  66     public void addListDataListener(ListDataListener l) {
  67         listenerList.add(ListDataListener.class, l);
  68     }
  69 
  70     public void removeListDataListener(ListDataListener l) {
  71         listenerList.remove(ListDataListener.class, l);
  72     }
  73 
  74     protected void fireContentsChanged() {
  75         ListDataEvent e =
  76             new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED,
  77             0, getSize());
  78         Object [] listeners = listenerList.getListenerList();
  79         /*
  80          * Listener array is formatted as pairs of (class, listener); walk
  81          * the array backwards and call each ListDataListener in turn with
  82          * the event.  See javax.swing.event.EventListenerList for more info.
  83          */
  84         for (int i = listeners.length - 2; i >= 0; i -= 2) {
  85             if (listeners[i] == ListDataListener.class) {
  86                 ((ListDataListener)listeners[i+1]).contentsChanged(e);
  87             }
  88         }
  89     }
  90 
  91     public Object getElementAt(int index) {
  92         return data.get(index);
  93     }
  94 
  95     public int getSize() {
  96         return data.size();
  97     }
  98 
  99     public void addElement(Object o) {
 100         data.add(o);
 101         fireContentsChanged();
 102     }
 103 
 104     public void removeElement(Object o) {
 105         data.remove(data.indexOf(o));
 106         fireContentsChanged();
 107     }
 108 
 109     public Object [] toArray(Object [] arr) {
 110         return data.toArray(arr);
 111     }
 112 }
 113 
 114 /*
 115  * Our own layout manager which keeps the left & right lists the same size.
 116  */
 117 class ListPairLayout implements LayoutManager {
 118     Component leftComponent, centerComponent, rightComponent;
 119     public static final String LEFT = "left";
 120     public static final String CENTER = "center";
 121     public static final String RIGHT = "right";
 122 
 123     public ListPairLayout() {
 124         leftComponent = centerComponent = rightComponent = null;
 125     }
 126 
 127     public void addLayoutComponent(String name, Component comp) {
 128         if (name.equals(LEFT)) {
 129             leftComponent = comp;
 130         } else if (name.equals(CENTER)) {
 131             centerComponent = comp;
 132         } else if (name.equals(RIGHT)) {
 133             rightComponent = comp;
 134         }
 135     }
 136 
 137     public void layoutContainer(Container target) {
 138         // Make left & right components same size, center no smaller than min
 139         Insets insets = target.getInsets();
 140         Dimension dim = target.getSize();
 141         int x = insets.left;
 142         int y = insets.top;
 143         int totalHeight = dim.height - insets.bottom;
 144         int totalWidth = dim.width - insets.right;
 145 
 146         // If preferred sizes don't fit, go to minimum.
 147         Dimension cDim = centerComponent.getPreferredSize();
 148         Dimension d = preferredLayoutSize(target);
 149         if (d.width > totalWidth || d.height > totalHeight) {
 150             cDim = centerComponent.getMinimumSize();
 151         }
 152 
 153         // Left & right each get half of what's left after center allocated
 154         int lrWidth = (totalWidth - cDim.width) / 2;
 155 
 156         // Now place each component
 157         leftComponent.setBounds(x, y, lrWidth, totalHeight);
 158         centerComponent.setBounds(x + lrWidth, y, cDim.width, totalHeight);
 159         rightComponent.setBounds(x + lrWidth + cDim.width, y, lrWidth,
 160             totalHeight);
 161     }
 162 
 163     public Dimension minimumLayoutSize(Container parent) {
 164         Dimension retDim = new Dimension();
 165         // Compute minimum width as max(leftwidth, rightwidth) * 2 + centerwidth
 166         int lrwidth = Math.max(leftComponent.getMinimumSize().width,
 167             rightComponent.getMinimumSize().width);
 168         retDim.width = lrwidth * 2 + centerComponent.getMinimumSize().width;
 169         // Compute minimum height as max(leftheight, rightheight, centerheight)
 170         int lrheight = Math.max(leftComponent.getMinimumSize().height,
 171             rightComponent.getMinimumSize().height);
 172         retDim.height = Math.max(centerComponent.getMinimumSize().height,
 173             lrheight);
 174         return retDim;
 175     }
 176 
 177     public Dimension preferredLayoutSize(Container parent) {
 178         Dimension retDim = new Dimension();
 179         // Preferred width is max(leftwidth, rightwidth) * 2 + centerwidth
 180         int lrwidth = Math.max(leftComponent.getPreferredSize().width,
 181             rightComponent.getPreferredSize().width);
 182         retDim.width = lrwidth * 2 + centerComponent.getPreferredSize().width;
 183         // Preferred height is max(leftheight, rightheight, centerheight)
 184         int lrheight = Math.max(leftComponent.getPreferredSize().height,
 185             rightComponent.getPreferredSize().height);
 186         retDim.height = Math.max(centerComponent.getPreferredSize().height,
 187             lrheight);
 188         return retDim;
 189     }
 190 
 191     public void removeLayoutComponent(Component comp) {
 192         // Do nothing
 193     }
 194 }
 195 
 196 /**
 197  * A ListPair provides a way to display two lists of objects and to move
 198  * objects from one list to another.  It is initialized with the contents
 199  * of each list, and can be queried at any time for the contents of each list
 200  */
 201 public class ListPair extends JPanel {
 202     private JList leftList, rightList;
 203     private OurListModel leftModel, rightModel;
 204     private ListSelectionModel leftSelectionModel, rightSelectionModel;
 205     private LeftButton leftButton = new LeftButton();
 206     private RightButton rightButton = new RightButton();
 207     private JScrollPane leftPane, rightPane;
 208 
 209     /**
 210      * Construct a ListPair with the specified data and captions for each list
 211      * @param leftCaption Caption for left list
 212      * @param leftData An array of objects to display in the left list
 213      * @param rightCaption Caption for right list
 214      * @param rightData An array of objects to display in the right list
 215      */
 216     public ListPair(String leftCaption, Object [] leftData, String rightCaption,
 217             Object [] rightData) {
 218 
 219         // Use our custom layout manager
 220         setLayout(new ListPairLayout());
 221 
 222         // Store data into the list models
 223         leftModel = new OurListModel(leftData);
 224         rightModel = new OurListModel(rightData);
 225 
 226         // Now create the lists
 227         leftList = new JList(leftModel);
 228         rightList = new JList(rightModel);
 229         leftList.setSelectionModel(new FixedSelectionModel());
 230         rightList.setSelectionModel(new FixedSelectionModel());
 231 
 232         // Now do the layout
 233         JPanel leftPanel = new JPanel(new BorderLayout());
 234 
 235         JLabel leftCapLbl = new JLabel(leftCaption);
 236         leftCapLbl.setLabelFor(leftPanel);
 237         leftCapLbl.setToolTipText(leftCaption);
 238         leftPanel.add(leftCapLbl, BorderLayout.NORTH);
 239 
 240         leftPane = new JScrollPane(leftList);
 241         leftPanel.add(leftPane, BorderLayout.CENTER);
 242         add(leftPanel, ListPairLayout.LEFT);
 243 
 244         JPanel centerPanel = new JPanel(new VerticalButtonLayout());
 245         rightButton.setEnabled(false);
 246         leftButton.setEnabled(false);
 247         centerPanel.add(rightButton);
 248         centerPanel.add(leftButton);
 249         add(centerPanel, ListPairLayout.CENTER);
 250 
 251         JPanel rightPanel = new JPanel(new BorderLayout());
 252 
 253         JLabel rightCapLbl = new JLabel(rightCaption);
 254         rightCapLbl.setLabelFor(rightPanel);
 255         rightCapLbl.setToolTipText(rightCaption);
 256         rightPanel.add(rightCapLbl, BorderLayout.NORTH);
 257 
 258         rightPane = new JScrollPane(rightList);
 259         rightPanel.add(rightPane, BorderLayout.CENTER);
 260         add(rightPanel, ListPairLayout.RIGHT);
 261 
 262         // Now create and hook up the listeners for selection state
 263         leftSelectionModel = leftList.getSelectionModel();
 264         leftSelectionModel.addListSelectionListener(
 265                 new ListSelectionListener() {
 266             public void valueChanged(ListSelectionEvent e) {
 267                 // Ignore if user is dragging selection state
 268                 if (e.getValueIsAdjusting()) {
 269                     return;
 270                 }
 271                 // Right enabled only if something is selected in left list
 272                 rightButton.setEnabled(!leftSelectionModel.isSelectionEmpty());
 273                 if (!leftSelectionModel.isSelectionEmpty()) {
 274                     rightSelectionModel.clearSelection();
 275                 }
 276             }
 277         });
 278 
 279         rightSelectionModel = rightList.getSelectionModel();
 280         rightSelectionModel.addListSelectionListener(
 281                 new ListSelectionListener() {
 282             public void valueChanged(ListSelectionEvent e) {
 283                 // Ignore if user is dragging selection state
 284                 if (e.getValueIsAdjusting()) {
 285                     return;
 286                 }
 287                 // Left enabled only if something is selected in the right list
 288                 leftButton.setEnabled(!rightSelectionModel.isSelectionEmpty());
 289                 if (!rightSelectionModel.isSelectionEmpty()) {
 290                     leftSelectionModel.clearSelection();
 291                 }
 292             }
 293         });
 294 
 295         // Now add listeners to buttons to move data between lists
 296         rightButton.addActionListener(new ActionListener() {
 297             public void actionPerformed(ActionEvent e) {
 298                 Object [] values = leftList.getSelectedValues();
 299                 for (int i = 0; i < values.length; ++i) {
 300                     rightModel.addElement(values[i]);
 301                     leftModel.removeElement(values[i]);
 302                 }
 303                 /*
 304                  * Clear the selection state; this shouldn't be necessary,
 305                  * but the selection and data models are unfortunately not
 306                  * hooked up to handle this automatically
 307                  */
 308                 leftSelectionModel.clearSelection();
 309             }
 310         });
 311 
 312         leftButton.addActionListener(new ActionListener() {
 313             public void actionPerformed(ActionEvent e) {
 314                 Object [] values = rightList.getSelectedValues();
 315                 for (int i = 0; i < values.length; ++i) {
 316                     leftModel.addElement(values[i]);
 317                     rightModel.removeElement(values[i]);
 318                 }
 319                 /*
 320                  * Clear the selection state; this shouldn't be necessary,
 321                  * but the selection and data models are unfortunately not
 322                  * hooked up to handle this automatically
 323                  */
 324                 rightSelectionModel.clearSelection();
 325             }
 326         });
 327     }
 328 
 329     /**
 330      * Retrieve the contents of the left list
 331      * @return the contents as an array of Object
 332      */
 333     public Object [] getLeftContents(Object [] arr) {
 334         return leftModel.toArray(arr);
 335     }
 336 
 337     /**
 338      * Retrieve the contents of the right list
 339      * @return the contents as an array of Object
 340      */
 341     public Object [] getRightContents(Object [] arr) {
 342         return rightModel.toArray(arr);
 343     }
 344 }