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-2016 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.ListModel; 040import javax.swing.ListSelectionModel; 041import javax.swing.event.ListDataEvent; 042import javax.swing.event.ListDataListener; 043import javax.swing.event.ListSelectionEvent; 044import javax.swing.event.ListSelectionListener; 045 046import org.opends.guitools.controlpanel.datamodel.SortableListModel; 047import org.opends.guitools.controlpanel.util.Utilities; 048 049/** 050 * This component displays three list (one available list and two selected 051 * lists) with some buttons to move the components of one list to the other. 052 * 053 * @param <T> the type of the objects in the list. 054 */ 055public class DoubleAddRemovePanel<T> extends JPanel 056{ 057 private static final long serialVersionUID = 6881453848780359594L; 058 private final SortableListModel<T> availableListModel; 059 private final SortableListModel<T> selectedListModel1; 060 private final SortableListModel<T> selectedListModel2; 061 private final JLabel selectedLabel1; 062 private final JLabel selectedLabel2; 063 private final JLabel availableLabel; 064 private final JButton add1; 065 private final JButton remove1; 066 private final JButton add2; 067 private final JButton remove2; 068 private final JButton addAll1; 069 private final JButton removeAll1; 070 private final JButton addAll2; 071 private final JButton removeAll2; 072 private final JScrollPane availableScroll; 073 private final JScrollPane selectedScroll1; 074 private final JScrollPane selectedScroll2; 075 private final JList availableList; 076 private final JList<T> selectedList1; 077 private final JList<T> selectedList2; 078 private final Class<T> theClass; 079 private final Collection<T> unmovableItems = new ArrayList<>(); 080 private boolean ignoreListEvents; 081 082 /** 083 * Mask used as display option. If the provided display options contain 084 * this mask, the panel will display the remove all button. 085 */ 086 private static final int DISPLAY_REMOVE_ALL = 0x001; 087 088 /** 089 * Mask used as display option. If the provided display options contain 090 * this mask, the panel will display the add all button. 091 */ 092 private static final int DISPLAY_ADD_ALL = 0x010; 093 094 /** 095 * Constructor of the double add remove panel allowing the user to provide 096 * some display options. 097 * The class is required to avoid warnings in compilation. 098 * @param displayOptions the display options. 099 * @param theClass the class of the objects in the panel. 100 */ 101 public DoubleAddRemovePanel(int displayOptions, Class<T> theClass) 102 { 103 super(new GridBagLayout()); 104 setOpaque(false); 105 this.theClass = theClass; 106 GridBagConstraints gbc = new GridBagConstraints(); 107 gbc.gridx = 0; 108 gbc.gridy = 0; 109 gbc.weightx = 0.0; 110 gbc.weighty = 0.0; 111 gbc.gridwidth = 1; 112 gbc.gridheight = 1; 113 gbc.fill = GridBagConstraints.HORIZONTAL; 114 gbc.anchor = GridBagConstraints.WEST; 115 116 availableLabel = Utilities.createDefaultLabel( 117 INFO_CTRL_PANEL_AVAILABLE_LABEL.get()); 118 add(availableLabel, gbc); 119 gbc.gridx = 2; 120 selectedLabel1 = Utilities.createDefaultLabel( 121 INFO_CTRL_PANEL_SELECTED_LABEL.get()); 122 add(selectedLabel1, gbc); 123 gbc.gridy ++; 124 125 ListDataListener listDataListener = new ListDataListener() 126 { 127 @Override 128 public void intervalRemoved(ListDataEvent ev) 129 { 130 listSelectionChanged(); 131 } 132 133 @Override 134 public void intervalAdded(ListDataEvent ev) 135 { 136 listSelectionChanged(); 137 } 138 139 @Override 140 public void contentsChanged(ListDataEvent ev) 141 { 142 listSelectionChanged(); 143 } 144 }; 145 MouseAdapter doubleClickListener = new MouseAdapter() 146 { 147 @Override 148 public void mouseClicked(MouseEvent e) { 149 if (isEnabled() && e.getClickCount() == 2) 150 { 151 if (e.getSource() == availableList) 152 { 153 if (availableList.getSelectedValue() != null) 154 { 155 addClicked(selectedListModel1); 156 } 157 } 158 else if (e.getSource() == selectedList1) 159 { 160 if (selectedList1.getSelectedValue() != null) 161 { 162 remove1Clicked(); 163 } 164 } 165 else if (e.getSource() == selectedList2 166 && selectedList2.getSelectedValue() != null) 167 { 168 remove2Clicked(); 169 } 170 } 171 } 172 }; 173 174 175 availableListModel = new SortableListModel<>(); 176 availableListModel.addListDataListener(listDataListener); 177 availableList = new JList<>(); 178 availableList.setModel(availableListModel); 179 availableList.setVisibleRowCount(15); 180 availableList.addMouseListener(doubleClickListener); 181 182 selectedListModel1 = new SortableListModel<>(); 183 selectedListModel1.addListDataListener(listDataListener); 184 selectedList1 = new JList<>(); 185 selectedList1.setModel(selectedListModel1); 186 selectedList1.setVisibleRowCount(7); 187 selectedList1.addMouseListener(doubleClickListener); 188 189 selectedListModel2 = new SortableListModel<>(); 190 selectedListModel2.addListDataListener(listDataListener); 191 selectedList2 = new JList<>(); 192 selectedList2.setModel(selectedListModel2); 193 selectedList2.setVisibleRowCount(7); 194 selectedList2.addMouseListener(doubleClickListener); 195 196 gbc.weighty = 1.0; 197 gbc.weightx = 1.0; 198 gbc.gridheight = 7; 199 displayOptions &= DISPLAY_ADD_ALL; 200 if (displayOptions != 0) 201 { 202 gbc.gridheight += 2; 203 } 204 // FIXME how can this be any different than 0? Ditto everywhere else down below 205 displayOptions &= DISPLAY_REMOVE_ALL; 206 if (displayOptions != 0) 207 { 208 gbc.gridheight += 2; 209 } 210 int listGridY = gbc.gridy; 211 gbc.gridx = 0; 212 gbc.insets.top = 5; 213 availableScroll = Utilities.createScrollPane(availableList); 214 gbc.fill = GridBagConstraints.BOTH; 215 add(availableScroll, gbc); 216 217 gbc.gridx = 1; 218 gbc.gridheight = 1; 219 gbc.weightx = 0.0; 220 gbc.weighty = 0.0; 221 gbc.fill = GridBagConstraints.HORIZONTAL; 222 add1 = Utilities.createButton(INFO_CTRL_PANEL_ADDREMOVE_ADD_BUTTON.get()); 223 add1.setOpaque(false); 224 add1.addActionListener(new ActionListener() 225 { 226 @Override 227 public void actionPerformed(ActionEvent ev) 228 { 229 addClicked(selectedListModel1); 230 } 231 }); 232 gbc.insets = new Insets(5, 5, 0, 5); 233 add(add1, gbc); 234 235 displayOptions &= DISPLAY_ADD_ALL; 236 if (displayOptions != 0) 237 { 238 addAll1 = Utilities.createButton( 239 INFO_CTRL_PANEL_ADDREMOVE_ADD_ALL_BUTTON.get()); 240 addAll1.setOpaque(false); 241 addAll1.addActionListener(new ActionListener() 242 { 243 @Override 244 public void actionPerformed(ActionEvent ev) 245 { 246 moveAll(availableListModel, selectedListModel1); 247 } 248 }); 249 gbc.gridy ++; 250 add(addAll1, gbc); 251 } 252 else 253 { 254 addAll1 = null; 255 } 256 257 remove1 = Utilities.createButton( 258 INFO_CTRL_PANEL_ADDREMOVE_REMOVE_BUTTON.get()); 259 remove1.setOpaque(false); 260 remove1.addActionListener(new ActionListener() 261 { 262 @Override 263 public void actionPerformed(ActionEvent ev) 264 { 265 remove1Clicked(); 266 } 267 }); 268 gbc.gridy ++; 269 gbc.insets.top = 10; 270 add(remove1, gbc); 271 272 displayOptions &= DISPLAY_REMOVE_ALL; 273 if (displayOptions != 0) 274 { 275 removeAll1 = Utilities.createButton( 276 INFO_CTRL_PANEL_ADDREMOVE_REMOVE_ALL_BUTTON.get()); 277 removeAll1.setOpaque(false); 278 removeAll1.addActionListener(new ActionListener() 279 { 280 @Override 281 public void actionPerformed(ActionEvent ev) 282 { 283 moveAll(selectedListModel1, availableListModel); 284 } 285 }); 286 gbc.gridy ++; 287 gbc.insets.top = 5; 288 add(removeAll1, gbc); 289 } 290 else 291 { 292 removeAll1 = null; 293 } 294 295 296 gbc.weighty = 1.0; 297 gbc.insets = new Insets(0, 0, 0, 0); 298 gbc.gridy ++; 299 gbc.gridheight = 1; 300 gbc.fill = GridBagConstraints.VERTICAL; 301 add(Box.createVerticalGlue(), gbc); 302 303 gbc.gridy += 2; 304 gbc.gridx = 1; 305 gbc.gridheight = 1; 306 gbc.weightx = 0.0; 307 gbc.weighty = 0.0; 308 gbc.fill = GridBagConstraints.HORIZONTAL; 309 add2 = Utilities.createButton(INFO_CTRL_PANEL_ADDREMOVE_ADD_BUTTON.get()); 310 add2.setOpaque(false); 311 add2.addActionListener(new ActionListener() 312 { 313 @Override 314 public void actionPerformed(ActionEvent ev) 315 { 316 addClicked(selectedListModel2); 317 } 318 }); 319 gbc.insets = new Insets(5, 5, 0, 5); 320 add(add2, gbc); 321 322 displayOptions &= DISPLAY_ADD_ALL; 323 if (displayOptions != 0) 324 { 325 addAll2 = Utilities.createButton( 326 INFO_CTRL_PANEL_ADDREMOVE_ADD_ALL_BUTTON.get()); 327 addAll2.setOpaque(false); 328 addAll2.addActionListener(new ActionListener() 329 { 330 @Override 331 public void actionPerformed(ActionEvent ev) 332 { 333 moveAll(availableListModel, selectedListModel2); 334 } 335 }); 336 gbc.gridy ++; 337 add(addAll2, gbc); 338 } 339 else 340 { 341 addAll2 = null; 342 } 343 344 remove2 = Utilities.createButton( 345 INFO_CTRL_PANEL_ADDREMOVE_REMOVE_BUTTON.get()); 346 remove2.setOpaque(false); 347 remove2.addActionListener(new ActionListener() 348 { 349 @Override 350 public void actionPerformed(ActionEvent ev) 351 { 352 remove2Clicked(); 353 } 354 }); 355 gbc.gridy ++; 356 gbc.insets.top = 10; 357 add(remove2, gbc); 358 359 displayOptions &= DISPLAY_REMOVE_ALL; 360 if (displayOptions != 0) 361 { 362 removeAll2 = Utilities.createButton( 363 INFO_CTRL_PANEL_ADDREMOVE_REMOVE_ALL_BUTTON.get()); 364 removeAll2.setOpaque(false); 365 removeAll2.addActionListener(new ActionListener() 366 { 367 @Override 368 public void actionPerformed(ActionEvent ev) 369 { 370 moveAll(selectedListModel2, availableListModel); 371 } 372 }); 373 gbc.gridy ++; 374 gbc.insets.top = 5; 375 add(removeAll2, gbc); 376 } 377 else 378 { 379 removeAll2 = null; 380 } 381 382 383 gbc.weighty = 1.0; 384 gbc.insets = new Insets(0, 0, 0, 0); 385 gbc.gridy ++; 386 gbc.gridheight = 1; 387 gbc.fill = GridBagConstraints.VERTICAL; 388 add(Box.createVerticalGlue(), gbc); 389 390 gbc.weightx = 1.0; 391 gbc.insets = new Insets(5, 0, 0, 0); 392 gbc.gridheight = 3; 393 displayOptions &= DISPLAY_ADD_ALL; 394 if (displayOptions != 0) 395 { 396 gbc.gridheight ++; 397 } 398 displayOptions &= DISPLAY_REMOVE_ALL; 399 if (displayOptions != 0) 400 { 401 gbc.gridheight ++; 402 } 403 gbc.gridy = listGridY; 404 gbc.gridx = 2; 405 gbc.fill = GridBagConstraints.BOTH; 406 selectedScroll1 = Utilities.createScrollPane(selectedList1); 407 gbc.weighty = 1.0; 408 add(selectedScroll1, gbc); 409 410 gbc.gridy += gbc.gridheight; 411 gbc.gridheight = 1; 412 gbc.weighty = 0.0; 413 gbc.insets.top = 10; 414 gbc.fill = GridBagConstraints.HORIZONTAL; 415 selectedLabel2 = Utilities.createDefaultLabel( 416 INFO_CTRL_PANEL_SELECTED_LABEL.get()); 417 add(selectedLabel2, gbc); 418 419 gbc.weightx = 1.0; 420 gbc.insets = new Insets(5, 0, 0, 0); 421 gbc.gridheight = 3; 422 displayOptions &= DISPLAY_ADD_ALL; 423 if (displayOptions != 0) 424 { 425 gbc.gridheight ++; 426 } 427 displayOptions &= DISPLAY_REMOVE_ALL; 428 if (displayOptions != 0) 429 { 430 gbc.gridheight ++; 431 } 432 gbc.gridy ++; 433 gbc.fill = GridBagConstraints.BOTH; 434 selectedScroll2 = Utilities.createScrollPane(selectedList2); 435 gbc.weighty = 1.0; 436 add(selectedScroll2, gbc); 437 438 439 selectedList1.getSelectionModel().setSelectionMode( 440 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 441 ListSelectionListener listener = new ListSelectionListener() 442 { 443 @Override 444 public void valueChanged(ListSelectionEvent ev) 445 { 446 listSelectionChanged(); 447 } 448 }; 449 selectedList1.getSelectionModel().addListSelectionListener(listener); 450 selectedList2.getSelectionModel().setSelectionMode( 451 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 452 selectedList2.getSelectionModel().addListSelectionListener(listener); 453 availableList.getSelectionModel().setSelectionMode( 454 ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); 455 availableList.getSelectionModel().addListSelectionListener(listener); 456 457 add1.setEnabled(false); 458 remove1.setEnabled(false); 459 460 add2.setEnabled(false); 461 remove2.setEnabled(false); 462 463 // Set preferred size for the scroll panes. 464 Component comp = 465 availableList.getCellRenderer().getListCellRendererComponent( 466 availableList, 467 "The cell that we want to display", 0, true, true); 468 Dimension d = new Dimension(comp.getPreferredSize().width, 469 availableScroll.getPreferredSize().height); 470 availableScroll.setPreferredSize(d); 471 d = new Dimension(comp.getPreferredSize().width, 472 selectedScroll1.getPreferredSize().height); 473 selectedScroll1.setPreferredSize(d); 474 selectedScroll2.setPreferredSize(d); 475 } 476 477 @Override 478 public void setEnabled(boolean enable) 479 { 480 super.setEnabled(enable); 481 482 selectedLabel1.setEnabled(enable); 483 selectedLabel2.setEnabled(enable); 484 availableLabel.setEnabled(enable); 485 availableList.setEnabled(enable); 486 selectedList1.setEnabled(enable); 487 selectedList2.setEnabled(enable); 488 availableScroll.setEnabled(enable); 489 selectedScroll2.setEnabled(enable); 490 selectedScroll2.setEnabled(enable); 491 492 listSelectionChanged(); 493 } 494 495 /** 496 * Returns the available label contained in the panel. 497 * @return the available label contained in the panel. 498 */ 499 public JLabel getAvailableLabel() 500 { 501 return availableLabel; 502 } 503 504 /** 505 * Returns the list of elements in the available list. 506 * @return the list of elements in the available list. 507 */ 508 public SortableListModel<T> getAvailableListModel() 509 { 510 return availableListModel; 511 } 512 513 /** 514 * Returns the first selected label contained in the panel. 515 * @return the first selected label contained in the panel. 516 */ 517 public JLabel getSelectedLabel1() 518 { 519 return selectedLabel1; 520 } 521 522 /** 523 * Returns the list of elements in the first selected list. 524 * @return the list of elements in the first selected list. 525 */ 526 public SortableListModel<T> getSelectedListModel1() 527 { 528 return selectedListModel1; 529 } 530 531 /** 532 * Returns the second selected label contained in the panel. 533 * @return the second selected label contained in the panel. 534 */ 535 public JLabel getSelectedLabel2() 536 { 537 return selectedLabel2; 538 } 539 540 /** 541 * Returns the list of elements in the second selected list. 542 * @return the list of elements in the second selected list. 543 */ 544 public SortableListModel<T> getSelectedListModel2() 545 { 546 return selectedListModel2; 547 } 548 549 private void listSelectionChanged() 550 { 551 if (ignoreListEvents) 552 { 553 return; 554 } 555 ignoreListEvents = true; 556 557 JList[] lists = {availableList, selectedList1, selectedList2}; 558 for (JList<T> list : lists) 559 { 560 for (T element : unmovableItems) 561 { 562 int[] indexes = list.getSelectedIndices(); 563 if (indexes != null) 564 { 565 for (int index : indexes) 566 { 567 // This check is necessary since the selection model might not 568 // be in sync with the list model. 569 if (selectionAndListModelAreInSync(list, element, index)) 570 { 571 list.getSelectionModel().removeIndexInterval(index, index); 572 } 573 } 574 } 575 } 576 } 577 578 ignoreListEvents = false; 579 add1.setEnabled(isEnabled(availableList, availableListModel)); 580 add2.setEnabled(add1.isEnabled()); 581 remove1.setEnabled(isEnabled(selectedList1, selectedListModel1)); 582 remove2.setEnabled(isEnabled(selectedList2, selectedListModel2)); 583 584 if (addAll1 != null) 585 { 586 addAll1.setEnabled(isEnabled(availableListModel)); 587 addAll2.setEnabled(addAll1.isEnabled()); 588 } 589 if (removeAll1 != null) 590 { 591 removeAll1.setEnabled(isEnabled(selectedListModel1)); 592 } 593 if (removeAll2 != null) 594 { 595 removeAll2.setEnabled(isEnabled(selectedListModel2)); 596 } 597 } 598 599 private boolean selectionAndListModelAreInSync(JList<T> list, T element, int index) 600 { 601 final ListModel<T> listModel = list.getModel(); 602 return index < listModel.getSize() 603 && listModel.getElementAt(index).equals(element); 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}