/******************************************************************************* * 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.relational; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; 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.MouseEvent; import java.util.EventObject; import java.util.Stack; import javax.swing.AbstractCellEditor; import javax.swing.JCheckBox; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTree; import javax.swing.UIManager; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreeCellEditor; import javax.swing.tree.TreeCellRenderer; import javax.swing.tree.TreePath; import org.eclipse.persistence.tools.workbench.framework.context.WorkbenchContext; import org.eclipse.persistence.tools.workbench.framework.resources.ResourceRepository; import org.eclipse.persistence.tools.workbench.framework.uitools.SwingComponentFactory; import org.eclipse.persistence.tools.workbench.mappingsmodel.query.MWQueryable; import org.eclipse.persistence.tools.workbench.mappingsmodel.query.relational.MWQueryableArgumentElement; /** * QueryableTree is used on the QueryableEditDialog. The renderer and editor for each node * contains a panel with a label(name and icon of the queryable) and check box for allowsNull. * The tree maintains selection. When a node is selected, all of it's parent nodes are selected as well */ final class QueryableTree extends SwingComponentFactory.AccessibleTree { private WorkbenchContext context; QueryableTree(QueryableTreeModel newModel, WorkbenchContext context) { super(newModel); initialize(context); } private FocusListener buildFocusListener() { return new FocusListener() { public void focusGained(FocusEvent e) { TreePath[] paths = getSelectionPaths(); if (paths == null) return; for (int index = 0; index < paths.length; index++) { Rectangle pathBounds = getUI().getPathBounds(QueryableTree.this, paths[index]); repaint(pathBounds.x, pathBounds.y, pathBounds.width, pathBounds.height); } } public void focusLost(FocusEvent e) { focusGained(e); } }; } private TreeSelectionListener buildTreeSelectionListener() { return new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { TreePath treePath = e.getNewLeadSelectionPath(); if (treePath != null) { TreePath[] paths = new TreePath[treePath.getPathCount() - 1]; int count = paths.length - 1; paths[count--] = treePath; treePath = treePath.getParentPath(); Object rootNode = getModel().getRoot(); while (rootNode != treePath.getLastPathComponent()) { paths[count--] = treePath; treePath = treePath.getParentPath(); } setSelectionPaths(paths); } } }; } /** * To fix the editing when there is more than one node selected, the order * of the nodes had to be reversed, see buildTreeSelectionListener(). But * that caused this method to return the wrong node, so here we simply return * the good one. */ public TreePath getSelectionPath() { TreePath[] paths = getSelectionPaths(); if ((paths == null) || (paths.length == 0)) return null; return paths[paths.length - 1]; } private void initialize(WorkbenchContext context) { this.context = context; setRowHeight(0); setEditable(true); setAutoscrolls(true); setRootVisible(false); setShowsRootHandles(true); addFocusListener(buildFocusListener()); QueryableTreeCell treeCell = new QueryableTreeCell(); setCellEditor(treeCell); setCellRenderer(treeCell); addTreeSelectionListener(buildTreeSelectionListener()); } /** * Processes <code>MouseEvent</code>s occurring on this component by * dispatching them to any registered <code>MouseListener</code> objects. * * @param e The <code>MouseEvent</code> */ protected void processMouseEvent(MouseEvent e) { // Make sure the editing is stopped before the new MousePressed is // dispatched to a new editor if (isEditing() && (e.getButton() == MouseEvent.BUTTON1)) { TreePath path = getEditingPath(); Rectangle pathBounds = getPathBounds(path); if (!pathBounds.contains(e.getX(), e.getY())) { stopEditing(); } } super.processMouseEvent(e); } private ResourceRepository resourceRepository() { return this.context.getApplicationContext().getResourceRepository(); } //this is used to populate the queryKey tree //it sets up which nodes are selected and whether allowsNull is true/false on the nodes void setSelectedQueryableArgumentElement(MWQueryableArgumentElement element) { if (element == null) return; Stack joinedElementsStack = new Stack(); while (element != null) { joinedElementsStack.add(element); element = element.getJoinedQueryableElement(); } QueryableTreeModel treeModel = (QueryableTreeModel) getModel(); MWQueryableArgumentElement nextElement = (MWQueryableArgumentElement) joinedElementsStack.pop(); if (nextElement.getQueryable() == null) return; int index = treeModel.buildQueryableObjects().indexOf(nextElement.getQueryable()); //bug #5231484 -invalid queryable object in the model, don't select anything in the tree if (index == -1) { return; } int count = 0; int selectionIndex = index; QueryableTreeNode node = (QueryableTreeNode) treeModel.getChild(treeModel.getRoot(), index); node.setAllowsNull(nextElement.isAllowsNull()); expandRow(index); MWQueryable queryable = nextElement.getQueryable(); while (!joinedElementsStack.empty()) { count++; nextElement = (MWQueryableArgumentElement) joinedElementsStack.pop(); if (nextElement.getQueryable() == null || !treeModel.getQueryableFilter().accept(nextElement.getQueryable())) return; index = queryable.subQueryableElements(treeModel.getQueryableFilter()).indexOf(nextElement.getQueryable()); node = (QueryableTreeNode) treeModel.getChild(node , index); node.setAllowsNull(nextElement.isAllowsNull()); selectionIndex = selectionIndex + index + 1; expandRow(selectionIndex); queryable = nextElement.getQueryable(); } setSelectionRow(selectionIndex); } /** * This panel is used as the renderer and editor for the QueryableTree. */ private final class QueryableNodeCheckBoxPanel extends JPanel { private JCheckBox allowsNullCheckBox; private DefaultTreeCellRenderer queryableLabel; private QueryableTreeNode queryableNode; protected QueryableNodeCheckBoxPanel() { super(new GridBagLayout()); initialize(); } protected ActionListener buildAllowsNullAction() { return new ActionListener() { public void actionPerformed(ActionEvent e) { queryableNode.setAllowsNull(allowsNullCheckBox.isSelected()); stopEditing(); } }; } private DefaultTreeCellRenderer buildQueryableLabel() { return new DefaultTreeCellRenderer() { public Color getBackgroundSelectionColor() { if (!QueryableTree.this.hasFocus() && !isEditing()) return UIManager.getColor("Panel.background"); return super.getBackgroundSelectionColor(); } public Color getBorderSelectionColor() { if (!QueryableTree.this.hasFocus() && !isEditing()) return UIManager.getColor("Panel.background"); return super.getBorderSelectionColor(); } public Dimension getPreferredSize() { Dimension size = super.getPreferredSize(); size.height += 2; return size; } }; } private void initialize() { setOpaque(false); GridBagConstraints constraints = new GridBagConstraints(); this.queryableLabel = buildQueryableLabel(); 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(this.queryableLabel, constraints); this.allowsNullCheckBox = new JCheckBox(); this.allowsNullCheckBox.setMargin(new Insets(0,0,0,0)); this.allowsNullCheckBox.setOpaque(false); this.allowsNullCheckBox.addActionListener(buildAllowsNullAction()); constraints.gridx = 1; constraints.gridy = 0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 0; constraints.weighty = 0; constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.CENTER; constraints.insets = new Insets(0, 8, 0, 0); add(this.allowsNullCheckBox, constraints); } protected void populate(QueryableTreeNode queryableNode, boolean selected) { this.queryableNode = queryableNode; MWQueryable queryable = (MWQueryable) queryableNode.getUserObject(); this.allowsNullCheckBox.setVisible(queryable.allowsOuterJoin()); this.allowsNullCheckBox.setEnabled(selected); this.allowsNullCheckBox.setSelected(queryableNode.isAllowsNull()); this.queryableLabel.setText(queryable.getName()); this.queryableLabel.setIcon(resourceRepository().getIcon(queryable.iconKey())); if (queryable.usesAnyOf()) { this.allowsNullCheckBox.setText(resourceRepository().getString("ALLOWS_NONE_CHECK_BOX_LABEL_IN_QUERYABLE_TREE")); this.allowsNullCheckBox.setMnemonic(resourceRepository().getMnemonic("ALLOWS_NONE_CHECK_BOX_LABEL_IN_QUERYABLE_TREE")); this.allowsNullCheckBox.setDisplayedMnemonicIndex(resourceRepository().getMnemonicIndex("ALLOWS_NONE_CHECK_BOX_LABEL_IN_QUERYABLE_TREE")); } else { this.allowsNullCheckBox.setText(resourceRepository().getString("ALLOWS_NULL_CHECK_BOX_LABEL_IN_QUERYABLE_TREE")); this.allowsNullCheckBox.setMnemonic(resourceRepository().getMnemonic("ALLOWS_NULL_CHECK_BOX_LABEL_IN_QUERYABLE_TREE")); this.allowsNullCheckBox.setDisplayedMnemonicIndex(resourceRepository().getMnemonicIndex("ALLOWS_NULL_CHECK_BOX_LABEL_IN_QUERYABLE_TREE")); } } protected void update(JTree tree, boolean selected, boolean expanded, boolean leaf, int rowIndex, boolean hasFocus) { queryableLabel.getTreeCellRendererComponent(tree, null, selected, expanded, leaf, rowIndex, hasFocus); } } private final class QueryableTreeCell extends AbstractCellEditor implements TreeCellEditor, TreeCellRenderer { private QueryableNodeCheckBoxPanel editor; private Object value; QueryableTreeCell() { super(); initialize(); } private QueryableNodeCheckBoxPanel createQueryKeyPanel() { return new QueryableNodeCheckBoxPanel(); } public Object getCellEditorValue() { return value; } public Component getTreeCellEditorComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row) { // If I am asking for the editor, then a selection is about to take // place so 'selected' might not be true right now, but it will be soon editor.update(tree, true, expanded, leaf, row, true); editor.populate((QueryableTreeNode) value, true); this.value = value; return editor; } public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int rowIndex, boolean hasFocus) { if (tree.getModel().getRoot() == value) return new JLabel(); QueryableTreeNode node = (QueryableTreeNode) value; hasFocus = isPathSelected(new TreePath(node.getPath())); QueryableNodeCheckBoxPanel checkBoxPanel = createQueryKeyPanel(); checkBoxPanel.update(tree, selected, expanded, leaf, rowIndex, hasFocus); checkBoxPanel.populate((QueryableTreeNode) value, selected); checkBoxPanel.invalidate(); return checkBoxPanel; } private void initialize() { editor = createQueryKeyPanel(); } public boolean isCellEditable(EventObject e) { // Retrieve the node where the mouse event location occurred TreePath path = null; MouseEvent mouseEvent = null; if (e instanceof MouseEvent) { mouseEvent = (MouseEvent) e; path = getPathForLocation(mouseEvent.getX(), mouseEvent.getY()); } else { path = getSelectionPath(); } if (path == null) return false; QueryableTreeNode node = (QueryableTreeNode) path.getLastPathComponent(); MWQueryable queryable = (MWQueryable) node.getUserObject(); if (!queryable.allowsOuterJoin()) return false; if (mouseEvent == null) return true; // Layout panel so we know if the location of the mouse is // inside of the check box JCheckBox checkBox = editor.allowsNullCheckBox; editor.populate(node, true); int x_checkBox = editor.queryableLabel.getPreferredSize().width + 8; int x_allowsNullCheckBox = getPathBounds(path).x + x_checkBox + checkBox.getBorder().getBorderInsets(checkBox).left; return mouseEvent.getX() >= x_allowsNullCheckBox; } } }