/* -*- 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 , 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: * add(aComponent, new Float(.5)) * If no weight is given the default is 1. *

* (Soon to be) Moveable separators can be created by the * createSeparator() * 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); } } }