pjs/grendel/widgets/Splitter.java

515 строки
14 KiB
Java

/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Grendel mail/news client.
*
* The Initial Developer of the Original Code is Netscape Communications
* Corporation. Portions created by Netscape are Copyright (C) 1997
* Netscape Communications Corporation. All Rights Reserved.
*
* Created: Will Scullin <scullin@netscape.com>, 26 Aug 1997.
*/
package grendel.widgets;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.LayoutManager2;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import com.sun.java.swing.JComponent;
import com.sun.java.swing.UIManager;
/**
* This class is used to create a divided pane with moveable dividers (NYI)
* Components can be added with numeric weights that will determine how
* much of the pane they will occupy:
* <code>add(aComponent, new Float(.5))</code>
* If no weight is given the default is 1.
* <p>
* (Soon to be) Moveable separators can be created by the
* <code>createSeparator()</code>
* method. The width of the separator is fixed in pixels.
*
* @author Will Scullin
*/
//
// Splitter Class
//
public class Splitter extends JComponent {
Hashtable fWeights = new Hashtable();
float fTotalWeight = 0.0f;
int fTotalSeparators = 0;
int fOrientation;
int fMinSize = 16;
SplitterSeparatorListener fListener;
/**
* Value to pass for a splitter that stacks components
* vertically.
*/
public static final int VERTICAL = 0;
/**
* Value to pass for a splitter that lays out components
* horizontally.
*/
public static final int HORIZONTAL = 1;
//
// Constructor
//
/**
* Contructs a splitter container with the given orientation.
*/
public Splitter(int aOrientation) {
super();
setLayout(new SplitterLayout());
fOrientation = aOrientation;
fListener = new SplitterSeparatorListener();
updateUI();
}
public void updateUI() {
super.updateUI();
setBackground(UIManager.getColor("control"));
}
/**
* Creates a separator of a given pixel size.
*/
public Component createSeparator(int aSize) {
return new Separator(fOrientation, aSize, false);
}
/**
* Creates an separator of a given pixel size. If aFixed is
* true, the separator is not user movable.
*/
public Component createSeparator(int aSize, boolean aFixed) {
return new Separator(fOrientation, aSize, aFixed);
}
/**
* Returns the weight associated with a given component. This
* number can be used to save and restore layout preferences.
*/
public Float getWeight(Component aComponent) {
return (Float) fWeights.get(aComponent);
}
/**
* Sets the minimum size a component can be sized to.
* Should be greater than zero for best results.
*/
public void setMinSize(int aMinSize) {
fMinSize = aMinSize;
}
/**
* Returns the minimum size a component can be sized to.
*/
public int getMinSize() {
return fMinSize;
}
//
// SplitterLayout
//
class SplitterLayout implements LayoutManager2 {
public void addLayoutComponent(String aString, Component aComponent) {
addLayoutComponent(aComponent, new Float(1.0));
}
public void addLayoutComponent(Component aComponent, Object aObject) {
if (aComponent instanceof Separator) {
Separator sep = (Separator) aComponent;
sep.addSeparatorListener(fListener);
fTotalSeparators += sep.getSeparatorSize();
} else {
Number weight = null;
if (aObject instanceof Number) {
weight = new Float(((Number) aObject).floatValue());
} else {
weight = new Float(1.0f);
}
fTotalWeight += weight.floatValue();
fWeights.put(aComponent, weight);
}
}
public void removeLayoutComponent(Component aComponent) {
if (aComponent instanceof Separator) {
Separator sep = (Separator) aComponent;
sep.removeSeparatorListener(fListener);
fTotalSeparators -= sep.getSeparatorSize();
} else {
Float weight = (Float) fWeights.get(aComponent);
fWeights.remove(aComponent);
fTotalWeight -= weight.floatValue();
}
}
public void layoutContainer(Container aParent) {
int i, count = aParent.getComponentCount();
Component components[] = aParent.getComponents();
Dimension parentSize = aParent.getSize();
Insets insets = aParent.getInsets();
parentSize.width -= insets.left + insets.right;
parentSize.height -= insets.top + insets.bottom;
Dimension availableSize = new Dimension(parentSize);
if (fOrientation == Splitter.VERTICAL) {
availableSize.height -= fTotalSeparators;
} else {
availableSize.width -= fTotalSeparators;
}
Point pos = new Point(insets.left, insets.right);
Dimension size = new Dimension(availableSize);
for (i = 0; i < count; i++) {
if (i == count - 1) {
if (fOrientation == Splitter.VERTICAL) {
size.height = parentSize.height - pos.y;
} else {
size.width = parentSize.width - pos.x;
}
} else {
if (components[i] instanceof Separator) {
if (fOrientation == Splitter.VERTICAL) {
size.height = ((Separator) components[i]).getSeparatorSize();
} else {
size.width = ((Separator) components[i]).getSeparatorSize();
}
} else {
float weight = ((Number) fWeights.get(components[i])).floatValue();
if (fOrientation == Splitter.VERTICAL) {
size.height = (int)(parentSize.height * (weight / fTotalWeight));
} else {
size.width = (int)(parentSize.width * (weight / fTotalWeight));
}
}
}
components[i].setLocation(pos);
components[i].setSize(size);
if (fOrientation == Splitter.VERTICAL) {
pos.y += size.height;
} else {
pos.x += size.width;
}
}
}
public float getLayoutAlignmentX(Container aParent) {
return 0.5f;
}
public float getLayoutAlignmentY(Container aParent) {
return 0.5f;
}
public void invalidateLayout(Container aParent) {
}
public Dimension preferredLayoutSize(Container aParent) {
return aParent.getSize();
}
public Dimension minimumLayoutSize(Container aParent) {
return preferredLayoutSize(aParent);
}
public Dimension maximumLayoutSize(Container aParent) {
return preferredLayoutSize(aParent);
}
}
class SplitterSeparatorListener implements SeparatorListener {
int fOldPos = -1;
public boolean separatorMoving(Separator aSep, int aDelta) {
Dimension size = aSep.getParent().getSize();
Graphics g = getGraphics();
int pos;
if (fOrientation == VERTICAL) {
pos = aSep.getY() + aDelta;
if (pos < fMinSize)
pos = fMinSize;
else if (pos > (size.height - aSep.getHeight() - fMinSize))
pos = size.height - aSep.getHeight() - fMinSize;
} else {
pos = aSep.getX() + aDelta;
if (pos < fMinSize)
pos = fMinSize;
else if (pos > (size.width - aSep.getWidth() - fMinSize))
pos = size.width - aSep.getWidth() - fMinSize;
}
drawDividerLine(g, pos, aSep.getSeparatorSize());
return true;
}
public void separatorMoved(Separator aSep, int aDelta) {
Graphics g = getGraphics();
drawDividerLine(g, -1, aSep.getSeparatorSize());
Component components[] = getComponents();
int count = getComponentCount();
float beforeValue = 0, afterValue = 0;
int beforeSize = 0, afterSize = 0;
boolean before = true;
for (int i = 0; i < count; i++) {
if (components[i] instanceof Separator) {
if (components[i] == aSep) {
before = false;
}
} else {
Dimension dim = components[i].getSize();
int size = fOrientation == VERTICAL ?
dim.height :
dim.width;
float value = ((Number) fWeights.get(components[i])).floatValue();
if (before) {
beforeValue += value;
beforeSize += size;
} else {
afterValue += value;
afterSize += size;
}
}
}
Dimension size = aSep.getParent().getSize();
int pos;
if (fOrientation == VERTICAL) {
pos = aSep.getY() + aDelta;
if (pos < fMinSize)
pos = fMinSize;
else if (pos > (size.height - aSep.getHeight() - fMinSize))
pos = size.height - aSep.getHeight() - fMinSize;
aDelta = pos - aSep.getY();
} else {
pos = aSep.getX() + aDelta;
if (pos < fMinSize)
pos = fMinSize;
else if (pos > (size.width - aSep.getWidth() - fMinSize))
pos = size.width - aSep.getWidth() - fMinSize;
aDelta = pos - aSep.getX();
}
float beforeDelta = (float) (beforeSize + aDelta) / (float) beforeSize;
float afterDelta = (float) (afterSize - aDelta) / (float) afterSize;
before = true;
fTotalWeight = 0;
for (int i = 0; i < count; i++) {
if (components[i] instanceof Separator) {
if (components[i] == aSep) {
before = false;
}
} else {
float value = ((Number) fWeights.get(components[i])).floatValue();
if (before) {
value *= beforeDelta;
} else {
value *= afterDelta;
}
fWeights.put(components[i], new Float(value));
fTotalWeight += value;
}
}
invalidate();
validate();
}
void drawDividerLine(Graphics g, int aPos, int aSize) {
g.setXORMode(Color.white);
g.setColor(Color.black);
if (fOldPos != -1) {
if (fOrientation == VERTICAL) {
g.fillRect(0, fOldPos, getWidth(), aSize);
} else {
g.fillRect(fOldPos, 0, aSize, getHeight());
}
}
if (aPos != -1) {
if (fOrientation == VERTICAL) {
g.fillRect(0, aPos, getWidth(), aSize);
} else {
g.fillRect(aPos, 0, aSize, getHeight());
}
}
fOldPos = aPos;
g.setPaintMode();
}
}
}
//
// SeparatorListener
//
interface SeparatorListener {
public boolean separatorMoving(Separator aSep, int aDelta);
public void separatorMoved(Separator aSep, int aDelta);
}
//
// Separator Class
//
class Separator extends JComponent {
int fOrientation;
int fSize;
SplitterMouseListener fMouseListener;
SeparatorListener fListener;
class SplitterMouseListener extends MouseAdapter implements MouseMotionListener {
int hitX, hitY;
boolean fDragging;
int fDelta;
public void mouseEntered(MouseEvent aEvent) {
setCursor(Cursor.getPredefinedCursor(fOrientation == Splitter.VERTICAL ?
Cursor.N_RESIZE_CURSOR :
Cursor.E_RESIZE_CURSOR));
}
public void mousePressed(MouseEvent aEvent) {
// XXX AWT bug work around
// mousePressed gets called again if the cursor leaves the
// frame. grr.
if (!fDragging) {
hitX = aEvent.getX();
hitY = aEvent.getY();
fDelta = 0;
fDragging = true;
}
}
public void mouseReleased(MouseEvent aEvent) {
notifyMoved(fDelta);
fDragging = false;
}
public void mouseMoved(MouseEvent aEvent) {
}
public void mouseDragged(MouseEvent aEvent) {
int delta = fOrientation == Splitter.VERTICAL ?
aEvent.getY() - hitY :
aEvent.getX() - hitX;
if (notifyMoving(delta)) {
fDelta = delta;
}
}
}
public Separator(int aOrientation, int aSize, boolean aFixed) {
fOrientation = aOrientation;
fSize = aSize;
if (!aFixed) {
fMouseListener = new SplitterMouseListener();
addMouseListener(fMouseListener);
addMouseMotionListener(fMouseListener);
}
}
public int getSeparatorSize() {
return fSize;
}
public void paint(Graphics g) {
Color bg = getBackground();
if (bg != null) {
Rectangle rect = getVisibleRect();
g.setColor(bg);
g.fillRect(rect.x, rect.y, rect.width, rect.height);
}
}
public void addSeparatorListener(SeparatorListener aListener) {
fListener = aListener;
}
public void removeSeparatorListener(SeparatorListener aListener) {
if (aListener == fListener) {
aListener = null;
}
}
boolean notifyMoving(int aDelta) {
if (fListener != null) {
return fListener.separatorMoving(this, aDelta);
}
return true;
}
void notifyMoved(int aDelta) {
if (fListener != null) {
fListener.separatorMoved(this, aDelta);
}
}
}