/*
* Created on Feb 26, 2005
*/
package org.flexdock.view;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import org.flexdock.docking.Dockable;
import org.flexdock.docking.DockingConstants;
import org.flexdock.docking.DockingManager;
import org.flexdock.docking.DockingPort;
import org.flexdock.docking.DockingStrategy;
import org.flexdock.docking.defaults.DefaultDockingStrategy;
import org.flexdock.docking.event.DockingEvent;
import org.flexdock.docking.event.DockingListener;
import org.flexdock.docking.props.DockablePropertySet;
import org.flexdock.docking.props.PropertyManager;
import org.flexdock.plaf.PlafManager;
import org.flexdock.plaf.theme.ViewUI;
import org.flexdock.util.DockingUtility;
import org.flexdock.util.ResourceManager;
import org.flexdock.util.SwingUtility;
/**
* The {@code View} class is slightly incompatible with {@code JComponent}.
* Similar to JFC/Swing top-level containers, a {@code View} contains only a
* content pane {@code Container} and a {@code Titlebar}. The <b>content pane</b>
* should contain all the components displayed by the {@code View}. As a
* convenience {@code add} and its variants, {@code remove(Component)} and
* {@code setLayout} have been overridden to forward to the
* {@code contentPane} as necessary. This means you can write:
*
* <pre>
* view.add(child);
* </pre>
*
* And the child will be added to the contentPane. The content pane will always
* be non-null. Attempting to set it to null will cause the View to throw an
* exception. The default content pane will not have a layout manager set.
*
* @see javax.swing.JFrame
* @see javax.swing.JRootPane
*
* @author Christopher Butler
* @author Karl Schaefer
*/
public class View extends JComponent implements Dockable, DockingConstants {
protected class ViewLayout implements LayoutManager2, Serializable {
/**
* Returns the amount of space the layout would like to have.
*
* @param parent
* the Container for which this layout manager is being used
* @return a Dimension object containing the layout's preferred size
*/
public Dimension preferredLayoutSize(Container parent) {
Dimension rd, tpd;
Insets i = getInsets();
if (contentPane != null) {
rd = contentPane.getPreferredSize();
} else {
rd = parent.getSize();
}
if (titlepane != null && titlepane.isVisible()) {
tpd = titlepane.getPreferredSize();
} else {
tpd = new Dimension(0, 0);
}
return new Dimension(Math.max(rd.width, tpd.width) + i.left
+ i.right, rd.height + tpd.height + i.top + i.bottom);
}
/**
* Returns the minimum amount of space the layout needs.
*
* @param parent
* the Container for which this layout manager is being used
* @return a Dimension object containing the layout's minimum size
*/
public Dimension minimumLayoutSize(Container parent) {
Dimension rd, tpd;
Insets i = getInsets();
if (contentPane != null) {
rd = contentPane.getMinimumSize();
} else {
rd = parent.getSize();
}
if (titlepane != null && titlepane.isVisible()) {
tpd = titlepane.getMinimumSize();
} else {
tpd = new Dimension(0, 0);
}
return new Dimension(Math.max(rd.width, tpd.width) + i.left
+ i.right, rd.height + tpd.height + i.top + i.bottom);
}
/**
* Returns the maximum amount of space the layout can use.
*
* @param target
* the Container for which this layout manager is being used
* @return a Dimension object containing the layout's maximum size
*/
public Dimension maximumLayoutSize(Container target) {
Dimension rd, tpd;
Insets i = getInsets();
if (titlepane != null && titlepane.isVisible()) {
tpd = titlepane.getMaximumSize();
} else {
tpd = new Dimension(0, 0);
}
if (contentPane != null) {
rd = contentPane.getMaximumSize();
} else {
// This is silly, but should stop an overflow error
rd = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE - i.top
- i.bottom - tpd.height - 1);
}
return new Dimension(Math.min(rd.width, tpd.width) + i.left
+ i.right, rd.height + tpd.height + i.top + i.bottom);
}
/**
* Instructs the layout manager to perform the layout for the specified
* container.
*
* @param parent
* the Container for which this layout manager is being used
*/
public void layoutContainer(Container parent) {
Rectangle b = parent.getBounds();
Insets i = getInsets();
int contentY = 0;
int w = b.width - i.right - i.left;
int h = b.height - i.top - i.bottom;
if (titlepane != null && titlepane.isVisible()) {
Dimension mbd = titlepane.getPreferredSize();
titlepane.setBounds(0, 0, w, mbd.height);
contentY += mbd.height;
}
if (contentPane != null) {
contentPane.setBounds(0, contentY, w, h - contentY);
}
}
public void addLayoutComponent(String name, Component comp) {
}
public void removeLayoutComponent(Component comp) {
}
public void addLayoutComponent(Component comp, Object constraints) {
}
public float getLayoutAlignmentX(Container target) {
return 0.0f;
}
public float getLayoutAlignmentY(Container target) {
return 0.0f;
}
public void invalidateLayout(Container target) {
}
}
public static final String uiClassID = "Flexdock.view";
public static final String ACTION_TOGGLE_NEXT = "toggleNextView";
public static final String ACTION_TOGGLE_PREVIOUS = "togglePreviousView";
static final DockingStrategy VIEW_DOCKING_STRATEGY = createDockingStrategy();
private String persistentId;
protected Titlebar titlepane;
protected Container contentPane;
protected boolean contentPaneCheckingEnabled;
protected ArrayList dockingListeners;
// protected boolean active;
protected ArrayList dragSources;
protected HashSet frameDragSources;
private transient HashSet blockedActions;
static {
DockingManager.setDockingStrategy(View.class, VIEW_DOCKING_STRATEGY);
PropertyManager.setDockablePropertyType(View.class, ViewProps.class);
}
public View(String persistentId) {
this(persistentId, null);
}
public View(String persistentId, String title) {
this(persistentId, title, null);
}
public View(String persistentId, String title, String tabText) {
if (persistentId == null)
throw new IllegalArgumentException(
"The 'persistentId' parameter cannot be null.");
this.persistentId = persistentId;
dragSources = new ArrayList(1);
frameDragSources = new HashSet(1);
dockingListeners = new ArrayList(1);
setContentPane(createContentPane());
setTitlebar(createTitlebar());
setLayout(createLayout());
setContentPaneCheckingEnabled(true);
if (title == null)
title = "";
setTitle(title);
if (tabText == null)
tabText = title;
setTabText(tabText);
addHierarchyListener(new HierarchyListener() {
public void hierarchyChanged(HierarchyEvent e) {
clearButtonRollovers();
}
});
updateUI();
DockingManager.registerDockable((Dockable) this);
getActionMap().put(ACTION_TOGGLE_NEXT, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
SwingUtility.toggleFocus(+1);
}
});
getActionMap().put(ACTION_TOGGLE_PREVIOUS, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
SwingUtility.toggleFocus(-1);
}
});
}
private static DockingStrategy createDockingStrategy() {
return new DefaultDockingStrategy() {
protected DockingPort createDockingPortImpl(DockingPort base) {
return new Viewport();
}
};
}
public static View getInstance(String viewId) {
Dockable view = DockingManager.getDockable(viewId);
return view instanceof View ? (View) view : null;
}
protected Container createContentPane() {
return new JPanel();
}
protected LayoutManager createLayout() {
return new ViewLayout();
}
protected Titlebar createTitlebar() {
Titlebar t = new Titlebar();
t.setView(this);
return t;
}
public Container getContentPane() {
return contentPane;
}
public Titlebar getTitlebar() {
return titlepane;
}
public DockablePropertySet getDockingProperties() {
return PropertyManager.getDockablePropertySet(this);
}
public ViewProps getViewProperties() {
return (ViewProps) getDockingProperties();
}
public void addAction(Action action) {
if (titlepane != null)
titlepane.addAction(action);
}
public void addAction(String action) {
if (titlepane != null)
titlepane.addAction(action);
}
public void removeActions() {
if (titlepane != null)
titlepane.removeAllActions();
}
public void setIcon(Icon icon) {
if (titlepane != null)
titlepane.setIcon(icon);
}
public void setIcon(String imgUri) {
Icon icon = imgUri == null ? null : ResourceManager.createIcon(imgUri);
setIcon(icon);
}
/**
* Sets the content pane for this view.
*
* @param c
* the container to use as the content pane.
* @throws IllegalArgumentException
* if {@code c} is {@code null} or if {@code c} is the
* {@code titlepane}.
* @see #titlepane
* @see #getTitlePane()
*/
public void setContentPane(Container c) throws IllegalArgumentException {
if (c == null)
throw new IllegalArgumentException(
"Unable to set a null content pane.");
if (c == titlepane)
throw new IllegalArgumentException(
"Cannot use the same component as both content pane and titlebar.");
if (contentPane != null) {
remove(contentPane);
}
contentPane = c;
if (contentPane != null) {
boolean checkingEnabled = isContentPaneCheckingEnabled();
try {
setContentPaneCheckingEnabled(false);
add(contentPane);
} finally {
setContentPaneCheckingEnabled(checkingEnabled);
}
}
}
protected String getPreferredTitlebarUIName() {
return ui instanceof ViewUI ? ((ViewUI) ui).getPreferredTitlebarUI()
: null;
}
public void setTitlebar(Titlebar titlebar) {
if (titlebar != null && titlebar == contentPane) {
throw new IllegalArgumentException(
"Cannot use the same component as both content pane and titlebar.");
}
if (titlepane != null) {
remove(titlepane);
titlepane.setView(null);
dragSources.remove(titlepane);
frameDragSources.remove(titlepane);
DockingManager.removeDragListeners(titlepane);
}
titlepane = titlebar;
if (titlepane != null) {
boolean checkingEnabled = isContentPaneCheckingEnabled();
try {
setContentPaneCheckingEnabled(false);
add(titlepane);
} finally {
setContentPaneCheckingEnabled(checkingEnabled);
}
dragSources.add(titlepane);
frameDragSources.add(titlepane);
DockingManager.updateDragListeners(this);
}
}
protected Component getTitlePane() {
return titlepane;
}
public void setTitle(String title) {
if (titlepane != null) {
titlepane.setText(title);
}
}
public void setTitle(String title, boolean alsoTabText) {
setTitle(title);
if (alsoTabText) {
setTabText(title);
}
}
public String getTitle() {
Titlebar tbar = getTitlebar();
return tbar == null ? null : tbar.getText();
}
public void updateUI() {
setUI(PlafManager.getUI(this));
}
public String getUIClassID() {
return uiClassID;
}
/**
* {@inheritDoc}
*/
protected void addImpl(Component comp, Object constraints, int index) {
if (comp instanceof Titlebar)
((Titlebar) comp).setView(this);
if (isContentPaneCheckingEnabled()) {
getContentPane().add(comp, constraints, index);
} else {
super.addImpl(comp, constraints, index);
}
}
public void remove(Component comp) {
if (comp == contentPane) {
super.remove(comp);
} else {
getContentPane().remove(comp);
}
}
public AbstractButton getActionButton(String actionName) {
Titlebar tbar = getTitlebar();
return tbar == null ? null : tbar.getActionButton(actionName);
}
public Component getComponent() {
return this;
}
public List getDragSources() {
return dragSources;
}
public Set getFrameDragSources() {
return frameDragSources;
}
public String getPersistentId() {
return persistentId;
}
public boolean isTerritoryBlocked(Dockable dockable, String region) {
return getDockingProperties().isTerritoryBlocked(region).booleanValue();
}
public void setTerritoryBlocked(String region, boolean blocked) {
getDockingProperties().setTerritoryBlocked(region, blocked);
}
public String getTabText() {
String txt = getDockingProperties().getDockableDesc();
return txt == null ? getTitle() : txt;
}
public void setTabText(String tabText) {
getDockingProperties().setDockableDesc(tabText);
}
public Icon getTabIcon() {
return getDockingProperties().getTabIcon();
}
public void setTabIcon(Icon icon) {
getDockingProperties().setTabIcon(icon);
}
public boolean dock(Dockable dockable) {
return dock(dockable, CENTER_REGION);
}
public DockingPort getDockingPort() {
return DockingManager.getDockingPort((Dockable) this);
}
public Dockable getSibling(String region) {
return DefaultDockingStrategy.getSibling(this, region);
}
public Viewport getViewport() {
DockingPort port = getDockingPort();
return port instanceof Viewport ? (Viewport) port : null;
}
public boolean dock(Dockable dockable, String relativeRegion) {
return DockingManager.dock(dockable, this, relativeRegion);
}
public boolean dock(Dockable dockable, String relativeRegion, float ratio) {
return DockingManager.dock(dockable, this, relativeRegion, ratio);
}
public void setActive(boolean b) {
getViewProperties().setActive(b);
}
public boolean isActive() {
return getViewProperties().isActive().booleanValue();
}
public void setActiveStateLocked(boolean b) {
getViewProperties().setActiveStateLocked(b);
}
public boolean isActiveStateLocked() {
return getViewProperties().isActiveStateLocked().booleanValue();
}
public boolean isMinimized() {
return DockingUtility.isMinimized(this);
}
public int getMinimizedConstraint() {
return DockingUtility.getMinimizedConstraint(this);
}
public void addDockingListener(DockingListener listener) {
dockingListeners.add(listener);
}
public DockingListener[] getDockingListeners() {
return (DockingListener[]) dockingListeners
.toArray(new DockingListener[0]);
}
public void removeDockingListener(DockingListener listener) {
dockingListeners.remove(listener);
}
public void dockingCanceled(DockingEvent evt) {
}
public void dockingComplete(DockingEvent evt) {
setActionBlocked(DockingConstants.PIN_ACTION, isFloating());
if (titlepane != null)
titlepane.revalidate();
}
public void dragStarted(DockingEvent evt) {
}
public void dropStarted(DockingEvent evt) {
}
public void undockingComplete(DockingEvent evt) {
clearButtonRollovers();
}
public void undockingStarted(DockingEvent evt) {
}
private void clearButtonRollovers() {
if (titlepane == null)
return;
Component[] comps = titlepane.getComponents();
for (int i = 0; i < comps.length; i++) {
Button button = comps[i] instanceof Button ? (Button) comps[i]
: null;
if (button != null) {
button.getModel().setRollover(false);
}
}
}
public void setActionBlocked(String actionName, boolean blocked) {
if (actionName == null)
return;
Set actions = getBlockedActions();
if (blocked)
actions.add(actionName);
else {
if (actions != null)
actions.remove(actionName);
}
}
public boolean isActionBlocked(String actionName) {
return actionName == null || blockedActions == null ? false
: blockedActions.contains(actionName);
}
private HashSet getBlockedActions() {
if (blockedActions == null)
blockedActions = new HashSet(1);
return blockedActions;
}
public boolean isFloating() {
return DockingUtility.isFloating(this);
}
protected String paramString() {
return "id=" + getPersistentId() + "," + super.paramString();
}
/**
* @return the contentPaneCheckingEnabled
*/
protected boolean isContentPaneCheckingEnabled() {
return contentPaneCheckingEnabled;
}
/**
* @param contentPaneCheckingEnabled
* the contentPaneCheckingEnabled to set
*/
protected void setContentPaneCheckingEnabled(
boolean contentPaneCheckingEnabled) {
this.contentPaneCheckingEnabled = contentPaneCheckingEnabled;
}
/**
* Sets the <code>LayoutManager</code>. Overridden to conditionally
* forward the call to the <code>contentPane</code>.
*
* @param manager
* the <code>LayoutManager</code>
* @see #setContentPaneCheckingEnabled
*/
public void setLayout(LayoutManager manager) {
if (isContentPaneCheckingEnabled()) {
getContentPane().setLayout(manager);
} else {
super.setLayout(manager);
}
}
}