001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008-2010 Sun Microsystems, Inc. 025 * Portions Copyright 2015 ForgeRock AS 026 */ 027package org.opends.guitools.controlpanel.ui.components; 028 029import static org.opends.messages.AdminToolMessages.*; 030 031import java.awt.Component; 032import java.awt.Dimension; 033import java.awt.GridBagConstraints; 034import java.awt.GridBagLayout; 035import java.awt.Insets; 036import java.awt.event.ActionEvent; 037import java.awt.event.ActionListener; 038import java.awt.event.MouseAdapter; 039import java.awt.event.MouseEvent; 040import java.util.ArrayList; 041import java.util.Collection; 042 043import javax.swing.Box; 044import javax.swing.JButton; 045import javax.swing.JLabel; 046import javax.swing.JList; 047import javax.swing.JPanel; 048import javax.swing.JScrollPane; 049import javax.swing.ListSelectionModel; 050import javax.swing.event.ListDataEvent; 051import javax.swing.event.ListDataListener; 052import javax.swing.event.ListSelectionEvent; 053import javax.swing.event.ListSelectionListener; 054 055import org.opends.guitools.controlpanel.datamodel.SortableListModel; 056import org.opends.guitools.controlpanel.util.Utilities; 057 058/** 059 * This component displays three list (one available list and two selected 060 * lists) with some buttons to move the components of one list to the other. 061 * 062 * @param <T> the type of the objects in the list. 063 */ 064public class DoubleAddRemovePanel<T> extends JPanel 065{ 066 private static final long serialVersionUID = 6881453848780359594L; 067 private SortableListModel<T> availableListModel; 068 private SortableListModel<T> selectedListModel1; 069 private SortableListModel<T> selectedListModel2; 070 private JLabel selectedLabel1; 071 private JLabel selectedLabel2; 072 private JLabel availableLabel; 073 private JButton add1; 074 private JButton remove1; 075 private JButton add2; 076 private JButton remove2; 077 private JButton addAll1; 078 private JButton removeAll1; 079 private JButton addAll2; 080 private JButton removeAll2; 081 private JScrollPane availableScroll; 082 private JScrollPane selectedScroll1; 083 private JScrollPane selectedScroll2; 084 private JList availableList; 085 private JList<T> selectedList1; 086 private JList<T> selectedList2; 087 private Class<T> theClass; 088 private Collection<T> unmovableItems = new ArrayList<>(); 089 private boolean ignoreListEvents; 090 091 /** 092 * Mask used as display option. If the provided display options contain 093 * this mask, the panel will display the remove all button. 094 */ 095 public static final int DISPLAY_REMOVE_ALL = 0x001; 096 097 /** 098 * Mask used as display option. If the provided display options contain 099 * this mask, the panel will display the add all button. 100 */ 101 public static final int DISPLAY_ADD_ALL = 0x010; 102 103 104 /** 105 * Constructor of the default double add remove panel (including 'Add All' and 106 * 'Remove All' buttons). 107 * The class is required to avoid warnings in compilation. 108 * @param theClass the class of the objects in the panel. 109 */ 110 public DoubleAddRemovePanel(Class<T> theClass) 111 { 112 this(DISPLAY_REMOVE_ALL | DISPLAY_ADD_ALL, theClass); 113 } 114 115 /** 116 * Constructor of the double add remove panel allowing the user to provide 117 * some display options. 118 * The class is required to avoid warnings in compilation. 119 * @param displayOptions the display options. 120 * @param theClass the class of the objects in the panel. 121 */ 122 public DoubleAddRemovePanel(int displayOptions, Class<T> theClass) 123 { 124 super(new GridBagLayout()); 125 setOpaque(false); 126 this.theClass = theClass; 127 GridBagConstraints gbc = new GridBagConstraints(); 128 gbc.gridx = 0; 129 gbc.gridy = 0; 130 gbc.weightx = 0.0; 131 gbc.weighty = 0.0; 132 gbc.gridwidth = 1; 133 gbc.gridheight = 1; 134 gbc.fill = GridBagConstraints.HORIZONTAL; 135 gbc.anchor = GridBagConstraints.WEST; 136 137 availableLabel = Utilities.createDefaultLabel( 138 INFO_CTRL_PANEL_AVAILABLE_LABEL.get()); 139 add(availableLabel, gbc); 140 gbc.gridx = 2; 141 selectedLabel1 = Utilities.createDefaultLabel( 142 INFO_CTRL_PANEL_SELECTED_LABEL.get()); 143 add(selectedLabel1, gbc); 144 gbc.gridy ++; 145 146 ListDataListener listDataListener = new ListDataListener() 147 { 148 /** {@inheritDoc} */ 149 public void intervalRemoved(ListDataEvent ev) 150 { 151 listSelectionChanged(); 152 } 153 154 /** {@inheritDoc} */ 155 public void intervalAdded(ListDataEvent ev) 156 { 157 listSelectionChanged(); 158 } 159 160 /** {@inheritDoc} */ 161 public void contentsChanged(ListDataEvent ev) 162 { 163 listSelectionChanged(); 164 } 165 }; 166 MouseAdapter doubleClickListener = new MouseAdapter() 167 { 168 /** {@inheritDoc} */ 169 public void mouseClicked(MouseEvent e) { 170 if (isEnabled() && e.getClickCount() == 2) 171 { 172 if (e.getSource() == availableList) 173 { 174 if (availableList.getSelectedValue() != null) 175 { 176 addClicked(selectedListModel1); 177 } 178 } 179 else if (e.getSource() == selectedList1) 180 { 181 if (selectedList1.getSelectedValue() != null) 182 { 183 remove1Clicked(); 184 } 185 } 186 else if (e.getSource() == selectedList2 187 && selectedList2.getSelectedValue() != null) 188 { 189 remove2Clicked(); 190 } 191 } 192 } 193 }; 194 195 196 availableListModel = new SortableListModel<>(); 197 availableListModel.addListDataListener(listDataListener); 198 availableList = new JList<>(); 199 availableList.setModel(availableListModel); 200 availableList.setVisibleRowCount(15); 201 availableList.addMouseListener(doubleClickListener); 202 203 selectedListModel1 = new SortableListModel<>(); 204 selectedListModel1.addListDataListener(listDataListener); 205 selectedList1 = new JList<>(); 206 selectedList1.setModel(selectedListModel1); 207 selectedList1.setVisibleRowCount(7); 208 selectedList1.addMouseListener(doubleClickListener); 209 210 selectedListModel2 = new SortableListModel<>(); 211 selectedListModel2.addListDataListener(listDataListener); 212 selectedList2 = new JList<>(); 213 selectedList2.setModel(selectedListModel2); 214 selectedList2.setVisibleRowCount(7); 215 selectedList2.addMouseListener(doubleClickListener); 216 217 gbc.weighty = 1.0; 218 gbc.weightx = 1.0; 219 gbc.gridheight = 7; 220 displayOptions &= DISPLAY_ADD_ALL; 221 if (displayOptions != 0) 222 { 223 gbc.gridheight += 2; 224 } 225 // FIXME how can this be any different than 0? Ditto everywhere else down below 226 displayOptions &= DISPLAY_REMOVE_ALL; 227 if (displayOptions != 0) 228 { 229 gbc.gridheight += 2; 230 } 231 int listGridY = gbc.gridy; 232 gbc.gridx = 0; 233 gbc.insets.top = 5; 234 availableScroll = Utilities.createScrollPane(availableList); 235 gbc.fill = GridBagConstraints.BOTH; 236 add(availableScroll, gbc); 237 238 gbc.gridx = 1; 239 gbc.gridheight = 1; 240 gbc.weightx = 0.0; 241 gbc.weighty = 0.0; 242 gbc.fill = GridBagConstraints.HORIZONTAL; 243 add1 = Utilities.createButton(INFO_CTRL_PANEL_ADDREMOVE_ADD_BUTTON.get()); 244 add1.setOpaque(false); 245 add1.addActionListener(new ActionListener() 246 { 247 /** {@inheritDoc} */ 248 public void actionPerformed(ActionEvent ev) 249 { 250 addClicked(selectedListModel1); 251 } 252 }); 253 gbc.insets = new Insets(5, 5, 0, 5); 254 add(add1, gbc); 255 256 displayOptions &= DISPLAY_ADD_ALL; 257 if (displayOptions != 0) 258 { 259 addAll1 = Utilities.createButton( 260 INFO_CTRL_PANEL_ADDREMOVE_ADD_ALL_BUTTON.get()); 261 addAll1.setOpaque(false); 262 addAll1.addActionListener(new ActionListener() 263 { 264 public void actionPerformed(ActionEvent ev) 265 { 266 moveAll(availableListModel, selectedListModel1); 267 } 268 }); 269 gbc.gridy ++; 270 add(addAll1, gbc); 271 } 272 273 remove1 = Utilities.createButton( 274 INFO_CTRL_PANEL_ADDREMOVE_REMOVE_BUTTON.get()); 275 remove1.setOpaque(false); 276 remove1.addActionListener(new ActionListener() 277 { 278 public void actionPerformed(ActionEvent ev) 279 { 280 remove1Clicked(); 281 } 282 }); 283 gbc.gridy ++; 284 gbc.insets.top = 10; 285 add(remove1, gbc); 286 287 displayOptions &= DISPLAY_REMOVE_ALL; 288 if (displayOptions != 0) 289 { 290 removeAll1 = Utilities.createButton( 291 INFO_CTRL_PANEL_ADDREMOVE_REMOVE_ALL_BUTTON.get()); 292 removeAll1.setOpaque(false); 293 removeAll1.addActionListener(new ActionListener() 294 { 295 /** {@inheritDoc} */ 296 public void actionPerformed(ActionEvent ev) 297 { 298 moveAll(selectedListModel1, availableListModel); 299 } 300 }); 301 gbc.gridy ++; 302 gbc.insets.top = 5; 303 add(removeAll1, gbc); 304 } 305 306 307 gbc.weighty = 1.0; 308 gbc.insets = new Insets(0, 0, 0, 0); 309 gbc.gridy ++; 310 gbc.gridheight = 1; 311 gbc.fill = GridBagConstraints.VERTICAL; 312 add(Box.createVerticalGlue(), gbc); 313 314 gbc.gridy += 2; 315 gbc.gridx = 1; 316 gbc.gridheight = 1; 317 gbc.weightx = 0.0; 318 gbc.weighty = 0.0; 319 gbc.fill = GridBagConstraints.HORIZONTAL; 320 add2 = Utilities.createButton(INFO_CTRL_PANEL_ADDREMOVE_ADD_BUTTON.get()); 321 add2.setOpaque(false); 322 add2.addActionListener(new ActionListener() 323 { 324 /** {@inheritDoc} */ 325 public void actionPerformed(ActionEvent ev) 326 { 327 addClicked(selectedListModel2); 328 } 329 }); 330 gbc.insets = new Insets(5, 5, 0, 5); 331 add(add2, gbc); 332 333 displayOptions &= DISPLAY_ADD_ALL; 334 if (displayOptions != 0) 335 { 336 addAll2 = Utilities.createButton( 337 INFO_CTRL_PANEL_ADDREMOVE_ADD_ALL_BUTTON.get()); 338 addAll2.setOpaque(false); 339 addAll2.addActionListener(new ActionListener() 340 { 341 /** {@inheritDoc} */ 342 public void actionPerformed(ActionEvent ev) 343 { 344 moveAll(availableListModel, selectedListModel2); 345 } 346 }); 347 gbc.gridy ++; 348 add(addAll2, gbc); 349 } 350 351 remove2 = Utilities.createButton( 352 INFO_CTRL_PANEL_ADDREMOVE_REMOVE_BUTTON.get()); 353 remove2.setOpaque(false); 354 remove2.addActionListener(new ActionListener() 355 { 356 /** {@inheritDoc} */ 357 public void actionPerformed(ActionEvent ev) 358 { 359 remove2Clicked(); 360 } 361 }); 362 gbc.gridy ++; 363 gbc.insets.top = 10; 364 add(remove2, gbc); 365 366 displayOptions &= DISPLAY_REMOVE_ALL; 367 if (displayOptions != 0) 368 { 369 removeAll2 = Utilities.createButton( 370 INFO_CTRL_PANEL_ADDREMOVE_REMOVE_ALL_BUTTON.get()); 371 removeAll2.setOpaque(false); 372 removeAll2.addActionListener(new ActionListener() 373 { 374 /** {@inheritDoc} */ 375 public void actionPerformed(ActionEvent ev) 376 { 377 moveAll(selectedListModel2, availableListModel); 378 } 379 }); 380 gbc.gridy ++; 381 gbc.insets.top = 5; 382 add(removeAll2, gbc); 383 } 384 385 386 gbc.weighty = 1.0; 387 gbc.insets = new Insets(0, 0, 0, 0); 388 gbc.gridy ++; 389 gbc.gridheight = 1; 390 gbc.fill = GridBagConstraints.VERTICAL; 391 add(Box.createVerticalGlue(), gbc); 392 393 gbc.weightx = 1.0; 394 gbc.insets = new Insets(5, 0, 0, 0); 395 gbc.gridheight = 3; 396 displayOptions &= DISPLAY_ADD_ALL; 397 if (displayOptions != 0) 398 { 399 gbc.gridheight ++; 400 } 401 displayOptions &= DISPLAY_REMOVE_ALL; 402 if (displayOptions != 0) 403 { 404 gbc.gridheight ++; 405 } 406 gbc.gridy = listGridY; 407 gbc.gridx = 2; 408 gbc.fill = GridBagConstraints.BOTH; 409 selectedScroll1 = Utilities.createScrollPane(selectedList1); 410 gbc.weighty = 1.0; 411 add(selectedScroll1, gbc); 412 413 gbc.gridy += gbc.gridheight; 414 gbc.gridheight = 1; 415 gbc.weighty = 0.0; 416 gbc.insets.top = 10; 417 gbc.fill = GridBagConstraints.HORIZONTAL; 418 selectedLabel2 = Utilities.createDefaultLabel( 419 INFO_CTRL_PANEL_SELECTED_LABEL.get()); 420 add(selectedLabel2, gbc); 421 422 gbc.weightx = 1.0; 423 gbc.insets = new Insets(5, 0, 0, 0); 424 gbc.gridheight = 3; 425 displayOptions &= DISPLAY_ADD_ALL; 426 if (displayOptions != 0) 427 { 428 gbc.gridheight ++; 429 } 430 displayOptions &= DISPLAY_REMOVE_ALL; 431 if (displayOptions != 0) 432 { 433 gbc.gridheight ++; 434 } 435 gbc.gridy ++; 436 gbc.fill = GridBagConstraints.BOTH; 437 selectedScroll2 = Utilities.createScrollPane(selectedList2); 438 gbc.weighty = 1.0; 439 add(selectedScroll2, gbc); 440 441 442 selectedList1.getSelectionModel().setSelectionMode( 443 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 444 ListSelectionListener listener = new ListSelectionListener() 445 { 446 public void valueChanged(ListSelectionEvent ev) 447 { 448 listSelectionChanged(); 449 } 450 }; 451 selectedList1.getSelectionModel().addListSelectionListener(listener); 452 selectedList2.getSelectionModel().setSelectionMode( 453 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 454 selectedList2.getSelectionModel().addListSelectionListener(listener); 455 availableList.getSelectionModel().setSelectionMode( 456 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 457 availableList.getSelectionModel().addListSelectionListener(listener); 458 459 add1.setEnabled(false); 460 remove1.setEnabled(false); 461 462 add2.setEnabled(false); 463 remove2.setEnabled(false); 464 465 // Set preferred size for the scroll panes. 466 Component comp = 467 availableList.getCellRenderer().getListCellRendererComponent( 468 availableList, 469 "The cell that we want to display", 0, true, true); 470 Dimension d = new Dimension(comp.getPreferredSize().width, 471 availableScroll.getPreferredSize().height); 472 availableScroll.setPreferredSize(d); 473 d = new Dimension(comp.getPreferredSize().width, 474 selectedScroll1.getPreferredSize().height); 475 selectedScroll1.setPreferredSize(d); 476 selectedScroll2.setPreferredSize(d); 477 } 478 479 /** 480 * Enables the state of the components in the panel. 481 * @param enable whether to enable the components in the panel or not. 482 */ 483 public void setEnabled(boolean enable) 484 { 485 super.setEnabled(enable); 486 487 selectedLabel1.setEnabled(enable); 488 selectedLabel2.setEnabled(enable); 489 availableLabel.setEnabled(enable); 490 availableList.setEnabled(enable); 491 selectedList1.setEnabled(enable); 492 selectedList2.setEnabled(enable); 493 availableScroll.setEnabled(enable); 494 selectedScroll2.setEnabled(enable); 495 selectedScroll2.setEnabled(enable); 496 497 listSelectionChanged(); 498 } 499 500 /** 501 * Returns the available label contained in the panel. 502 * @return the available label contained in the panel. 503 */ 504 public JLabel getAvailableLabel() 505 { 506 return availableLabel; 507 } 508 509 /** 510 * Returns the list of elements in the available list. 511 * @return the list of elements in the available list. 512 */ 513 public SortableListModel<T> getAvailableListModel() 514 { 515 return availableListModel; 516 } 517 518 /** 519 * Returns the first selected label contained in the panel. 520 * @return the first selected label contained in the panel. 521 */ 522 public JLabel getSelectedLabel1() 523 { 524 return selectedLabel1; 525 } 526 527 /** 528 * Returns the list of elements in the first selected list. 529 * @return the list of elements in the first selected list. 530 */ 531 public SortableListModel<T> getSelectedListModel1() 532 { 533 return selectedListModel1; 534 } 535 536 /** 537 * Returns the second selected label contained in the panel. 538 * @return the second selected label contained in the panel. 539 */ 540 public JLabel getSelectedLabel2() 541 { 542 return selectedLabel2; 543 } 544 545 /** 546 * Returns the list of elements in the second selected list. 547 * @return the list of elements in the second selected list. 548 */ 549 public SortableListModel<T> getSelectedListModel2() 550 { 551 return selectedListModel2; 552 } 553 554 private void listSelectionChanged() 555 { 556 if (ignoreListEvents) 557 { 558 return; 559 } 560 ignoreListEvents = true; 561 562 JList[] lists = {availableList, selectedList1, selectedList2}; 563 for (JList<T> list : lists) 564 { 565 for (T element : unmovableItems) 566 { 567 int[] indexes = list.getSelectedIndices(); 568 if (indexes != null) 569 { 570 for (int i=0; i<indexes.length; i++) 571 { 572 // This check is necessary since the selection model might not 573 // be in sync with the list model. 574 if (indexes[i] < list.getModel().getSize() && 575 list.getModel().getElementAt(indexes[i]).equals(element)) 576 { 577 list.getSelectionModel().removeIndexInterval(indexes[i], 578 indexes[i]); 579 } 580 } 581 } 582 } 583 } 584 585 ignoreListEvents = false; 586 add1.setEnabled(isEnabled(availableList, availableListModel)); 587 add2.setEnabled(add1.isEnabled()); 588 remove1.setEnabled(isEnabled(selectedList1, selectedListModel1)); 589 remove2.setEnabled(isEnabled(selectedList2, selectedListModel2)); 590 591 if (addAll1 != null) 592 { 593 addAll1.setEnabled(isEnabled(availableListModel)); 594 addAll2.setEnabled(addAll1.isEnabled()); 595 } 596 if (removeAll1 != null) 597 { 598 removeAll1.setEnabled(isEnabled(selectedListModel1)); 599 } 600 if (removeAll2 != null) 601 { 602 removeAll2.setEnabled(isEnabled(selectedListModel2)); 603 } 604 } 605 606 private boolean isEnabled(JList<T> list, SortableListModel<T> model) 607 { 608 int index = list.getSelectedIndex(); 609 return index != -1 && index < model.getSize() && isEnabled(); 610 } 611 612 private boolean isEnabled(SortableListModel<T> model) 613 { 614 boolean onlyUnmovable = unmovableItems.containsAll(model.getData()); 615 return model.getSize() > 0 && isEnabled() && !onlyUnmovable; 616 } 617 618 /** 619 * Returns the available list. 620 * @return the available list. 621 */ 622 public JList getAvailableList() 623 { 624 return availableList; 625 } 626 627 /** 628 * Returns the first selected list. 629 * @return the first selected list. 630 */ 631 public JList<T> getSelectedList1() 632 { 633 return selectedList1; 634 } 635 636 /** 637 * Returns the second selected list. 638 * @return the second selected list. 639 */ 640 public JList<T> getSelectedList2() 641 { 642 return selectedList2; 643 } 644 645 private void addClicked(SortableListModel<T> selectedListModel) 646 { 647 for (Object selectedObject : availableList.getSelectedValuesList()) 648 { 649 T value = DoubleAddRemovePanel.this.theClass.cast(selectedObject); 650 selectedListModel.add(value); 651 availableListModel.remove(value); 652 } 653 selectedListModel.fireContentsChanged(selectedListModel, 0, selectedListModel.getSize()); 654 availableListModel.fireContentsChanged(availableListModel, 0, availableListModel.getSize()); 655 } 656 657 private void remove1Clicked() 658 { 659 removeClicked(selectedListModel1, selectedList1); 660 } 661 662 private void remove2Clicked() 663 { 664 removeClicked(selectedListModel2, selectedList2); 665 } 666 667 private void removeClicked(SortableListModel<T> selectedListModel, JList<T> selectedList) 668 { 669 for (T value : selectedList.getSelectedValuesList()) 670 { 671 availableListModel.add(value); 672 selectedListModel.remove(value); 673 } 674 selectedListModel.fireContentsChanged(selectedListModel, 0, selectedListModel.getSize()); 675 availableListModel.fireContentsChanged(availableListModel, 0, availableListModel.getSize()); 676 } 677 678 /** 679 * Sets the list of items that cannot be moved from one list to the others. 680 * @param unmovableItems the list of items that cannot be moved from one 681 * list to the others. 682 */ 683 public void setUnmovableItems(Collection<T> unmovableItems) 684 { 685 this.unmovableItems.clear(); 686 this.unmovableItems.addAll(unmovableItems); 687 } 688 689 private void moveAll(SortableListModel<T> fromModel, 690 SortableListModel<T> toModel) 691 { 692 Collection<T> toKeep = fromModel.getData(); 693 toKeep.retainAll(unmovableItems); 694 Collection<T> toMove = fromModel.getData(); 695 toMove.removeAll(unmovableItems); 696 toModel.addAll(toMove); 697 fromModel.clear(); 698 fromModel.addAll(toKeep); 699 fromModel.fireContentsChanged(selectedListModel1, 0, 700 selectedListModel1.getSize()); 701 toModel.fireContentsChanged(availableListModel, 0, 702 availableListModel.getSize()); 703 } 704}