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