Clover coverage report - DrJava Test Coverage (drjava-20120422-r5456)
Coverage timestamp: Sun Apr 22 2012 03:13:25 CDT
file stats: LOC: 399   Methods: 29
NCLOC: 209   Classes: 2
 
 Source file Conditionals Statements Methods TOTAL
ScrollableListDialog.java 75% 62.3% 44.8% 61.8%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.util.swing;
 38   
 39    import javax.swing.*;
 40    import java.awt.BorderLayout;
 41    import java.awt.Dimension;
 42    import java.awt.EventQueue;
 43    import java.awt.FlowLayout;
 44    import java.awt.Frame;
 45    import java.awt.Toolkit;
 46    import java.awt.event.*;
 47    import java.util.ArrayList;
 48    import java.util.List;
 49    import java.util.Vector;
 50   
 51   
 52    /** <p>The ScrollableListDialog is a popup dialog with a message and a scrollable list of items. A ScrollableListDialog
 53    * should be used when a message may need to display a variable number of items, for example, when reporting missing
 54    * files.</p>
 55    * <p>The message (also know as the leader text) is displayed above the items with an optional icon. The items are
 56    * displayed in a scrollable list. Buttons are added below the list of items.</p>
 57    * <p>This dialog is somewhat styled after {@link javax.swing.JOptionPane} and uses the message-type constants from
 58    * JOptionPane.</p>
 59    *
 60    * @author Chris Warrington
 61    * @version $Id: ScrollableListDialog.java 5175 2010-01-20 08:46:32Z mgricken $
 62    * @since 2007-03-06
 63    */
 64    public class ScrollableListDialog<T> extends JDialog {
 65    /** The default width for this dialog. */
 66    private static final int DEFAULT_WIDTH = 400;
 67    /** The default height for this dialog. */
 68    private static final int DEFAULT_HEIGHT = 450;
 69   
 70    /** The ratio of the screen width to use by default. */
 71    private static final double WIDTH_RATIO = .75;
 72    /** The ratio of the screen height to use by default. */
 73    private static final double HEIGHT_RATIO = .50;
 74   
 75    /** The list of items displayed. */
 76    protected final JList list;
 77   
 78    /** The number of the button that was pressed to close the dialog. */
 79    protected int _buttonPressed = -1;
 80   
 81    /** The list of items being listed. */
 82    protected List<T> listItems;
 83   
 84    /** Factory design pattern. Used to create new ScrollableListDialogs with
 85    * less complicated and ambiguous constructors. */
 86    public static class Builder<T> {
 87    protected Frame _owner;
 88    protected String _dialogTitle;
 89    protected String _leaderText;
 90    protected List<T> _listItems = new ArrayList<T>();
 91    protected List<T> _selectedItems = new ArrayList<T>();
 92    protected int _messageType = JOptionPane.PLAIN_MESSAGE;
 93    protected int _width = DEFAULT_WIDTH;
 94    protected int _height = DEFAULT_HEIGHT;
 95    protected Icon _icon = null;
 96    protected boolean _fitToScreen = true;
 97    protected List<JButton> _buttons = new ArrayList<JButton>();
 98    protected List<JComponent> _additional = new ArrayList<JComponent>();
 99    protected boolean _selectable = false;
 100  9 public Builder() { addOkButton(); }
 101  9 public Builder<T> setOwner(Frame owner) { _owner = owner; return this; }
 102  9 public Builder<T> setTitle(String dialogTitle) { _dialogTitle = dialogTitle; return this; }
 103  9 public Builder<T> setText(String leaderText) { _leaderText = leaderText; return this; }
 104  9 public Builder<T> setItems(List<T> listItems) { _listItems = listItems; return this; }
 105  0 public Builder<T> setSelectedItems(List<T> selItems) { _selectedItems = selItems; return this; }
 106  7 public Builder<T> setMessageType(int messageType) { _messageType = messageType; return this; }
 107  0 public Builder<T> setWidth(int width) { _width = width; return this; }
 108  0 public Builder<T> setHeight(int height) { _height = height; return this; }
 109  0 public Builder<T> setIcon(Icon icon) { _icon = icon; return this; }
 110  0 public Builder<T> setFitToScreen(boolean fts) { _fitToScreen = fts; return this; }
 111  0 public Builder<T> clearButtons() { _buttons.clear(); return this; }
 112  9 public Builder<T> addOkButton() { _buttons.add(new JButton("OK")); return this; }
 113  0 public Builder<T> addButton(JButton b) { _buttons.add(b); return this; }
 114  0 public Builder<T> addAdditionalComponent(JComponent c) { _additional.add(c); return this; }
 115  0 public Builder<T> setSelectable(boolean b) { _selectable = b; return this; }
 116  9 public ScrollableListDialog<T> build() {
 117  9 return new ScrollableListDialog<T>(_owner, _dialogTitle, _leaderText, _listItems, _selectedItems, _messageType,
 118    _width, _height, _icon, _fitToScreen, _buttons, _additional, _selectable);
 119    }
 120    }
 121   
 122    /** <p>Creates a new ScrollableListDialog with the given title, leader
 123    * text, items, message type, width, height, and icon. The list of
 124    * items is used to construct an internal string list that is not
 125    * backed by the original list. Changes made to the list or items
 126    * after dialog construction will not be reflected in the dialog.</p>
 127    *
 128    * <p>The message type must be one of the message types from
 129    * {@link javax.swing.JOptionPane}. The message type controlls which
 130    * default icon is used. If {@code icon} is non-null, it is used
 131    * instead of the default icon.</p>
 132    *
 133    * @param owner The frame that owns this dialog. May be {@code null}.
 134    * @param dialogTitle The text to use as the dialog title.
 135    * @param leaderText Text to display before the list of items.
 136    * @param listItems The items to display in the list.
 137    * @param selItems The items selected at the beginning.
 138    * @param messageType The type of dialog message.
 139    * @param width The width of the dialog box.
 140    * @param height The height of the dialog box.
 141    * @param icon The icon to display. May be {@code null}.
 142    * @param fitToScreen If {@code true}, the width and height of the dialog will be calculated using the screen
 143    * dimensions, {@link #WIDTH_RATIO}, and {@link #HEIGHT_RATIO}. If {@code false}, the provided width and
 144    * height will be used.
 145    * @param buttons The list of buttons to display
 146    * @param additional The list of additional components to display
 147    * @param selectable true if items can be selected
 148    *
 149    * @throws IllegalArgumentException if {@code listItems} is {@code null.}
 150    * @throws IllegalArgumentException if the message type is unknown or {@code listItems} is {@code null.}
 151    */
 152  9 private ScrollableListDialog(Frame owner, String dialogTitle, String leaderText, List<T> listItems, List<T> selItems,
 153    int messageType, int width, int height, Icon icon, boolean fitToScreen,
 154    List<JButton> buttons, List<JComponent> additional, boolean selectable) {
 155  9 super(owner, dialogTitle, true);
 156  9 this.listItems = listItems;
 157   
 158  9 if (!_isknownMessageType(messageType)) {
 159  2 throw new IllegalArgumentException("The message type \"" + messageType + "\" is unknown");
 160    }
 161   
 162  1 if (listItems == null) throw new IllegalArgumentException("listItems cannot be null");
 163   
 164    /* create the leader text panel */
 165  6 JLabel dialogIconLabel = null;
 166  6 if (icon != null) { //use the user-provided icon
 167  0 dialogIconLabel = new JLabel(icon);
 168    }
 169    else { //lookup the message-dependent icon
 170  6 Icon messageIcon = _getIcon(messageType);
 171  4 if (messageIcon != null) dialogIconLabel = new JLabel(messageIcon);
 172    }
 173   
 174  6 final JLabel leaderLabel = new JLabel(leaderText);
 175  6 final JPanel leaderPanel = new JPanel();
 176  6 leaderPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
 177  4 if (dialogIconLabel != null) leaderPanel.add(dialogIconLabel);
 178  6 leaderPanel.add(leaderLabel);
 179   
 180    /* create the item list box */
 181    //copy the items string representations into a vector
 182  6 final Vector<String> dataAsStrings = new Vector<String>(listItems.size());
 183    //keep track of the longest string for use later
 184  6 String longestString = "";
 185  6 for (T obj : listItems) {
 186  12 if (obj != null) {
 187  12 final String objAsString = obj.toString();
 188    //update longest string
 189  12 if (objAsString.length() > longestString.length()) {
 190  6 longestString = objAsString;
 191    }
 192  12 dataAsStrings.add(objAsString);
 193    }
 194    }
 195   
 196  6 if (selectable) {
 197  0 final Vector<String> selAsStrings = new Vector<String>(selItems.size());
 198  0 for (T obj : selItems) {
 199  0 if (obj != null) {
 200  0 final String objAsString = obj.toString();
 201  0 selAsStrings.add(objAsString);
 202    }
 203    }
 204  0 list = new CheckBoxJList(dataAsStrings, selAsStrings);
 205    //let the user select several
 206  0 list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
 207    }
 208    else {
 209  6 list = new JList(dataAsStrings);
 210    //let the user select several
 211  6 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 212    }
 213    //use the longest string to calculate item cell widths and heights
 214  6 list.setPrototypeCellValue(longestString);
 215   
 216    //create a scrollable view around the list
 217  6 final JScrollPane scrollPane = new JScrollPane(list);
 218   
 219    /* create the button panel */
 220  6 final JPanel buttonPanel = new JPanel();
 221  6 buttonPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
 222    //allow children to add additional buttons, if overridden
 223  6 _addButtons(buttonPanel, buttons);
 224  6 _addAdditionalComponents(buttonPanel, additional);
 225   
 226    /* create the dialog */
 227  6 final JPanel contentPanel = new JPanel();
 228  6 contentPanel.setLayout(new BorderLayout(10, 5));
 229  6 contentPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 10));
 230   
 231  6 contentPanel.add(leaderPanel, BorderLayout.NORTH);
 232  6 contentPanel.add(scrollPane, BorderLayout.CENTER);
 233  6 contentPanel.add(buttonPanel, BorderLayout.SOUTH);
 234   
 235  6 getContentPane().add(contentPanel);
 236   
 237    /* calculate the dialog's dimensions */
 238  6 Dimension dialogSize = new Dimension();
 239   
 240  6 if (fitToScreen) {
 241    //use the screen dimensions to calculate the dialog's
 242  6 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
 243  6 int screenBasedWidth = (int) (WIDTH_RATIO * screenSize.getWidth());
 244  6 int screenBasedHeight = (int) (HEIGHT_RATIO * screenSize.getHeight());
 245   
 246  6 dialogSize.setSize(Math.max(DEFAULT_WIDTH, screenBasedWidth),
 247    Math.max(DEFAULT_HEIGHT, screenBasedHeight));
 248    } else {
 249    //use the user-provided dimensions
 250  0 dialogSize.setSize(width, height);
 251    }
 252   
 253  6 setSize(dialogSize);
 254    }
 255   
 256    /**
 257    * A method to check if they given message type is a know message
 258    * type.
 259    *
 260    * @param messageType The message type to check
 261    * @return {@code true} if the message type is known, {@code false} otherwise
 262    */
 263  15 private boolean _isknownMessageType(int messageType) {
 264  15 return messageType == JOptionPane.ERROR_MESSAGE ||
 265    messageType == JOptionPane.INFORMATION_MESSAGE ||
 266    messageType == JOptionPane.WARNING_MESSAGE ||
 267    messageType == JOptionPane.QUESTION_MESSAGE ||
 268    messageType == JOptionPane.PLAIN_MESSAGE;
 269    }
 270   
 271    /**
 272    * Lookup the icon associated with the given messageType. The message
 273    * type must be one of the message types from
 274    * {@link javax.swing.JOptionPane}.
 275    *
 276    * @param messageType The message for which the icon is requested.
 277    * @return The message's icon or {@code null} is no icon was found.
 278    */
 279  6 private Icon _getIcon(int messageType) {
 280  6 assert _isknownMessageType(messageType);
 281   
 282    /* The OptionPane.xxxIcon constants were taken from
 283    * javax.swing.plaf.basic.BasicOptionPaneUI, which may changed
 284    * without notice.
 285    */
 286  6 if (messageType == JOptionPane.ERROR_MESSAGE) {
 287  1 return UIManager.getIcon("OptionPane.errorIcon");
 288  5 } else if (messageType == JOptionPane.INFORMATION_MESSAGE) {
 289  1 return UIManager.getIcon("OptionPane.informationIcon");
 290  4 } else if (messageType == JOptionPane.WARNING_MESSAGE) {
 291  1 return UIManager.getIcon("OptionPane.warningIcon");
 292  3 } else if (messageType == JOptionPane.QUESTION_MESSAGE) {
 293  1 return UIManager.getIcon("OptionPane.questionIcon");
 294  2 } else if (messageType == JOptionPane.PLAIN_MESSAGE) {
 295  2 return null;
 296    } else {
 297    //should never get here
 298    assert false;
 299    }
 300   
 301  0 return null;
 302    }
 303   
 304    /**
 305    * Adds buttons to the bottom of the dialog. By default, a single
 306    * &quot;OK&quot; button is added that calls {@link #closeDialog}. It
 307    * is also set as the dialog's default button.
 308    *
 309    * Inheritors should feel free the change settings of the panel such
 310    * as the layout manager. However, no guarantees are made that every
 311    * change will work with every version of this class.
 312    *
 313    * @param buttonPanel The JPanel that should contain the buttons.
 314    * @param buttons The list of buttons
 315    */
 316  6 protected void _addButtons(JPanel buttonPanel, List<JButton> buttons) {
 317  6 int i = 0;
 318  6 for (JButton b: buttons) {
 319  6 final int j = i++;
 320  6 b.addActionListener(new ActionListener() {
 321  0 public void actionPerformed(ActionEvent notUsed) {
 322  0 _buttonPressed = j;
 323  0 closeDialog();
 324    }
 325    });
 326  6 buttonPanel.add(b);
 327    }
 328   
 329  6 getRootPane().setDefaultButton(buttons.get(0));
 330    }
 331   
 332    /**
 333    * Adds additional components to the bottom of the dialog.
 334    * @param buttonPanel The JPanel that should contain the components.
 335    * @param additional The list of components
 336    */
 337  6 protected void _addAdditionalComponents(JPanel buttonPanel, List<JComponent> additional) {
 338  6 int i = 0;
 339  6 for (JComponent c: additional) {
 340  0 buttonPanel.add(c);
 341    }
 342    }
 343   
 344    /**
 345    * Shows the dialog.
 346    */
 347  0 public void showDialog() {
 348  0 pack();
 349  0 Utilities.setPopupLoc(this, getOwner());
 350  0 setVisible(true);
 351    }
 352   
 353    /**
 354    * Should be called when the dialog should be closed. The default
 355    * implementation simply hides the dialog.
 356    */
 357  0 protected void closeDialog() {
 358  0 setVisible(false);
 359    }
 360   
 361    /** Return the number of the button that was pressed to close the dialog. */
 362  0 public int getButtonPressed() { return _buttonPressed; }
 363   
 364    /** Return a list of the selected items. */
 365  0 public List<T> getSelectedItems() {
 366  0 ArrayList<T> l = new ArrayList<T>();
 367  0 for (int i: list.getSelectedIndices()) l.add(listItems.get(i));
 368   
 369  0 return l;
 370    }
 371   
 372    /** A simple main method for testing purposes.
 373    * @param args Not used.
 374    */
 375  0 public static void main(String args[]) {
 376  0 final List<String> data = new java.util.ArrayList<String>();
 377  0 data.add("how");
 378  0 data.add("now");
 379  0 data.add("brown");
 380  0 data.add("cow");
 381   
 382  0 EventQueue.invokeLater(new Runnable() {
 383  0 public void run() {
 384  0 ScrollableListDialog<String> ld = new ScrollableListDialog.Builder<String>()
 385    .setOwner(null)
 386    .setTitle("TITLE")
 387    .setText("LEADER")
 388    .setItems(data)
 389    .setMessageType(JOptionPane.ERROR_MESSAGE)
 390    .setSelectable(true)
 391    .setSelectedItems(data.subList(0,2))
 392    .build();
 393  0 ld.pack();
 394  0 ld.setVisible(true);
 395    }
 396    });
 397    }
 398    }
 399