/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.mappingsplugin.ui.query; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Vector; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ListCellRenderer; import javax.swing.ListModel; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.eclipse.persistence.tools.workbench.framework.context.WorkbenchContextHolder; import org.eclipse.persistence.tools.workbench.framework.ui.view.AbstractSubjectPanel; import org.eclipse.persistence.tools.workbench.framework.uitools.SwingComponentFactory; import org.eclipse.persistence.tools.workbench.uitools.app.ListAspectAdapter; import org.eclipse.persistence.tools.workbench.uitools.app.ListValueModel; import org.eclipse.persistence.tools.workbench.uitools.app.PropertyValueModel; import org.eclipse.persistence.tools.workbench.uitools.app.ValueModel; import org.eclipse.persistence.tools.workbench.uitools.app.swing.ListModelAdapter; import org.eclipse.persistence.tools.workbench.uitools.cell.SimpleListCellRenderer; import org.eclipse.persistence.tools.workbench.utility.CollectionTools; import org.eclipse.persistence.tools.workbench.utility.events.ListChangeEvent; import org.eclipse.persistence.tools.workbench.utility.events.ListChangeListener; import org.eclipse.persistence.tools.workbench.utility.node.AbstractNodeModel; import org.eclipse.persistence.tools.workbench.utility.node.Node; public abstract class QuickViewPanel extends AbstractSubjectPanel { private QuickViewModel quickViewModel; protected QuickViewPanel(ValueModel subjectHolder, WorkbenchContextHolder contextHolder) { super(subjectHolder, contextHolder); } protected void initialize(ValueModel subjectHolder) { super.initialize(subjectHolder); this.quickViewModel = new QuickViewModel(); // Listen to the selection holder's selection in order to keep the pseudo // model's parent up to date getSubjectHolder().addPropertyChangeListener(PropertyValueModel.VALUE, buildSubjectHolderListener()); // Set the pseudo model's parent this.quickViewModel.setParentNode((AbstractNodeModel) subject()); } protected void initializeLayout() { GridBagConstraints constraints = new GridBagConstraints(); // Label JLabel quickViewLabel = buildLabel("QUICK_VIEW_LABEL"); constraints.gridx = 0; constraints.gridy = 0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.LINE_START; constraints.insets = new Insets(0, 0, 0, 0); add(quickViewLabel, constraints); // Quick View list JList list = SwingComponentFactory.buildList(buildQuickViewListAdapter()); list.setCellRenderer(new QuickViewRenderer()); list.addListSelectionListener(buildListSelectionListener()); list.addFocusListener(buildListFocusListener()); list.addMouseListener(buildMouseListener()); constraints.gridx = 0; constraints.gridy = 1; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 1; constraints.weighty = 1; constraints.fill = GridBagConstraints.BOTH; constraints.anchor = GridBagConstraints.CENTER; constraints.insets = new Insets(1, 0, 0, 0); JScrollPane scrollPane = new JScrollPane(list); scrollPane.getViewport().setPreferredSize(new Dimension(0, 0)); add(scrollPane, constraints); // Button Panel JPanel buttonPanel = new JPanel(new GridLayout(1, 2, 5, 0)); constraints.gridx = 0; constraints.gridy = 2; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 1; constraints.weighty = 0; constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.LINE_END; constraints.insets = new Insets(5, 0, 0, 0); add(buttonPanel, constraints); // Remove button JButton removeButton = buildButton("QUICK_VIEW_REMOVE_BUTTON"); removeButton.putClientProperty("list", list); removeButton.addActionListener(buildRemoveActionListener()); removeButton.setEnabled(false); buttonPanel.add(removeButton); list.putClientProperty("remove", removeButton); } /** On single click, the QuickViewItem will select the appropriate components **/ private MouseListener buildMouseListener() { return new MouseAdapter() { public void mouseClicked(MouseEvent e) { JList list = (JList) e.getComponent(); int index = list.locationToIndex(e.getPoint()); Rectangle cellBounds = list.getCellBounds(index, index); if (cellBounds != null && (cellBounds.width > 0) && (cellBounds.height > 0)) { ((QuickViewItem) list.getModel().getElementAt(index)).select(); } } }; } /** * This repaints the list when the focus is lost of gained. When there are * items selected, the color of the selection is changed, which is not done * by default. */ private FocusListener buildListFocusListener() { return new FocusListener() { public void focusGained(FocusEvent e) { ((JList) e.getSource()).repaint(); } public void focusLost(FocusEvent e) { ((JList) e.getSource()).repaint(); } }; } private ListSelectionListener buildListSelectionListener() { return new ListSelectionListener() { private void updateRemoveButtonEnablement(JButton removeButton, Object[] values) { // Remove button is enabled if all the selected items are removable boolean enabled = (values.length > 0); if (enabled) { for (int index = 0; index < values.length; index++) { QuickViewItem item = (QuickViewItem) values[index]; enabled &= item.isRemovable(); } } removeButton.setEnabled(enabled); } public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting()) return; JList list = (JList) e.getSource(); Object[] values = list.getSelectedValues(); JButton removeButton = (JButton) list.getClientProperty("remove"); updateRemoveButtonEnablement(removeButton, values); } }; } private ListModel buildQuickViewListAdapter() { return new ListModelAdapter(buildQuickViewListValueModel()); } private ListValueModel buildQuickViewListValueModel() { return new ListAspectAdapter(QuickViewModel.ITEMS_LIST, this.quickViewModel) { protected ListIterator getValueFromSubject() { return ((QuickViewModel) this.subject).items(); } protected int sizeFromSubject() { return ((QuickViewModel) this.subject).itemsSize(); } }; } private ActionListener buildRemoveActionListener() { return new ActionListener() { public void actionPerformed(ActionEvent e) { JButton button = (JButton) e.getSource(); JList list = (JList) button.getClientProperty("list"); Object[] values = list.getSelectedValues(); for (int index = 0; index < values.length; index++) { ((QuickViewItem) values[index]).remove(); } } }; } /** * Ask the subclass to create the factory that will return the sections to * be displayed in the list for the given query. */ protected abstract QuickViewSectionFactory buildSectionFactory(Node node); private PropertyChangeListener buildSubjectHolderListener () { return new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent e) { quickViewModel.setParentNode((Node) subject()); } }; } /** * Any items in the list are an instance of this interface. */ public interface QuickViewItem { public void select(); public Object getValue(); public boolean isRemovable(); public void remove(); public String displayString(); public Icon icon(); public String accessibleName(); } /** * A section regroups items of the same type into a category. */ protected interface QuickViewSection extends QuickViewItem { public ListValueModel buildItemsHolder(); } /** * This factory creates all the available sections of the list based on the * query that is been given. */ protected interface QuickViewSectionFactory { public QuickViewSection[] buildSections(); } /** * The renderer that formats a <code>IQuickViewItem</code>. */ private class QuickViewItemRenderer extends SimpleListCellRenderer { public String buildAccessibleName(Object value) { return ((QuickViewItem) value).accessibleName(); } protected Icon buildIcon(Object value) { return ((QuickViewItem) value).icon(); } protected String buildText(Object value) { return ((QuickViewItem) value).displayString(); } public Component getListCellRendererComponent(JList list, Object value, int index, boolean selected, boolean hasFocus) { QuickViewItemRenderer renderer = new QuickViewItemRenderer(); renderer.getListCellRendererComponentInternal(list, value, index, selected, hasFocus); renderer.updateBackground(list, selected); renderer.updateBorder(); return renderer; } private Component getListCellRendererComponentInternal(JList list, Object value, int index, boolean selected, boolean hasFocus) { return super.getListCellRendererComponent(list, value, index, selected, hasFocus); } private void updateBackground(JList list, boolean selected) { if (selected) { if (list.hasFocus()) { setBackground(list.getSelectionBackground()); } else { setBackground(UIManager.getColor("Panel.background")); } } else { setBackground(list.getBackground()); } } private void updateBorder() { setBorder(BorderFactory.createCompoundBorder ( BorderFactory.createMatteBorder(0, 5, 0, 0, UIManager.getColor("List.background")), getBorder()) ); } } /** * This pseudo-model aggregates lists into a single list and manages the * synchronization of the list model with the underlying lists that are * shown on screen with the sections. */ private class QuickViewModel extends AbstractNodeModel { public List items; private List sectionSynchronizers; public static final String ITEMS_LIST = "items"; protected final void checkParent(Node parent) { // The parent is set/unset dynamically } private void disengageListeners() { for (Iterator iter = this.sectionSynchronizers.iterator(); iter.hasNext();) { SectionSynchronizer synchronizer = (SectionSynchronizer) iter.next(); synchronizer.disengageListeners(); } } public final String displayString() { return null; } protected void initialize() { super.initialize(); this.items = new Vector(); this.sectionSynchronizers = new Vector(); } public ListIterator items() { return this.items.listIterator(); } public int itemsSize() { return this.items.size(); } public final void setParentNode(Node parentNode) { if (getParent() != null) { disengageListeners(); removeItemsFromList(0, items.size(), items, ITEMS_LIST); sectionSynchronizers.clear(); items.clear(); } super.setParent(parentNode); if (parentNode != null) { updateItems(); } } private void updateItems() { QuickViewSection[] sections = buildSectionFactory(getParent()).buildSections(); for (int index = 0; index < sections.length; index++) { // Adds the section as an item QuickViewSection section = sections[index]; items.add(section); // Creates a synchronizer that will keep this model in sync with // the items from the section SectionSynchronizer synchronizer = new SectionSynchronizer(section, items.size()); sectionSynchronizers.add(synchronizer); // Retrieves all the children and adds them to the collection CollectionTools.addAll(items, synchronizer.items()); } fireItemsAdded(ITEMS_LIST, 0, items); } /** * This <code>SectionSynchronizer</code> keeps the pseudo-model in sync * with its section it represents. Basically, upon changes in the * underlying model's list, the event is updated and forwarded to the * pseudo-model. */ private class SectionSynchronizer { private int index; private ListValueModel itemsHolder; private ListChangeListener listener; SectionSynchronizer(QuickViewSection section, int index) { super(); initialize(section, index); } public void disengageListeners() { itemsHolder.removeListChangeListener(ValueModel.VALUE, listener); } private void initialize(QuickViewSection section, int index) { this.index = index; listener = new ListChangeHandler(); itemsHolder = section.buildItemsHolder(); itemsHolder.addListChangeListener(ValueModel.VALUE, listener); } Iterator items() { return (Iterator) itemsHolder.getValue(); } int itemsSize() { return this.itemsHolder.size(); } private void updateSectionsIndex(SectionSynchronizer previousSynchronizer) { int index = 1 + sectionSynchronizers.indexOf(previousSynchronizer); for (; index < sectionSynchronizers.size(); index++) { SectionSynchronizer synchronizer = (SectionSynchronizer) sectionSynchronizers.get(index); synchronizer.index = previousSynchronizer.itemsSize() + previousSynchronizer.index + 1; previousSynchronizer = synchronizer; } } private class ListChangeHandler implements ListChangeListener { public void itemsAdded(ListChangeEvent e) { // Notify the model new items have been added QuickViewModel.this.addItemsToList(index + e.getIndex(), CollectionTools.list(e.items()), items, ITEMS_LIST); // Update the index of all the sections after this one updateSectionsIndex(SectionSynchronizer.this); } public void itemsRemoved(ListChangeEvent e) { int size = itemsHolder.size(); // Remove the deleted items QuickViewModel.this.removeItemsFromList(index + e.getIndex(), e.size(), items, ITEMS_LIST); // Update the index of all the sections after this one updateSectionsIndex(SectionSynchronizer.this); } public void itemsReplaced(ListChangeEvent e) { QuickViewModel.this.removeItemsFromList(index + e.getIndex(), e.size(), items, ITEMS_LIST); QuickViewModel.this.addItemsToList(index + e.getIndex(), CollectionTools.list(e.items()), items, ITEMS_LIST); } public void listChanged(ListChangeEvent e) { // Nothing to do } } } } /** * The main <code>ListCellRenderer</code> which asks a sub cell renderer to * format the items of the list. */ private class QuickViewRenderer implements ListCellRenderer { private ListCellRenderer[] cachedRenderers; private static final int ITEM_RENDERER = 0; private static final int SECTION_RENDERER = 1; QuickViewRenderer() { super(); initialize(); } public Component getListCellRendererComponent(JList list, Object value, int index, boolean selected, boolean hasFocus) { ListCellRenderer renderer = retrieveCellRenderer(value); return renderer.getListCellRendererComponent(list, value, index, selected, hasFocus); } private void initialize() { this.cachedRenderers = new ListCellRenderer[3]; this.cachedRenderers[ITEM_RENDERER] = new QuickViewItemRenderer(); this.cachedRenderers[SECTION_RENDERER] = new QuickViewSectionRenderer(); } private ListCellRenderer retrieveCellRenderer(Object value) { if (value instanceof QuickViewSection) { return this.cachedRenderers[SECTION_RENDERER]; } return this.cachedRenderers[ITEM_RENDERER]; } } /** * The renderer that formats a <code>IQuickViewSection</code>. */ private class QuickViewSectionRenderer implements ListCellRenderer { private Color getBackground(JList list, boolean selected, boolean hasFocus) { if (selected) { if (hasFocus) { return list.getSelectionBackground(); } else { return UIManager.getColor("Panel.background"); } } else { return list.getBackground(); } } private Border getBorder(JList list, int index, boolean selected, boolean hasFocus) { Border border; if (selected) { if (hasFocus) { border = UIManager.getBorder("List.focusCellHighlightBorder"); } // Background border with the underline else { border = BorderFactory.createCompoundBorder ( BorderFactory.createMatteBorder(0, 0, 1, 0, UIManager.getColor("Panel.background").darker()), BorderFactory.createMatteBorder(1, 1, 0, 1, UIManager.getColor("Panel.background")) ); } } else { // Only the underline border = BorderFactory.createCompoundBorder ( BorderFactory.createMatteBorder(0, 0, 1, 0, UIManager.getColor("Panel.background")), BorderFactory.createEmptyBorder(1, 1, 0, 1) ); } return updateBorder(border, index); } private Color getForeground(JList list, boolean selected, boolean hasFocus) { if (selected) { if (hasFocus) { return list.getSelectionForeground(); } else { return UIManager.getColor("Panel.foreground"); } } else { return list.getForeground(); } } public Component getListCellRendererComponent(JList list, Object value, int index, boolean selected, boolean hasFocus) { JLabel label = new JLabel(); updateUI(label, (QuickViewSection) value, list, index, selected, hasFocus); return label; } private Border updateBorder(Border border, int index) { // Add space before the IQuickViewItems from the previous // section and the new section if (index > 0) { return BorderFactory.createCompoundBorder ( BorderFactory.createMatteBorder(5, 0, 0, 0, UIManager.getColor("List.background")), border ); } return border; } private void updateUI(JLabel label, QuickViewSection section, JList list, int index, boolean selected, boolean hasFocus) { Color background = getBackground(list, selected, hasFocus); Color foreground = getForeground(list, selected, hasFocus); Border border = getBorder(list, index, selected, hasFocus); label.setOpaque(true); label.setBackground(background); label.setBorder(border); label.setComponentOrientation(list.getComponentOrientation()); label.setEnabled(list.isEnabled()); label.setForeground(foreground); label.setIcon(section.icon()); label.setText(section.displayString()); label.getAccessibleContext().setAccessibleName(section.accessibleName()); Font listFont = list.getFont(); label.setFont(new Font(listFont.getName(), Font.BOLD, listFont.getSize())); } } }