/*******************************************************************************
* Mission Control Technologies, Copyright (c) 2009-2012, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
* The MCT platform is licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
* MCT includes source code licensed under additional open source licenses. See
* the MCT Open Source Licenses file included with this distribution or the About
* MCT Licenses dialog available at runtime from the MCT Help menu for additional
* information.
*******************************************************************************/
/*
* MCTContentArea.java - Aug. 18, 2008.
*
* This code is property of the National Aeronautics and Space Administration
* and was produced for the Mission Control Technologies (MCT) Project.
*
*/
package gov.nasa.arc.mct.gui.housing;
import gov.nasa.arc.mct.components.AbstractComponent;
import gov.nasa.arc.mct.gui.ActionContext;
import gov.nasa.arc.mct.gui.CompositeViewManifestationProvider;
import gov.nasa.arc.mct.gui.ContextAwareButton;
import gov.nasa.arc.mct.gui.SelectionProvider;
import gov.nasa.arc.mct.gui.SettingsButton;
import gov.nasa.arc.mct.gui.View;
import gov.nasa.arc.mct.gui.ViewProvider;
import gov.nasa.arc.mct.gui.actions.RefreshAction;
import gov.nasa.arc.mct.gui.housing.MCTHousing.ControlProvider;
import gov.nasa.arc.mct.gui.impl.ActionContextImpl;
import gov.nasa.arc.mct.gui.menu.MenuFactory;
import gov.nasa.arc.mct.platform.spi.PlatformAccess;
import gov.nasa.arc.mct.services.component.ViewInfo;
import gov.nasa.arc.mct.services.component.ViewType;
import gov.nasa.arc.mct.util.LafColor;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
/**
* Framework for holding the MCT Content Area (often refereed to as "the canvas") including its title bar and
* control panel.
*/
@SuppressWarnings("serial")
public class MCTContentArea extends JPanel implements CompositeViewManifestationProvider, SelectionProvider, ControlProvider {
public static final String CENTER_PANE_VIEW_CHANGE = "center-pane-view-change";
private MCTHousing parentHousing;
private final AbstractComponent ownerComponent;
private CanvasTitleArea titleBar;
private View ownerComponentCanvasManifestation = null; // The current contained view manifestation.
private JSplitPane splitPane = null; // hold owner component canvas manifestation and its control area.
private boolean canvasTitleBarShowing = true;
private JComponent controlManifestation = null;
private int dividerSize = 0; // size of split pane's divider.
private final Map<ViewInfo, ViewProvider> housedManifestations = new HashMap<ViewInfo, ViewProvider>();
private ContextAwareButton refreshButton = new ContextAwareButton(new RefreshAction());
private static final ResourceBundle BUNDLE =
ResourceBundle.getBundle(
MCTStandardHousing.class.getName().substring(0,
MCTStandardHousing.class.getName().lastIndexOf("."))+".Bundle");
private final JLabel STALE_LABEL = new JLabel(BUNDLE.getString("view.modified.status.bar.text"));
private final PropertyChangeListener selectionListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
}
};
private final PropertyChangeListener objectStaleStateListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
markStale((Boolean)evt.getNewValue());
}
};
public MCTContentArea(MCTHousing parentHousing, AbstractComponent ownerComponent, View initialViewManifestation) {
super(new BorderLayout());
STALE_LABEL.setToolTipText(BUNDLE.getString("view.modified.status.bar.tooltip.text"));
this.parentHousing = parentHousing;
this.ownerComponent = ownerComponent;
if (ownerComponentCanvasManifestation == null) {
AbstractComponent clonedComponent = PlatformAccess.getPlatform().getPersistenceProvider().getComponent(ownerComponent.getComponentId());
ownerComponentCanvasManifestation = ((initialViewManifestation != null)
? initialViewManifestation
: clonedComponent.getViewInfos(ViewType.CENTER).iterator().next().createView(clonedComponent));
}
assert ownerComponentCanvasManifestation != null;
setOwnerComponentCanvasManifestation(ownerComponentCanvasManifestation);
this.revalidate();
this.parentHousing.setContentArea(this);
refreshButton.setContentAreaFilled(false);
refreshButton.setText("");
refreshButton.setBorder(BorderFactory.createEmptyBorder());
refreshButton.setContext(context);
STALE_LABEL.setForeground(Color.red);
}
public MCTContentArea(MCTHousing parentHousing, AbstractComponent ownerComponent) {
this(parentHousing, ownerComponent, null);
}
public MCTContentArea(MCTHousing parentHousing, View initialViewManifestation) {
this(parentHousing, initialViewManifestation.getManifestedComponent(), initialViewManifestation);
}
@Override
public Collection<View> getSelectedManifestations() {
return ownerComponentCanvasManifestation.getSelectionProvider().getSelectedManifestations();
}
public void showControl(boolean flag) {
if (splitPane != null) {
if (!flag) {
splitPane.setDividerLocation(0);
} else if (controlManifestation.getPreferredSize().getHeight() < 200) {
// Small control manifesation. Give it a small percentage of the panes.
splitPane.setDividerLocation((int) controlManifestation.getPreferredSize().getHeight());
} else {
splitPane.setDividerLocation(-1);
splitPane.setResizeWeight(0.66);
}
splitPane.setDividerSize(flag ? dividerSize : 0);
if (controlManifestation != null) {
controlManifestation.setVisible(flag);
titleBar.controlToggle.setSelected(flag);
}
revalidate();
}
}
@Override
public boolean isControlShowing() {
return controlManifestation.isVisible();
}
public void markStale(boolean isStale) {
MCTStatusArea statusArea = parentHousing.getStatusArea();
if (statusArea == null)
return;
if (isStale)
statusArea.addToLeft(STALE_LABEL);
else
statusArea.removeFromLeft(STALE_LABEL);
}
private void setupSelectionPropertyChangeListener(View oldManifestation) {
if (oldManifestation != null) {
oldManifestation.getSelectionProvider().removeSelectionChangeListener(selectionListener);
}
if (ownerComponentCanvasManifestation != null) {
ownerComponentCanvasManifestation.getSelectionProvider().addSelectionChangeListener(selectionListener);
}
}
public MCTContentArea() {
super(new BorderLayout());
ownerComponent = null;
}
public boolean isTitleBarShowing() {
return this.canvasTitleBarShowing;
}
public void showCanvasTitle(boolean doShow) {
canvasTitleBarShowing = doShow;
if (doShow) {
add(this.titleBar, BorderLayout.NORTH);
} else {
remove(this.titleBar);
showControl(false);
titleBar.setTitleAreaVisible(false);
}
doLayout();
validate();
}
public JComponent getContentAreaPane() {
return this.ownerComponentCanvasManifestation;
}
public void setParentHousing(MCTHousing parentHousing) {
this.parentHousing = parentHousing;
this.refreshButton.setContext(context);
}
/**
* Set the owner view manifestation and update the GUI to display it.
* @param viewManifestation
*/
public void setOwnerComponentCanvasManifestation(View viewManifestation) {
if (viewManifestation == null) {
throw new IllegalArgumentException("viewManifestation argument cannot be null");
}
ownerComponentCanvasManifestation.removePropertyChangeListener(View.VIEW_STALE_PROPERTY, objectStaleStateListener);
markStale(false);
viewManifestation.addPropertyChangeListener(View.VIEW_STALE_PROPERTY, objectStaleStateListener);
housedManifestations.put(viewManifestation.getInfo(), viewManifestation);
if (ownerComponentCanvasManifestation != null) {
// clean out previous manifestation's swing components if there is one.
removeAll();
SelectionProvider provider = ownerComponentCanvasManifestation.getSelectionProvider();
Collection<View> selections = provider.getSelectedManifestations();
if (!selections.isEmpty()) {
provider.clearCurrentSelections();
firePropertyChange(SelectionProvider.SELECTION_CHANGED_PROP, selections, Collections.emptyList());
}
}
// setup new manifestation.
View oldManifestation = ownerComponentCanvasManifestation;
ownerComponentCanvasManifestation = viewManifestation;
setupSelectionPropertyChangeListener(oldManifestation);
controlManifestation = ownerComponentCanvasManifestation.getControlManifestation();
// Setup title bar
titleBar = new CanvasTitleArea(ownerComponentCanvasManifestation.getInfo().getViewName());
// Setup scroll pane.
JScrollPane scrollPane = new JScrollPane(ownerComponentCanvasManifestation, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
// Stop scroll bars responding to arrow keys
scrollPane.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("LEFT"), "doNothing");
scrollPane.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("RIGHT"), "doNothing");
scrollPane.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("LEFT"), "doNothing");
scrollPane.getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.put(KeyStroke.getKeyStroke("RIGHT"), "doNothing");
// Attach control manifestation.
if (controlManifestation != null) {
controlManifestation.setAlignmentX(Component.LEFT_ALIGNMENT);
// use a split pane to separate control manifestation from canvas manifestation.
splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlManifestation, scrollPane);
splitPane.setOneTouchExpandable(true);
splitPane.setContinuousLayout(true);
splitPane.setBorder(null);
dividerSize = splitPane.getDividerSize();
showControl(false); // default is that formatting control is not visible.
showCanvasTitle(canvasTitleBarShowing);
add(splitPane);
instrumentNames(splitPane);
} else {
// Just add the canvas as we there is no control manifestation.
splitPane = null;
showCanvasTitle(canvasTitleBarShowing);
add(scrollPane);
}
this.refreshButton.setContext(context);
revalidate();
}
private void instrumentNames(JSplitPane pane) {
pane.setName("contentAreaSplitPane");
}
@Override
public void addSelectionChangeListener(PropertyChangeListener listener) {
addPropertyChangeListener(SelectionProvider.SELECTION_CHANGED_PROP, listener);
}
@Override
public void removeSelectionChangeListener(PropertyChangeListener listener) {
removePropertyChangeListener(SelectionProvider.SELECTION_CHANGED_PROP, listener);
}
@Override
public void clearCurrentSelections() {
ownerComponentCanvasManifestation.getSelectionProvider().clearCurrentSelections();
}
public AbstractComponent getOwnerComponent() {
return this.ownerComponent;
}
@Override
public View getHousedViewManifestation() {
return this.ownerComponentCanvasManifestation;
}
@Override
public Collection<ViewProvider> getHousedManifestationProviders() {
return this.housedManifestations.values();
}
/**
* Determines whether the content area is empty or not. The initial algorithm will look
* at the current manifestation and determine whether the swing hierarchy contains any other
* MCTViewManifestations. This is not optimal as this relies on the current structure of
* a canvas view. In the future this method may change to invoke a more specific method on the
* view manifestation.
* @return true if the the content area is empty, false otherwise.
*/
public boolean isAreaEmpty() {
View manifestation = getHousedViewManifestation();
return manifestation == null ||
!containsMCTViewManifestation(manifestation);
}
public boolean expandFullContentArea() {
return getHousedViewManifestation().getInfo().shouldExpandCenterPaneInWindow() || !isAreaEmpty();
}
private boolean containsMCTViewManifestation(Container parent) {
for (Component c:parent.getComponents()) {
if (c instanceof View) {
return true;
}
if (c instanceof Container &&
containsMCTViewManifestation(Container.class.cast(c))) {
return true;
}
}
return false;
}
void clearHousedManifestations() {
housedManifestations.clear();
}
private class CanvasTitleArea extends JPanel {
private final Color BACKGROUND_COLOR = LafColor.WINDOW_BORDER.darker();
private final Color FOREGROUND_COLOR = LafColor.WINDOW.brighter();
private static final int HORIZONTAL_SPACING = 5;
private final JLabel title;
private JToggleButton controlToggle = new SettingsButton();
/**
* Inform the title area that it has been hidden and that it
* should update its visual state appropriately.
*/
public void setTitleAreaVisible(boolean state){
if (controlToggle!=null) {
if (controlToggle.isSelected() != state) {
controlToggle.doClick();
}
}
}
public CanvasTitleArea (String text) {
setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
setBackground(BACKGROUND_COLOR);
controlToggle.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
showControl(controlToggle.isSelected());
}
});
title = new JLabel(text);
title.setForeground(FOREGROUND_COLOR);
add(Box.createHorizontalStrut(HORIZONTAL_SPACING));
add(title);
add(Box.createHorizontalStrut(HORIZONTAL_SPACING));
// Only enable control toggle if control manifestation not null.
controlToggle.setEnabled(controlManifestation != null);
add(Box.createHorizontalGlue());
add(refreshButton);
add(controlToggle);
// Add popup listener to title bar.
MouseListener popupListener = new PopupListener();
this.addMouseListener(popupListener);
instrumentNamesInCanvasTitle(title);
}
private void instrumentNamesInCanvasTitle(JLabel title) {
this.setName("canvasTitleArea");
title.setName("title");
controlToggle.setName("settingsToggle");
}
}
class PopupListener extends MouseAdapter {
// Variables used by unit test.
boolean testMode = false;
boolean popupActivated = false;
@Override
public void mousePressed(MouseEvent e) {
processPopUpListenerEvent(e, true);
}
/**
* Respond to mouse event in the canvas area by either showing a popup menu if appropriate
* or noting that items are no longer selected.
* @param e the event.
* @param mousePressed state of the mouse button.
*/
private void processPopUpListenerEvent(MouseEvent e, boolean mousePressed) {
if (e.isPopupTrigger()) {
ActionContextImpl context = new ActionContextImpl();
context.setTargetComponent(ownerComponentCanvasManifestation.getManifestedComponent());
context.setTargetHousing((MCTHousing) SwingUtilities.getAncestorOfClass(MCTAbstractHousing.class,
ownerComponentCanvasManifestation));
JPopupMenu popup = MenuFactory.createViewPopupMenu(context);
assert popup != null;
if (!testMode) {
popup.show(e.getComponent(), e.getX(), e.getY());
} else {
popupActivated = true;
}
}
}
// Test methods only to be utilized by unit tests.
void setTestMode() {
testMode = true;
popupActivated = false;
}
boolean popupActivated() {
return popupActivated;
}
}
@Override
public boolean setHousedViewManifestation(ViewInfo viewInfo) {
View newView = viewInfo.createView(ownerComponent);
setOwnerComponentCanvasManifestation(newView);
return true;
}
// Used to provide a context for buttons
private ActionContext context = new ActionContextImpl() {
@Override
public View getWindowManifestation() {
return parentHousing.getHousedViewManifestation();
}
};
}