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 }