/******************************************************************************* * 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.common; import java.awt.Component; import java.awt.Dialog; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Vector; import javax.accessibility.AccessibleContext; import javax.swing.Icon; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import org.eclipse.persistence.tools.workbench.framework.context.WorkbenchContext; import org.eclipse.persistence.tools.workbench.framework.ui.dialog.AbstractDialog; import org.eclipse.persistence.tools.workbench.framework.uitools.SwingComponentFactory; import org.eclipse.persistence.tools.workbench.mappingsmodel.MWError; import org.eclipse.persistence.tools.workbench.uitools.Displayable; import org.eclipse.persistence.tools.workbench.uitools.LabelArea; import org.eclipse.persistence.tools.workbench.uitools.cell.AbstractCellRendererAdapter; import org.eclipse.persistence.tools.workbench.uitools.cell.CellRendererAdapter; import org.eclipse.persistence.tools.workbench.uitools.cell.SimpleTreeCellRenderer; import org.eclipse.persistence.tools.workbench.utility.node.Node; /** * This dialog shows a tree representation of status. A status is composed of * an item that is been described by a collection of status. The status can have * a collection of children. */ public class StatusDialog extends AbstractDialog { /** * The id used to retrieve the help content. */ private final String helpTopicId; /** * The key used to retrieve the text to be shown above the tree. */ private final String messageKey; /** * The collection of <code>ModelStatus</code>. */ private final Collection status; /** * The tree used to display all the <code>ModelStatus</code>. */ JTree tree; /** * Creates a new <code>StatusDialog</code>. * * @param context The context of this dialog * @param status The collection of <code>ModelStatus</code> to be displayed * @param titleKey The key used to retreive the title from the repository */ public StatusDialog(WorkbenchContext context, Collection status, String titleKey) { this(context, status, titleKey, "dialog.status"); } /** * Creates a new <code>StatusDialog</code>. * * @param context The context of this dialog * @param owner The dialog owner of this dialog * @param status The collection of <code>ModelStatus</code> to be displayed * @param titleKey The key used to retreive the title from the repository */ public StatusDialog(WorkbenchContext context, Dialog owner, Collection status, String titleKey) { this(context, owner, status, titleKey, "dialog.status"); } /** * Creates a new <code>StatusDialog</code>. * * @param context The context of this dialog * @param status The collection of <code>ModelStatus</code> to be displayed * @param titleKey The key used to retreive the title from the repository * @param helpTopicId The key used to retrieve the help content */ public StatusDialog(WorkbenchContext context, Collection status, String titleKey, String helpTopicId) { this(context, status, titleKey, "STATUS_DIALOG_MESSAGE", helpTopicId); } /** * Creates a new <code>StatusDialog</code>. * * @param context The context of this dialog * @param owner The dialog owner of this dialog * @param status The collection of <code>ModelStatus</code> to be displayed * @param titleKey The key used to retreive the title from the repository * @param helpTopicId The key used to retrieve the help content */ public StatusDialog(WorkbenchContext context, Dialog owner, Collection status, String titleKey, String helpTopicId) { this(context, owner, status, titleKey, "STATUS_DIALOG_MESSAGE", helpTopicId); } /** * Creates a new <code>StatusDialog</code>. * * @param context The context of this dialog * @param status The collection of <code>ModelStatus</code> to be displayed * @param titleKey The key used to retreive the title from the repository * @param messageKey The key used to retrieve the text to be shown above the * tree * @param helpTopicId The key used to retrieve the help content */ public StatusDialog(WorkbenchContext context, Collection status, String titleKey, String messageKey, String helpTopicId) { super(context); if (status == null) { throw new NullPointerException("The collection of status cannot be null"); } this.status = status; this.messageKey = messageKey; this.helpTopicId = helpTopicId; setTitle(resourceRepository().getString(titleKey)); } /** * Creates a new <code>StatusDialog</code>. * * @param context The context of this dialog * @param owner The dialog owner of this dialog * @param status The collection of <code>ModelStatus</code> to be displayed * @param titleKey The key used to retreive the title from the repository * @param messageKey The key used to retrieve the text to be shown above the * tree * @param helpTopicId The key used to retrieve the help content */ public StatusDialog(WorkbenchContext context, Dialog owner, Collection status, String titleKey, String messageKey, String helpTopicId) { super(context, owner); if (status == null) { throw new NullPointerException("The collection of status cannot be null"); } this.status = status; this.messageKey = messageKey; this.helpTopicId = helpTopicId; setTitle(resourceRepository().getString(titleKey)); } /** * Creates a new <code>IStatus</code> for the given item. * * @param item The item that has a status to be displayed * @param status The list of status * @return A new <code>IStatus</code> */ public static Status createStatus(Object item, List status) { return createStatus(item, status, Collections.EMPTY_LIST); } /** * Creates a new <code>IStatus</code> for the given item. * * @param item The item that has a status to be displayed * @param status The list of status * @param children The children <code>IStatus</code> * @return A new <code>IStatus</code> */ public static Status createStatus(Object item, List status, List children) { return new LocalStatus(item, status, children); } /** * Creates a new <code>IStatus</code> for the given item. * * @param item The item that has a status to be displayed * @param status The list of status where the status are the keys of this map * @return A new <code>IStatus</code> */ public static Status createStatus(Object item, Map status) { return createStatus(item, new Vector(status.keySet())); } /** * Creates a new <code>IStatus</code> for the given item. * * @param item The item that has a status to be displayed * @param status The list of status where the status are the keys of this map * @param children The children <code>IStatus</code> * @return A new <code>IStatus</code> */ public static Status createStatus(Object item, Map status, List children) { return createStatus(item, new Vector(status.keySet()), children); } /** * Creates the listener responsible to revalidate the tree when the component * listened is resided. * * @return A new <code>ComponentListener</code> */ private ComponentListener buildComponentListener() { return new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { StatusDialog.this.updateTree(); } }; } /** * Creates the widgets of this dialog. * * @return The container with its widgets */ @Override protected Component buildMainPanel() { GridBagConstraints constraints = new GridBagConstraints(); JPanel container = new JPanel(new GridBagLayout()); // Message label LabelArea messageLabel = new LabelArea(resourceRepository().getString(this.messageKey)); messageLabel.getAccessibleContext().setAccessibleName(messageLabel.getText()); messageLabel.setScrollable(true); constraints.gridx = 0; constraints.gridy = 0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.weightx = 1; constraints.weighty = 0; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.anchor = GridBagConstraints.CENTER; constraints.insets = new Insets(0, 0, 0, 0); container.add(messageLabel, constraints); // Tree this.tree = new StatusTree(); this.tree.setRowHeight(0); this.tree.setShowsRootHandles(false); this.tree.setRootVisible(false); this.tree.setDoubleBuffered(true); this.tree.setCellRenderer(new StatusTreeNodeRenderer()); 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 pane = new JScrollPane(this.tree); pane.addComponentListener(buildComponentListener()); container.add(pane, constraints); messageLabel.setLabelFor(this.tree); return container; } /** * Creates a new <code>CellRendererAdapter</code> to render the given value. * * @param item The item to be formated on screen * @return A new <code>CellRendererAdapter</code> */ protected CellRendererAdapter buildNodeRenderer(Object value) { if (value instanceof Node) return CellRendererAdapter.NODE_CELL_RENDERER_ADAPTER; if (value instanceof Displayable) return CellRendererAdapter.DISPLAYABLE_CELL_RENDERER_ADAPTER; if (value instanceof MWError) return new MWErrorCellRendererAdapter(); return CellRendererAdapter.DEFAULT_CELL_RENDERER_ADAPTER; } /** * Populates the tree by adding all the <code>ModelStatus</code> to the root * node. */ private void buildTreeContent() { MutableTreeNode rootNode = rootNode(); for (Iterator iter = this.status.iterator(); iter.hasNext(); ) { buildTreeNode(rootNode, (Status) iter.next()); } } /** * Create the nodes for the given <code>ModelStatus</code> and add it to * the given parent node. * * @param parentNode The node where the newly created node wrapping the given * status * @param status The status to be added to the tree */ private void buildTreeNode(MutableTreeNode parentNode, Status nodeStatus) { // Add the model node StatusTreeNode statusNode = buildTreeNode(nodeStatus.getItem(), false); parentNode.insert(statusNode, parentNode.getChildCount()); // Add the status first for (Iterator iter = nodeStatus.status(); iter.hasNext();) { StatusTreeNode node = buildTreeNode(iter.next(), true); statusNode.insert(node, statusNode.getChildCount()); } // Add the children after the status for (Iterator iter = nodeStatus.children(); iter.hasNext();) { buildTreeNode(statusNode, (Status) iter.next()); } } /** * Creates a new <code>IStatusTreeNode</code> where its object will be the * given value. * * @param item The node's user object * @param requireWrapping Flag used by the tree renderer to see if the text * requires to be wrapped onto multiple lines * @return A new <code>IStatusTreeNode</code> */ private StatusTreeNode buildTreeNode(Object value, boolean requireWrapping) { return new StatusTreeNode(value, buildNodeRenderer(value)); } @Override protected boolean cancelButtonIsVisible() { return false; } private void expandAll(MutableTreeNode node) { if (node.isLeaf()) return; expandPath(node); for (Enumeration stream = node.children(); stream.hasMoreElements();) { MutableTreeNode childNode = (MutableTreeNode) stream.nextElement(); expandAll(childNode); } } /** * Expands * * @param node */ private void expandPath(MutableTreeNode node) { DefaultTreeModel model = (DefaultTreeModel) this.tree.getModel(); TreeNode[] paths = model.getPathToRoot(node); this.tree.expandPath(new TreePath(paths)); } /** * Returns the topic id used to retrieve the help topic id in the help. * * @return The id used to retrieve the help content */ @Override protected String helpTopicId() { return this.helpTopicId; } @Override protected void initialize(WorkbenchContext context) { super.initialize(context.buildExpandedResourceRepositoryContext(UiCommonBundle.class)); } /** * The dialog is about to become visible, build the content of tree. */ @Override protected void prepareToShow() { setSize(455, 230); setLocationRelativeTo(getParent()); buildTreeContent(); updateTree(); } /** * Reloads the tree model and revalidate the nodes in order to resize the * status nodes. */ void updateTree() { ((DefaultTreeModel) this.tree.getModel()).reload(); expandAll(rootNode()); this.tree.revalidate(); this.tree.repaint(); } /** * Returns the root node of the tree. * * @return The root node */ protected final MutableTreeNode rootNode() { return (MutableTreeNode) ((DefaultTreeModel) this.tree.getModel()).getRoot(); } /** * The status of an item to be displayed. The item can have one or more * status and zero or more children. */ public interface Status { /** * This <code>Iterator</code> returns the list of <code>IStatus</code> * contained in this <code>IStatus</code>. * * @return The iterator over the children if any exist */ public Iterator children(); /** * The item that has at least a status attached to it. * * @return The item that is shown as a root node */ public Object getItem(); /** * This <code>Iterator</code> returns the list of status (description of * errors, warnings or a simple success status. * * @return The iterator over the status */ public Iterator status(); } /** * The renderer of <code>MWError</code> object. */ protected class MWErrorCellRendererAdapter extends AbstractCellRendererAdapter { @Override public Icon buildIcon(Object value) { MWError error = (MWError) value; String errorId = error.getErrorId(); if (errorId.endsWith("SUCCESSFUL")) return resourceRepository().getIcon("approve"); if (errorId.endsWith("ERROR")) return resourceRepository().getIcon("error"); return resourceRepository().getIcon("warning"); } @Override public String buildText(Object value) { MWError error = (MWError) value; return resourceRepository().getString(error.getErrorId(), error.getArguments()); } } /** * This <code>Status</code> reflects the information to be displayed in this * dialog. Basically, the item is the object that has status attached to it. * It is possible to have children status for this status. */ private static class LocalStatus implements Status { private final List children; private final Object item; private final List status; public LocalStatus(Object item, List status, List children) { super(); this.item = item; this.status = status; this.children = children; } public Iterator children() { return this.children.iterator(); } public Object getItem() { return this.item; } public Iterator status() { return this.status.iterator(); } } /** * This subclass only prevents the user from collapsing a node. */ private class StatusTree extends SwingComponentFactory.AccessibleTree { private StatusTree() { super(new DefaultTreeModel(new DefaultMutableTreeNode(""))); } @Override public void collapsePath(TreePath path) { // Prevent the tree to collapse } @Override public void collapseRow(int row) { // Prevent the tree to collapse } @Override protected void setExpandedState(TreePath path, boolean state) { // Prevent the tree to collapse if (state) { super.setExpandedState(path, state); } } } /** * Default tree node. The rendering of this node is performed by a * <code>CellRendererAdapter</code> that is attached to it. */ private class StatusTreeNode extends DefaultMutableTreeNode { private final CellRendererAdapter renderer; private StatusTreeNode(Object value, CellRendererAdapter renderer) { super(value); this.renderer = renderer; } public String buildAccessibleName() { return this.renderer.buildAccessibleName(getUserObject()); } public Icon buildIcon() { return this.renderer.buildIcon(getUserObject()); } public String buildText() { return this.renderer.buildText(getUserObject()); } } /** * The renderer of tree where it request the information from the * <code>IStatusNode</code>. */ // TODO convert this to use a LabelArea once LabelArea supports an icon private class StatusTreeNodeRenderer extends SimpleTreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree t, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean cellHasFocus) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; return node.isRoot() ? this : super.getTreeCellRendererComponent(t, value, sel, expanded, leaf, row, cellHasFocus); } @Override protected String buildAccessibleName(Object value) { return ((StatusTreeNode) value).buildAccessibleName(); } @Override protected Icon buildIcon(Object value) { return ((StatusTreeNode) value).buildIcon(); } @Override protected String buildText(Object value) { return ((StatusTreeNode) value).buildText(); } @Override public AccessibleContext getAccessibleContext() { if (this.accessibleContext == null) { this.accessibleContext = new AccessibleStatusNode(); } return this.accessibleContext; } protected class AccessibleStatusNode extends AccessibleJLabel { @Override public String getAccessibleName() { return super.getAccessibleName(); } } } }