/*
* org.openmicroscopy.shoola.util.ui.tpane.TinyPaneModel
*
*------------------------------------------------------------------------------
* Copyright (C) 2006 University of Dundee. All rights reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.util.ui.tpane;
//Java imports
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
//Third-party libraries
//Application-internal dependencies
/**
* The Model sub-component in the {@link TinyPane}'s MVC triad.
* This is a presentation Model which holds and manages presentation data
* in behalf of the {@link TinyPane}, which has pass-through methods that
* delegate to this class, but manage the change notification process. So
* all the other sub-components regard the {@link TinyPane} as the Model.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @version 2.2
* <small>
* (<b>Internal version:</b> $Revision: 4967 $ $Date: 2007-07-30 13:36:27 +0000 (Mon, 30 Jul 2007) $)
* </small>
* @since OME2.2
*/
class TinyPaneModel
{
/** The frame's content pane. */
private Container contentPane;
/**
* Tells if the frame has to be highlighted.
* If <code>null</code>, the frame's title bar will display the normal
* background. If a color is specified, the title bar will be highlighted
* using the specified color.
*/
private Color highlight;
/**
* Tells if the frame has to be highlighted.
* If <code>null</code>, the frame's title bar will display the normal
* background. If a color is specified, the title bar will be highlighted
* using the specified color.
*/
private Color highlightHistory;
/** Tells if the frame is expanded or collapsed. */
private boolean collapsed;
/** Tells if the frame is expanded or collapsed. */
private boolean closed;
/** The size of the frame before the last collapse request. */
private Dimension restoreSize;
/** Identifies the title bar the frame is fitted with. */
private int titleBarType;
/** All contents are added to this container. */
private JLayeredPane desktopPane;
/** Tells if the frame is in single or multi-view mode. */
private boolean singleViewMode;
/** Replaces the {@link #desktopPane} when we're in single-view mode. */
private JLayeredPane singleViewDesktop;
/**
* The bounds of the currently displayed child view relative to the
* {@link #desktopPane} in which it was originally contained, if we're
* in single-view mode or <code>null</code> when in multi-view mode.
* We save this object so that we can restore the original bounds when we
* add the component back to the internal desktop that is, after
* the user selects another child to display in the single-view desktop
* or when the user switches back to the multi-view mode.
*/
private Rectangle childViewBounds;
/**
* The type of the currently displayed child view's title bar, if we're in
* single-view mode.
* We save this value so that we can restore it when the child view is
* returned to the original {@link #desktopPane}.
*/
private int childViewTitleBarType;
/**
* The collapsed state of the the currently displayed child view, if we're
* in single-view mode.
* We save this value so that we can restore it when the child view is
* returned to the original {@link #desktopPane}.
*/
private boolean childViewCollapsed;
/** The title of the component. */
private String title;
/** The note displayed in the titleBar e.g. # of items. */
private String note;
/**
* Flag to indicate if the frame is resizable. Default value is
* <code>false</code>.
*/
private boolean resizable;
/**
* Flag to indicate if listen to the border so that the component can
* be resized, dragged.
*/
private boolean listenToBorder;
/** The frame's icon. */
private Icon frameIcon;
/** The collection of buttons to add to the <code>TitleBar</code>.*/
private List decoration;
/**
* Checks that <code>c</code> is one of the components that were added
* to the internal desktop.
*
* @param c The component to check.
* @return <code>true</code> if <code>c</code> is a child to the internal
* desktop; <code>false</code> otherwise.
*/
private boolean isChild(Component c)
{
if (c != null) {
//First check if it's a child to the desktopPane.
Component[] children = desktopPane.getComponents();
for (int i = 0; i < children.length; ++i)
if (c == children[i]) return true;
//We might be in single-view mode and c was removed from the
//desktopPane. Check.
if (c == getChildView()) return true;
}
return false;
}
/**
* Utility method to pick the first child component out of a given
* container.
*
* @param c The container.
* @return Returns the first component in <code>c</code> if <code>c</code>
* is not <code>null</code> and has children; returns <code>null
* </code> if <code>c</code> is <code>null</code> or has no
* children.
*/
private Component getFirstChild(Container c)
{
Component child = null;
if (c != null) {
Component[] comp = c.getComponents();
if (0 < comp.length) child = comp[0];
}
return child;
}
/**
* Sets the size of the container when we display
* a child in single view mode.
*
* @param parent The container.
*/
private void setParentSizeForSingleView(Container parent)
{
if (parent instanceof TinyPane) {
Dimension d = ((TinyPane) parent).getTitleBar().getPreferredSize();
Dimension dDesktop = singleViewDesktop.getPreferredSize();
parent.setSize(d.width+dDesktop.width, d.height+dDesktop.height+4);
parent.validate();
} else setParentSizeForSingleView(parent.getParent());
}
/**
* Sets the size of the container when we display
* the children in multi-view mode.
*
* @param parent The container.
*/
private void setParentSizeForMultiView(Container parent)
{
if (parent == null) return;
if (parent instanceof TinyPane) {
parent.setSize(parent.getPreferredSize());
parent.validate();
} else setParentSizeForMultiView(parent.getParent());
}
/**
* Creates a new instance.
* After creation the model is not collapsed, the title bar is the
* {@link TinyPane#FULL_BAR}, and there's no highlight.
* However, the internal desktop is not installed yet and you need
* to call {@link #setSingleViewMode(boolean, TinyPaneUI)} passing
* <code>false</code> to complete intialization. At which point, the
* model will be in multi-view mode and the internal desktop will be
* installed.
*
* @param contentPane The container hosting the display.
* @param title The frame's title.
* @param restoreSize The initial frame's size.
* Mustn't be <code>null</code>.
*/
TinyPaneModel(Container contentPane, String title, Dimension restoreSize)
{
if (contentPane == null)
throw new NullPointerException("No content pane.");
this.contentPane = contentPane;
this.title = title;
setRestoreSize(restoreSize);
desktopPane = new JLayeredPane();
setTitleBarType(TinyPane.FULL_BAR);
resizable = true;
listenToBorder = true;
note = "";
decoration = new ArrayList();
}
/**
* Restores the original view.
*
* @param uiDelegate The frame's View. We need it here to decorate the
* desktops. Mustn't be <code>null</code>.
*/
void restoreDisplay(TinyPaneUI uiDelegate)
{
contentPane.removeAll();
if (singleViewMode) {
if (singleViewDesktop != null)
contentPane.add(
uiDelegate.decorateDesktopPane(singleViewDesktop),
BorderLayout.CENTER);
} else
contentPane.add(uiDelegate.decorateDesktopPane(desktopPane),
BorderLayout.CENTER);
contentPane.validate();
contentPane.repaint();
}
/**
* Tells if we're in single or multi-view mode.
*
* @return See above.
*/
boolean isSingleViewMode() { return singleViewMode; }
/**
* Sets the frame's view mode.
*
* @param singleViewMode <code>true</code> to switch to the single-view
* mode, <code>false</code> to return to the default
* multi-view mode.
* @param uiDelegate The frame's View. We need it here to decorate the
* desktops. Mustn't be <code>null</code>.
*/
void setSingleViewMode(boolean singleViewMode, TinyPaneUI uiDelegate)
{
if (uiDelegate == null)
throw new NullPointerException("No UI delegate.");
this.singleViewMode = singleViewMode;
if (singleViewMode) { //Create the single-view tmp desktop.
singleViewDesktop = new JLayeredPane();
contentPane.removeAll();
contentPane.add(
uiDelegate.decorateDesktopPane(singleViewDesktop),
BorderLayout.CENTER);
//Make the first child (if any) of the internal desktop the
//current child view.
setChildView(getFirstChild(desktopPane));
} else { //Normal multi-view mode, restore the internal desktop.
contentPane.removeAll();
contentPane.add(
uiDelegate.decorateDesktopPane(desktopPane),
BorderLayout.CENTER);
//Return the current child view to the internal desktop.
setChildView(null);
singleViewDesktop = null;
}
}
/**
* Sets the given component in the single-view desktop.
* This method does nothing if the frame is not in single-view mode.
* Otherwise, the passed component is assumed to be a child of the
* internal desktop or is <code>null</code> if the single-view desktop
* has to be cleared.
*
* @param child The child component.
*/
void setChildView(Component child)
{
Component oldChild = getChildView();
if (oldChild != null) { //Return it to the internal desktop.
singleViewDesktop.remove(oldChild);
desktopPane.add(oldChild);
oldChild.setBounds(childViewBounds);
if (oldChild instanceof TinyPane) {
TinyPane tf = (TinyPane) oldChild;
tf.setTitleBarType(childViewTitleBarType);
tf.setCollapsed(childViewCollapsed);
if (child == null) {
setParentSizeForMultiView(tf.getParent());
//contentPane.validate();
//contentPane.repaint();
}
}
}
if (isChild(child)) { //We've got a new child to display.
desktopPane.remove(child);
singleViewDesktop.add(child);
childViewBounds = child.getBounds();
//was new Dimension(childViewBounds.width, childViewBounds.height)
singleViewDesktop.setPreferredSize(child.getPreferredSize());
if (child instanceof TinyPane) {
TinyPane tf = (TinyPane) child;
childViewTitleBarType = tf.getTitleBarType();
childViewCollapsed = tf.isCollapsed();
tf.setTitleBarType(TinyPane.HEADER_BAR);
tf.setCollapsed(false);
setParentSizeForSingleView(tf.getParent());
}
//Need to set the size of the child after setting the title bar.
Dimension d = child.getPreferredSize();
child.setBounds(2, 2, d.width, d.height);
//Do it this way otherwise the methods are called at init time.
}
}
/**
* Returns the component that is showing in the single-view desktop when
* in single-view mode.
* The returned value will always be <code>null</code> if the frame is
* not in single-view mode. However, it can also be <code>null</code>
* if the frame is in single-view mode but no components were added to
* the internal desktop.
*
* @return The child view, that is the component that is showing in the
* single-view desktop when in single-view mode.
*/
Component getChildView() { return getFirstChild(singleViewDesktop); }
/**
* Returns the title of this component.
*
* @return See below.
*/
String getTitle() { return title; }
/**
* Sets the title of this component.
*
* @param title The title to set.
*/
void setTitle(String title) { this.title = title; }
/**
* Returns the note added to the <code>TitleBar</code>.
*
* @return See above.
*/
String getNote() { return note; }
/**
* Sets the note added to the <code>TitleBar</code>.
*
* @param note The note to set.
*/
void setNote(String note) { this.note = note; }
/**
* Returns the title bar type.
*
* @return One of the constants defined by {@link TinyPane}.
*/
int getTitleBarType() { return titleBarType; }
/**
* Sets the title bar type.
* Does nothing if <code>type</code> is not a valid one.
*
* @param type One of the constants defined by {@link TinyPane}.
* @return <code>true</code> if <code>type</code> is a valid flag and so
* it has been set; <code>false</code> if <code>type</code> is
* not a valid flag and so it has been ignored.
*/
boolean setTitleBarType(int type)
{
switch (type) {
case TinyPane.FULL_BAR:
case TinyPane.STATIC_BAR:
case TinyPane.SMALL_BAR:
case TinyPane.HEADER_BAR:
case TinyPane.NO_BAR:
case TinyPane.SMALL_TITLE_BAR:
titleBarType = type;
return true;
}
return false;
}
/**
* Returns the bounds of the area, within the internal desktop, which is
* occupied by the contained components.
*
* @return See above.
*/
Rectangle getContentsBounds()
{
Component[] comp = desktopPane.getComponents();
int x = 0, y = 0, //Top left corner coordinates.
br_x = 0, br_y = 0; //Bottom right corner coordinates.
Rectangle bounds;
for (int i = 0; i < comp.length; ++i) {
bounds = comp[i].getBounds();
x = Math.min(x, bounds.x);
y = Math.min(y, bounds.y);
br_x = Math.max(br_x, bounds.x+bounds.width);
br_y = Math.max(br_y, bounds.y+bounds.height);
}
return new Rectangle(x, y, br_x-x, br_y-y);
}
/**
* Returns the restore size.
*
* @return See above.
*/
Dimension getRestoreSize() { return restoreSize; }
/**
* Sets the restore size.
*
* @param rs The restore size. Mustn't be <code>null</code>.
*/
void setRestoreSize(Dimension rs)
{
if (rs == null) throw new NullPointerException("No restore size.");
this.restoreSize = rs;
}
/**
* Returns the frame's internal desktop.
*
* @return See above.
*/
JComponent getDesktopPane() { return desktopPane; }
/**
* Returns the highlight color.
*
* @return See aoove.
*/
Color getHighlight() { return highlight; }
/**
* Returns the highlight color.
*
* @return See aoove.
*/
Color getPreviousHighlight() { return highlightHistory; }
/**
* Sets the highlight color.
*
* @param highlight The color to set.
*/
void setHighlight(Color highlight)
{
highlightHistory = getHighlight();
this.highlight = highlight;
}
/**
* Returns the collapsed state.
*
* @return See above.
*/
boolean isCollapsed() { return collapsed; }
/**
* Sets the collapsed state.
*
* @param collapsed The state to set.
*/
void setCollapsed(boolean collapsed) { this.collapsed = collapsed; }
/**
* Sets the resizable state.
*
* @param resizable The state to set.
*/
void setResizable(boolean resizable) { this.resizable = resizable; }
/**
* Returns the resizable state.
*
* @return See above.
*/
boolean isResizable() { return resizable; }
/**
* Sets the frame's icon.
*
* @param frameIcon The icon to set.
*/
void setFrameIcon(Icon frameIcon) { this.frameIcon = frameIcon; }
/**
* Returns the frame's icon.
*
* @return See above.
*/
Icon getFrameIcon() { return frameIcon; }
/**
* Installs a border listener if <code>true</code>, removes
* it if <code>false</code>.
*
* @param listenToBorder The border listener flag.
*/
void setListenToBorder(boolean listenToBorder)
{
this.listenToBorder = listenToBorder;
}
/**
* Returns the border listener flag.
*
* @return See below.
*/
boolean isListenToBorder() { return listenToBorder; }
/**
* Returns the frame's container.
*
* @return See above.
*/
Container getContentPane() { return contentPane; }
/**
* Returns the collection of buttons to add to the <code>TitleBar</code>.
*
* @return See above.
*/
List getDecoration() { return decoration; }
/**
* Sets the collection of buttons to add to the <code>TitleBar</code>.
*
* @param decoration The collection to set.
*/
void setDecoration(List decoration) { this.decoration = decoration; }
/**
* Returns the closed state.
*
* @return See above.
*/
boolean isClosed() { return closed; }
/**
* Sets the closed state.
*
* @param closed The state to set.
*/
void setClosed(boolean closed) { this.closed = closed; }
}