1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2009, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.tool;
  28 
  29 import java.awt.BorderLayout;
  30 import java.awt.Component;
  31 import java.awt.Dimension;
  32 import java.awt.event.ActionEvent;
  33 import java.awt.event.ActionListener;
  34 import java.awt.event.MouseEvent;
  35 import java.awt.event.MouseListener;
  36 import java.io.File;
  37 import java.lang.reflect.Array;
  38 import javax.accessibility.Accessible;
  39 import javax.accessibility.AccessibleContext;
  40 import javax.swing.BorderFactory;
  41 import javax.swing.DefaultListCellRenderer;
  42 import javax.swing.DefaultListModel;
  43 import javax.swing.JButton;
  44 import javax.swing.JComponent;
  45 import javax.swing.JList;
  46 import javax.swing.JOptionPane;
  47 import javax.swing.JScrollPane;
  48 import javax.swing.JToolBar;
  49 import javax.swing.ListSelectionModel;
  50 import javax.swing.event.ListDataEvent;
  51 import javax.swing.event.ListDataListener;
  52 import javax.swing.event.ListSelectionEvent;
  53 import javax.swing.event.ListSelectionListener;
  54 
  55 /**
  56  * A component that displays an editable list of items.
  57  */
  58 public class EditableList extends JComponent implements Accessible
  59 {
  60     /**
  61      * Create an empty component, using a standard UIFactory for this class,
  62      * and using resources beginning with "list.".
  63      */
  64     public EditableList() {
  65         this(new UIFactory(EditableList.class, null), "list");
  66     }
  67 
  68     private static UIFactory getDefaultUIF() {
  69         // use EditableList.class instead of "this" to get correct i18n
  70         if (defaultUIF == null)
  71             defaultUIF = new UIFactory(EditableList.class, null);  // no help required
  72         return defaultUIF;
  73     }
  74 
  75     /**
  76      * Create an empty component, using a specified UIFactory and resource prefix.
  77      * @param uif The UIFactory used to construct the component
  78      * @param uiKey The prefix for any UI resources that may be required
  79      */
  80     public EditableList(UIFactory uif, String uiKey) {
  81         this.uif = uif;
  82         setLayout(new BorderLayout());
  83         listModel = new DefaultListModel<>(); // need to force the type of model
  84         listModel.addListDataListener(listener);
  85         list = uif.createList(uiKey, listModel);
  86         list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  87         list.setCellRenderer(renderer);
  88 
  89         list.addListSelectionListener(listener);
  90         add(new JScrollPane(list), BorderLayout.CENTER);
  91 
  92         JToolBar bar = new JToolBar(JToolBar.VERTICAL);
  93         bar.setFloatable(false);
  94         bar.add(addBtn  = createButton(uiKey + ".add"));
  95         bar.add(removeBtn = createButton(uiKey + ".remove"));
  96         bar.add(upBtn = createButton(uiKey + ".up"));
  97         bar.add(downBtn = createButton(uiKey + ".down"));
  98         add(bar, BorderLayout.EAST);
  99 
 100         updateButtons();
 101         setBorder(BorderFactory.createEtchedBorder());
 102     }
 103 
 104     /**
 105      * Get the accessible context for this pane.
 106      * @return the accessible context for this pane
 107      */
 108     public AccessibleContext getAccessibleContext() {
 109         if (accessibleContext == null)
 110             accessibleContext = new AccessibleJComponent() { };
 111         return accessibleContext;
 112     }
 113 
 114     /**
 115      * Set whether or not the list can be edited by the user.
 116      * @param b if true, the component can be edited by the user;
 117      *   if false, it cannot
 118      */
 119     public void setEnabled(boolean b) {
 120         super.setEnabled(b);
 121         list.setEnabled(b);
 122         updateButtons();
 123     }
 124 
 125     /**
 126      * Set the items in the list. Any previous items are removed first.
 127      * @param items the array of items to be put in the list.
 128      * @see #getItems
 129      */
 130     public void setItems(Object[] items) {
 131         listModel.clear();
 132         if (items != null) {
 133             for (int i = 0; i < items.length; i++)
 134                 listModel.addElement(items[i]);
 135         }
 136     }
 137 
 138     /**
 139      * Remove all entries from the list.
 140      */
 141     public void clear() {
 142         listModel.clear();
 143     }
 144 
 145     /**
 146      * Get the items currently in the list.
 147      * @return an array containing the items currently in the list
 148      * @see #setItems
 149      */
 150     public Object[] getItems() {
 151         return listModel.toArray();
 152     }
 153 
 154 
 155     /**
 156      * Get the items currently in the list, in an array of a specific type.
 157      * @param c the component type of the array to be returned
 158      * @return an array containing the items currently in the list
 159      * @see #setItems
 160      */
 161     public Object[] getItems(Class c) {
 162         Object[] items = (Object[]) (Array.newInstance(c, listModel.size()));
 163         listModel.copyInto(items);
 164         return items;
 165     }
 166 
 167     /**
 168      * Get the tool tip text that appears on the list.
 169      * (Separate tool tip text will appear on the buttons to manipulate the list.)
 170      * @return the tool tip text that appears on the list
 171      * @see #setToolTipText
 172      */
 173     public String getToolTipText() {
 174         return list.getToolTipText();
 175     }
 176 
 177 
 178     /**
 179      * Set the tool tip text that appears on the list.
 180      * (Separate tool tip text will appear on the buttons to manipulate the list.)
 181      * @param tip the tool tip text to appear on the list
 182      * @see #getToolTipText
 183      */
 184     public void setToolTipText(String tip) {
 185         list.setToolTipText(tip);
 186     }
 187 
 188     /**
 189      * Add a listener to be notified of events when the list data changes.
 190      * @param l the listener to be notified
 191      * @see #removeListDataListener
 192      */
 193     public void addListDataListener(ListDataListener l) {
 194         listenerList.add(ListDataListener.class, l);
 195     }
 196 
 197     /**
 198      * Remove a listener that was previously added to be notified of
 199      * events when the list data changes.
 200      * @param l the listener to be notified
 201      * @see #addListDataListener
 202      */
 203     public void removeListDataListener(ListDataListener l) {
 204         listenerList.remove(ListDataListener.class, l);
 205     }
 206 
 207     /**
 208      * Specify whether or not duplicates should be allowed in the list.
 209      * @param b true if duplicates should be allowed, and false otherwise
 210      * @see #isDuplicatesAllowed
 211      */
 212     public void setDuplicatesAllowed(boolean b) {
 213         duplicatesAllowed = b;
 214     }
 215 
 216     /**
 217      * Check whether or not duplicates should be allowed in the list.
 218      * @return true if duplicates should be allowed, and false otherwise
 219      * @see #setDuplicatesAllowed
 220      */
 221     public boolean isDuplicatesAllowed() {
 222         return duplicatesAllowed;
 223     }
 224 
 225     /**
 226      * Get the display value for an item in the list. By default, the display
 227      * value is the item itself.
 228      * @param item the object for which to get the display value
 229      * @return the display value for the specified item
 230      */
 231     protected Object getDisplayValue(Object item) {
 232         return item;
 233     }
 234 
 235     /**
 236      * Invoked to get a new item to put in the list, when the user clicks
 237      * the "Add" button". The default is to show an input dialog to allow
 238      * the user to type in a new string. Subtypes may override this method
 239      * to provide other ways of specifying items to be added, such as a
 240      * file chooser.
 241      * @return an object to be added to the list, or null if no object
 242      * to be added.
 243      */
 244     protected Object getNewItem() {
 245         return JOptionPane.showInputDialog(this, uif.getI18NString("list.add.txt"));
 246     }
 247 
 248     /**
 249      * Invoked to get a new item to replace an existing item in the list.
 250      * The default is to show an input dialog to allow
 251      * the user to type in a new string. Subtypes may override this method
 252      * to provide other ways of specifying items to be added, such as a
 253      * file chooser.
 254      * @param oldItem the item to be replaced in the list
 255      * @return an object to replace the old item the list, or null if no
 256      * replacement should occur.
 257      */
 258     protected Object getNewItem(Object oldItem) {
 259         return JOptionPane.showInputDialog(this, uif.getI18NString("list.change.txt"), oldItem);
 260     }
 261 
 262     private JButton createButton(String uiKey) {
 263         JButton b = uif.createButton(uiKey);
 264         // set max size so button can grow within toolbar
 265         b.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
 266         b.addActionListener(listener);
 267         // should be in uif.createButton?
 268         b.setMnemonic(uif.getI18NString(uiKey+ ".mne").charAt(0));
 269         return b;
 270     }
 271 
 272     private void insertItem() {
 273         Object newItem = getNewItem();
 274 
 275         if (!duplicatesAllowed && listModel.contains(newItem)) {
 276             showDuplicateError(newItem);
 277             return;
 278         }
 279 
 280         if (newItem != null) {
 281             if (list.isSelectionEmpty())
 282                 listModel.addElement(newItem);
 283             else
 284                 listModel.add(1 + list.getSelectedIndex(), newItem);
 285             list.setSelectedValue(newItem, true);
 286         }
 287     }
 288 
 289     private void removeSelectedItem() {
 290         if (!list.isSelectionEmpty())
 291             listModel.remove(list.getSelectedIndex());
 292     }
 293 
 294     private void moveSelectedItemUp() {
 295         if (!list.isSelectionEmpty()) {
 296             int i = list.getSelectedIndex();
 297             if (i > 0) {
 298                 swap(i, i - 1);
 299                 list.setSelectedIndex(i - 1);
 300             }
 301         }
 302     }
 303 
 304     private void moveSelectedItemDown() {
 305         if (!list.isSelectionEmpty()) {
 306             int i = list.getSelectedIndex();
 307             if (i + 1 < listModel.size()) {
 308                 swap(i, i + 1);
 309                 list.setSelectedIndex(i + 1);
 310             }
 311         }
 312     }
 313     private void editItem(int index) {
 314         Object newItem = getNewItem(listModel.getElementAt(index));
 315 
 316         if (!duplicatesAllowed && listModel.contains(newItem)) {
 317             showDuplicateError(newItem);
 318             return;
 319         }
 320 
 321         if (newItem != null)
 322             listModel.set(index, newItem);
 323     }
 324 
 325     private void showDuplicateError(Object item) {
 326         String text = uif.getI18NString("list.duplicate.text",
 327                                         new Object[] { getDisplayValue(item) });
 328 
 329         String title = uif.getI18NString("list.duplicate.title");
 330 
 331         JOptionPane.showMessageDialog(this,
 332                                       text,
 333                                       title,
 334                                       JOptionPane.INFORMATION_MESSAGE);
 335     }
 336 
 337     private class Renderer
 338         extends DefaultListCellRenderer {
 339         public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
 340             return super.getListCellRendererComponent(list,
 341                                                       getDisplayValue(value),
 342                                                       index,
 343                                                       isSelected,
 344                                                       cellHasFocus);
 345         }
 346     }
 347 
 348     private class Listener
 349         implements ActionListener, ListDataListener, ListSelectionListener, MouseListener
 350     {
 351         // ActionListener events, for buttons
 352         public void actionPerformed(ActionEvent e) {
 353             Object src = e.getSource();
 354             if (src == addBtn)
 355                 insertItem();
 356             else if (src == removeBtn)
 357                 removeSelectedItem();
 358             else if (src == upBtn)
 359                 moveSelectedItemUp();
 360             else if (src == downBtn)
 361                 moveSelectedItemDown();
 362 
 363             updateButtons();
 364         }
 365 
 366         // ListSelect events, to update buttons depending on list selection
 367         public void valueChanged(ListSelectionEvent e) {
 368             updateButtons();
 369         }
 370 
 371         // MouseListener, to react to double click in list
 372         public void mouseClicked(MouseEvent e) {
 373             if (e.getClickCount() == 2) {
 374                 int index = list.locationToIndex(e.getPoint());
 375                 if(index != -1)
 376                     editItem(index);
 377             }
 378         }
 379 
 380         public void mouseEntered(MouseEvent e) { }
 381         public void mouseExited(MouseEvent e) { }
 382         public void mousePressed(MouseEvent e) { }
 383         public void mouseReleased(MouseEvent e) { }
 384 
 385         // ListData events, to redispatch to client, with EditableList.this as the source
 386         public void contentsChanged(ListDataEvent e) {
 387             ListDataEvent e2 = null;
 388             Object[] listeners = listenerList.getListenerList();
 389             for (int i = listeners.length - 2; i >= 0; i -= 2) {
 390                 if (listeners[i] == ListDataListener.class) {
 391                     if (e2 == null)
 392                         e2 = new ListDataEvent(EditableList.this, e.getType(), e.getIndex0(), e.getIndex1());
 393                     ((ListDataListener)listeners[i+1]).contentsChanged(e2);
 394                 }
 395             }
 396         }
 397 
 398         public void intervalAdded(ListDataEvent e) {
 399             ListDataEvent e2 = null;
 400             Object[] listeners = listenerList.getListenerList();
 401             for (int i = listeners.length - 2; i >= 0; i -= 2) {
 402                 if (listeners[i] == ListDataListener.class) {
 403                     if (e2 == null)
 404                         e2 = new ListDataEvent(EditableList.this, e.getType(), e.getIndex0(), e.getIndex1());
 405                     ((ListDataListener)listeners[i+1]).intervalAdded(e2);
 406                 }
 407             }
 408         }
 409 
 410         public void intervalRemoved(ListDataEvent e) {
 411             ListDataEvent e2 = null;
 412             Object[] listeners = listenerList.getListenerList();
 413             for (int i = listeners.length - 2; i >= 0; i -= 2) {
 414                 if (listeners[i] == ListDataListener.class) {
 415                     if (e2 == null)
 416                         e2 = new ListDataEvent(EditableList.this, e.getType(), e.getIndex0(), e.getIndex1());
 417                     ((ListDataListener)listeners[i+1]).intervalRemoved(e2);
 418                 }
 419             }
 420         }
 421     }
 422 
 423     private void updateButtons() {
 424         boolean enabled = isEnabled();
 425         addBtn.setEnabled(enabled);
 426         if (list.isSelectionEmpty() || !enabled) {
 427             removeBtn.setEnabled(false);
 428             upBtn.setEnabled(false);
 429             downBtn.setEnabled(false);
 430         }
 431         else {
 432             removeBtn.setEnabled(true);
 433             int i = list.getSelectedIndex();
 434             upBtn.setEnabled(i > 0);
 435             downBtn.setEnabled((i + 1 < listModel.size()));
 436         }
 437     }
 438 
 439     private void swap(int i1, int i2) {
 440         Object o1 = listModel.elementAt(i1);
 441         Object o2 = listModel.elementAt(i2);
 442         listModel.set(i1, o2);
 443         listModel.set(i2, o1);
 444     }
 445 
 446     /**
 447      * The factory used to create the GUI elements of the component.
 448      */
 449     protected final UIFactory uif;
 450 
 451     // only initialize this if required
 452     private static UIFactory defaultUIF;
 453 
 454     /**
 455      * The list model that contains the elements of the list.
 456      */
 457     protected DefaultListModel<Object> listModel;
 458     private JList<Object> list;
 459     private JButton addBtn;
 460     private JButton removeBtn;
 461     private JButton upBtn;
 462     private JButton downBtn;
 463     private Listener listener = new Listener();
 464     private Renderer renderer = new Renderer();
 465 
 466     private boolean duplicatesAllowed;
 467 }