001/** 002 * SelectCasesDialog -- Dialog allowing user to select a subset of a list of cases. 003 * 004 * Copyright (C) 2003-2016, by Joseph A. Huwaldt. All rights reserved. 005 * 006 * This library is free software; you can redistribute it and/or modify it under the terms 007 * of the GNU Lesser General Public License as published by the Free Software Foundation; 008 * either version 2 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 011 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 012 * PARTICULAR PURPOSE. See the GNU Library General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public License along with 015 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - 016 * Suite 330, Boston, MA 02111-1307, USA. Or visit: http://www.gnu.org/licenses/lgpl.html 017 */ 018package jahuwaldt.js.datareader; 019 020import jahuwaldt.swing.EscapeJDialog; 021import java.awt.BorderLayout; 022import java.awt.Component; 023import java.awt.Dimension; 024import java.awt.Frame; 025import java.awt.event.*; 026import java.util.ArrayList; 027import java.util.List; 028import java.util.ResourceBundle; 029import javax.swing.*; 030import javax.swing.event.DocumentEvent; 031import javax.swing.event.DocumentListener; 032import javax.swing.table.AbstractTableModel; 033import javax.swing.table.TableCellRenderer; 034import javax.swing.table.TableColumn; 035import javax.swing.table.TableRowSorter; 036 037/** 038 * A modal dialog the allows the user to select a subset of all the available cases in a 039 * list of data sets. 040 * 041 * <p> Modified by: Joseph A. Huwaldt </p> 042 * 043 * @author Joseph A. Huwaldt, Date: March 25, 2003 044 * @version October 29, 2016 045 */ 046@SuppressWarnings("serial") 047public class SelectCasesDialog extends EscapeJDialog implements ActionListener { 048 049 /** 050 * The resource bundle for this package. 051 */ 052 public static final ResourceBundle RESOURCES = DataReader.RESOURCES; 053 054 // A list of column names to display above the table. 055 private static final String[] columnNames = { 056 padRight(RESOURCES.getString("dataSetColName"), 20), 057 padRight(RESOURCES.getString("dataCaseColName"), 20)}; 058 059 // A list of lists of strings. 060 private List<List<Object>> rowData = new ArrayList<>(); 061 062 /** 063 * The full list of DataElements displayed to the user. 064 */ 065 private List<DataElement> elements = new ArrayList<>(); 066 067 /** 068 * The total number of input DataSets. 069 */ 070 private int numSets = 0; 071 072 /** 073 * The JTable displaying the contents of the "elements" list that was input. 074 */ 075 private JTable inputTable; 076 077 /** 078 * The row sorter used on the input table. 079 */ 080 private TableRowSorter<AbstractTableModel> inputSorter; 081 082 /** 083 * The filter text field for filtering the input table. 084 */ 085 private final JTextField filterText; 086 087 /** 088 * The JTable displaying the elements that the user has selected. This table is only 089 * used if multiple selections are possible. 090 */ 091 private JTable outputTable; 092 093 // A list of the cases that were selected by the user. 094 private DataSet selectedCases; 095 096 /** 097 * Flag indicating if the user is allowed to select multiple items or not. 098 */ 099 private boolean singleSelection = false; 100 101 // A list of lists of selected elements. 102 private ArrayList<ArrayList<Object>> selRowData = new ArrayList<>(); 103 private ArrayList<DataElement> selElements = new ArrayList<>(); 104 105 /** 106 * Construct a model dialog that allows the user to select a subset of all the 107 * available cases in a list of data sets. 108 * 109 * @param parent A reference to the parent frame that this dialog belongs to. 110 * @param title The title for the dialog window. 111 * @param message A message to display telling the user what to do. 112 * @param data A list of DataSet objects containing the runs to be chosen 113 * from. 114 * @param singleSelection Set to true to allow only a single element to be selected, 115 * false for multiple elements. 116 * @param showArraySizes Append the size of ArrayParam objects to the parameter names 117 * if set to true. Show only the parameter names if set to 118 * false. 119 */ 120 public SelectCasesDialog(Frame parent, String title, String message, List<? extends DataElementList<?>> data, 121 boolean singleSelection, boolean showArraySizes) { 122 super(parent, title, true); 123 124 // Copy all the cases from the input data sets into the list. 125 numSets = data.size(); 126 for (DataElementList<?> list : data) { 127 128 for (DataElement element : list) { 129 elements.add(element); 130 ArrayList<Object> colData = new ArrayList<>(); 131 colData.add(list.getName().toString()); 132 if (element instanceof ArrayParam) { 133 String str = element.toString(); 134 if (showArraySizes) 135 str += " (" + ((ArrayParam)element).size() + ")"; 136 colData.add(str); 137 } else 138 colData.add(element); 139 rowData.add(colData); 140 } 141 } 142 143 // If there is only one row, simplify the dialog. 144 if (rowData.size() == 1) 145 singleSelection = true; 146 this.singleSelection = singleSelection; 147 148 // Layout the dialog window. 149 JPanel cp = new JPanel(new BorderLayout()); 150 this.getContentPane().add(cp); 151 cp.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); 152 153 // Add instructions at the top. 154 Box topPanel = Box.createHorizontalBox(); 155 cp.add(topPanel, BorderLayout.NORTH); 156 157 JLabel label = new JLabel(message, SwingConstants.LEADING); 158 label.setBorder(BorderFactory.createEmptyBorder(6, 0, 6, 0)); 159 topPanel.add(label); 160 topPanel.add(Box.createHorizontalStrut(10)); 161 topPanel.add(Box.createGlue()); 162 163 // Create a panel to display in the center. 164 Box centerPanel = Box.createVerticalBox(); 165 cp.add(centerPanel, BorderLayout.CENTER); 166 Box tablePanel = Box.createHorizontalBox(); 167 centerPanel.add(tablePanel); 168 169 // Create a table that displays the cases in all the input data sets. 170 AbstractTableModel model = new AbstractTableModel() { 171 @Override 172 public String getColumnName(int col) { 173 if (numSets == 1) 174 col = 1; 175 return SelectCasesDialog.this.getColumnName(col); 176 } 177 178 @Override 179 public int getRowCount() { 180 return rowData.size(); 181 } 182 183 @Override 184 public int getColumnCount() { 185 if (numSets == 1) 186 return 1; 187 return columnNames.length; 188 } 189 190 @Override 191 public Object getValueAt(int row, int col) { 192 if (numSets == 1) 193 col = 1; 194 return ((ArrayList<?>)rowData.get(row)).get(col); 195 } 196 197 @Override 198 public boolean isCellEditable(int row, int col) { 199 return false; 200 } 201 }; 202 inputTable = new JTable(model); 203 inputSorter = new TableRowSorter<>(model); 204 inputTable.setRowSorter(inputSorter); 205 inputTable.setCellSelectionEnabled(false); 206 inputTable.setColumnSelectionAllowed(false); 207 inputTable.setRowSelectionAllowed(true); 208 inputTable.clearSelection(); 209 inputTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); // Show horiz. scroll bars. 210 inputTable.addMouseListener(new MouseAdapter() { 211 @Override 212 public void mouseClicked(MouseEvent e) { 213 if (e.getClickCount() == 2) { 214 if (!SelectCasesDialog.this.singleSelection) 215 doAdd(); 216 else 217 SelectCasesDialog.this.actionPerformed(null); 218 } 219 } 220 }); 221 if (singleSelection) 222 inputTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 223 224 // If only a single row exists, preselect it. 225 if (rowData.size() == 1) 226 inputTable.selectAll(); 227 else { 228 ListSelectionModel selModel = inputTable.getSelectionModel(); 229 selModel.setSelectionInterval(0, 0); 230 } 231 232 // Initialize the column widths to show all the data. 233 initColumnWidths(inputTable); 234 235 // Determine the table preferred width. 236 int width = 0; 237 int numColumns = inputTable.getColumnCount(); 238 for (int i = 0; i < numColumns; ++i) { 239 TableColumn column = inputTable.getColumnModel().getColumn(i); 240 width += column.getPreferredWidth(); 241 } 242 inputTable.setPreferredScrollableViewportSize(new Dimension(width, 300)); 243 244 // Put the list in a scroll pane so that we have scroll bars. 245 JScrollPane scrollPane = new JScrollPane(inputTable); 246 scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 247 scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 248 249 // Add the list to the panel. 250 tablePanel.add(scrollPane); 251 252 if (!singleSelection) { 253 254 // Create a button panel for selection buttons. 255 tablePanel.add(Box.createHorizontalStrut(10)); 256 tablePanel.add(createButtonPanel()); 257 258 // Create an output table that displays selected tables. 259 outputTable = new JTable(new AbstractTableModel() { 260 @Override 261 public String getColumnName(int col) { 262 if (numSets == 1) 263 col = 1; 264 return SelectCasesDialog.this.getColumnName(col); 265 } 266 267 @Override 268 public int getRowCount() { 269 return selRowData.size(); 270 } 271 272 @Override 273 public int getColumnCount() { 274 if (numSets == 1) 275 return 1; 276 return columnNames.length; 277 } 278 279 @Override 280 public Object getValueAt(int row, int col) { 281 if (numSets == 1) 282 col = 1; 283 return ((ArrayList<?>)selRowData.get(row)).get(col); 284 } 285 286 @Override 287 public boolean isCellEditable(int row, int col) { 288 return false; 289 } 290 }); 291 outputTable.setCellSelectionEnabled(false); 292 outputTable.setColumnSelectionAllowed(false); 293 outputTable.setRowSelectionAllowed(true); 294 outputTable.clearSelection(); 295 outputTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); // Show hoirz. scroll bars. 296 outputTable.addMouseListener(new MouseAdapter() { 297 @Override 298 public void mouseClicked(MouseEvent e) { 299 if (e.getClickCount() == 2) { 300 doRemove(); 301 } 302 } 303 }); 304 305 // Initialize the column widths to show all the data. 306 initColumnWidths(outputTable); 307 308 // Determine the table preferred width. 309 width = 0; 310 numColumns = outputTable.getColumnCount(); 311 for (int i = 0; i < numColumns; ++i) { 312 TableColumn column = outputTable.getColumnModel().getColumn(i); 313 width += column.getPreferredWidth(); 314 } 315 outputTable.setPreferredScrollableViewportSize(new Dimension(width, 300)); 316 317 // Put the list in a scroll pane so that we have scroll bars. 318 scrollPane = new JScrollPane(outputTable); 319 scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); 320 scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); 321 322 // Add the list to the panel. 323 tablePanel.add(Box.createHorizontalStrut(10)); 324 tablePanel.add(scrollPane); 325 } 326 327 // Define the filter text field. 328 centerPanel.add(Box.createVerticalStrut(10)); 329 Box form = Box.createHorizontalBox(); 330 JLabel l1 = new JLabel(RESOURCES.getString("filterTextLabel"), SwingConstants.TRAILING); 331 form.add(l1); 332 filterText = new JTextField(); 333 // Whenever filterText changes, invoke newInputFilter. 334 filterText.getDocument().addDocumentListener(new DocumentListener() { 335 @Override 336 public void changedUpdate(DocumentEvent e) { 337 newInputFilter(); 338 } 339 340 @Override 341 public void insertUpdate(DocumentEvent e) { 342 newInputFilter(); 343 } 344 345 @Override 346 public void removeUpdate(DocumentEvent e) { 347 newInputFilter(); 348 } 349 }); 350 l1.setLabelFor(filterText); 351 form.add(filterText); 352 centerPanel.add(form); 353 centerPanel.add(Box.createVerticalStrut(10)); 354 355 // Define OK and Cancel buttons. 356 Box box = Box.createHorizontalBox(); 357 box.add(Box.createGlue()); 358 359 JButton cancelBtn = new JButton(RESOURCES.getString("cancelBtnText")); 360 cancelBtn.addActionListener(new ActionListener() { 361 @Override 362 public void actionPerformed(ActionEvent e) { 363 performEscapeAction(null); 364 } 365 }); 366 box.add(cancelBtn); 367 368 box.add(Box.createHorizontalStrut(40)); 369 370 // If multiple selections allowed, change "OK" button text to "Done". 371 String okMessage = RESOURCES.getString("doneBtnText"); 372 if (singleSelection) 373 okMessage = RESOURCES.getString("selectBtnText"); 374 JButton okayBtn = new JButton(okMessage); 375 okayBtn.addActionListener(new ActionListener() { 376 @Override 377 public void actionPerformed(ActionEvent e) { 378 SelectCasesDialog.this.actionPerformed(null); 379 } 380 }); 381 this.getRootPane().setDefaultButton(okayBtn); 382 box.add(okayBtn); 383 384 box.add(Box.createHorizontalStrut(20)); 385 cp.add(box, BorderLayout.SOUTH); 386 387 this.pack(); 388 } 389 390 /** 391 * Pad a string by adding spaces on the right side until the given length is reached. 392 * 393 * @param s The string to be padded. 394 * @param n The desired length of the string. 395 * @return The input string with spaces added to the right until the given length is 396 * reached 397 */ 398 protected static String padRight(String s, int n) { 399 return String.format("%1$-" + n + "s", s); 400 } 401 402 /** 403 * This method returns a single DataSet object that contains all the cases that were 404 * selected in this dialog. If the user has not clicked on the "OK" button, null is 405 * returned. 406 * 407 * @return A single DataSet object that contains all the cases that were selected in 408 * this dialog. 409 */ 410 public DataSet getSelected() { 411 return selectedCases; 412 } 413 414 /** 415 * Method that creates a button panel containing selection buttons. 416 */ 417 private Box createButtonPanel() { 418 419 // Create selection buttons to run down the center. 420 Box btnBox = Box.createVerticalBox(); 421 btnBox.add(Box.createGlue()); 422 423 // Create an "Add" button. 424 JButton button = new JButton(RESOURCES.getString("addBtnText")); 425 button.setAlignmentX(JButton.CENTER_ALIGNMENT); 426 button.addActionListener(new ActionListener() { 427 @Override 428 public void actionPerformed(ActionEvent e) { 429 doAdd(); 430 } 431 }); 432 btnBox.add(button); 433 btnBox.add(Box.createVerticalStrut(10)); 434 435 // Create a "Remove" button. 436 button = new JButton(RESOURCES.getString("removeBtnText")); 437 button.setAlignmentX(JButton.CENTER_ALIGNMENT); 438 button.addActionListener(new ActionListener() { 439 @Override 440 public void actionPerformed(ActionEvent e) { 441 doRemove(); 442 } 443 }); 444 btnBox.add(button); 445 btnBox.add(Box.createVerticalStrut(10)); 446 447 // Create an "Add All" button. 448 button = new JButton(RESOURCES.getString("addAllBtnText")); 449 button.setAlignmentX(JButton.CENTER_ALIGNMENT); 450 button.addActionListener(new ActionListener() { 451 @Override 452 public void actionPerformed(ActionEvent e) { 453 doAddAll(); 454 } 455 }); 456 btnBox.add(button); 457 btnBox.add(Box.createVerticalStrut(10)); 458 459 // Create a "Remove All" button. 460 button = new JButton(RESOURCES.getString("removeAllBtnText")); 461 button.setAlignmentX(JButton.CENTER_ALIGNMENT); 462 button.addActionListener(new ActionListener() { 463 @Override 464 public void actionPerformed(ActionEvent e) { 465 doRemoveAll(); 466 } 467 }); 468 btnBox.add(button); 469 btnBox.add(Box.createGlue()); 470 471 return btnBox; 472 } 473 474 /** 475 * This method is called when the user clicks on the "Add" button. 476 */ 477 private void doAdd() { 478 479 // Copy the selected elements to the output table. 480 int[] selected = inputTable.getSelectedRows(); 481 int numSelected = selected.length; 482 483 for (int i = 0; i < numSelected; ++i) { 484 int modelRow = inputTable.convertRowIndexToModel(selected[i]); // Convert from View to Model indexes. 485 DataElement element = elements.get(modelRow); 486 selElements.add(element); 487 ArrayList<Object> colData = new ArrayList<>(); 488 colData.add(inputTable.getValueAt(selected[i], 0)); 489 colData.add(inputTable.getValueAt(selected[i], 1)); 490 selRowData.add(colData); 491 } 492 493 if (numSelected > 0) 494 // Update the output table's UI. 495 outputTable.revalidate(); 496 497 } 498 499 /** 500 * This method is called when the user clicks on the "Add All" button. 501 */ 502 private void doAddAll() { 503 504 int numRows = inputTable.getRowCount(); 505 for (int i = 0; i < numRows; ++i) { 506 int modelRow = inputTable.convertRowIndexToModel(i); // Convert from View to Model indexes. 507 DataElement element = elements.get(modelRow); 508 selElements.add(element); 509 ArrayList<Object> colData = new ArrayList<>(); 510 colData.add(inputTable.getValueAt(i, 0)); 511 colData.add(inputTable.getValueAt(i, 1)); 512 selRowData.add(colData); 513 } 514 515 outputTable.revalidate(); 516 517 } 518 519 /** 520 * This method is called when the user clicks on the "Remove" button. 521 */ 522 private void doRemove() { 523 524 // Get a list of all the selected rows in the table. 525 int[] selected = outputTable.getSelectedRows(); 526 int numSelected = selected.length; 527 528 for (int i = 0; i < numSelected; ++i) { 529 selElements.remove(selected[i]); 530 selRowData.remove(selected[i]); 531 } 532 533 outputTable.revalidate(); 534 } 535 536 /** 537 * This method is called when the user clicks on the "Remove All" button. 538 */ 539 private void doRemoveAll() { 540 selElements.clear(); 541 selRowData.clear(); 542 543 outputTable.revalidate(); 544 } 545 546 /** 547 * Method that returns a list of selected elements. 548 * 549 * @return A list of selected elements. 550 */ 551 protected DataElement[] getSelectedElements() { 552 553 DataElement[] selList; 554 555 if (singleSelection) { 556 // Get a list of all the selected rows in the input table. 557 int[] selected = inputTable.getSelectedRows(); 558 int numSelected = selected.length; 559 560 selList = new DataElement[numSelected]; 561 for (int i = 0; i < numSelected; ++i) { 562 int modelRow = inputTable.convertRowIndexToModel(selected[i]); // Convert from View to Model indexes. 563 selList[i] = elements.get(modelRow); 564 } 565 } else { 566 // Return a list of all the elements in the output table. 567 int numRows = outputTable.getRowCount(); 568 selList = new DataElement[numRows]; 569 for (int i = 0; i < numRows; ++i) { 570 selList[i] = selElements.get(i); 571 } 572 573 } 574 575 return selList; 576 } 577 578 /** 579 * This method is called when the user clicks on the "OK Button". 580 * 581 * @param evt The event that caused this method to be called (ignored). 582 */ 583 @Override 584 public void actionPerformed(ActionEvent evt) { 585 586 // Get a list of all the selected rows in the table. 587 DataElement[] selected = getSelectedElements(); 588 int numSelected = selected.length; 589 590 // Add selected cases to the selected data set. 591 selectedCases = DataSet.newInstance(RESOURCES.getString("selectedSetName")); 592 for (int i = 0; i < numSelected; ++i) { 593 selectedCases.add((DataCase)selected[i]); 594 } 595 596 // Hide the window. 597 this.setVisible(false); 598 } 599 600 /** 601 * Response to ENTER key pressed goes here. This has the same result as clicking on 602 * the OK or Select button. 603 */ 604 @Override 605 protected void performEnterAction(KeyEvent e) { 606 actionPerformed(null); 607 } 608 609 /** 610 * Method that returns the name to place above the specified column. 611 * 612 * @param col The column to return the name for. 613 * @return The name to use for the specified column. 614 */ 615 protected String getColumnName(int col) { 616 return columnNames[col]; 617 } 618 619 /** 620 * Method that resets the minimum column widths. 621 */ 622 private static void initColumnWidths(JTable table) { 623 int numColumns = table.getColumnCount(); 624 for (int i = 0; i < numColumns; ++i) { 625 // Get each column. 626 TableColumn column = table.getColumnModel().getColumn(i); 627 628 // Determine the header width. 629 int headerWidth = 0; 630 try { 631 TableCellRenderer renderer = column.getHeaderRenderer(); 632 if (renderer == null) 633 renderer = table.getTableHeader().getDefaultRenderer(); 634 635 Component comp = renderer. 636 getTableCellRendererComponent( 637 null, column.getHeaderValue(), 638 false, false, 0, 0); 639 headerWidth = comp.getPreferredSize().width; 640 } catch (NullPointerException e) { 641 e.printStackTrace(); 642 } 643 644 // Set the minimum width to the larger of the header width 645 // and 150 pixels. 646 column.setMinWidth(Math.max(150, headerWidth) + 10); 647 } 648 } 649 650 /** 651 * Method called whenever the filter text is edited to change the input table filter. 652 */ 653 private void newInputFilter() { 654 RowFilter<AbstractTableModel, Object> rf; 655 // If current expression doesn't parse, don't update. 656 try { 657 rf = RowFilter.regexFilter(filterText.getText(), 0); 658 } catch (java.util.regex.PatternSyntaxException e) { 659 return; 660 } 661 inputSorter.setRowFilter(rf); 662 663 // Make sure something in the table is selected. 664 if (inputTable.getSelectedRow() < 0) { 665 ListSelectionModel selModel = inputTable.getSelectionModel(); 666 selModel.setSelectionInterval(0, 0); 667 } 668 } 669}