I've been working on a custom layout manager for a long time now, and I'm almost done. I call it OrbitLayout and it displays components in concentric circles. I'm itching to show it off, so if you'd like to give it a try, here it is:
To add components, use the following method:
Container con;
Componenent com;
// initialize them, etc
con.setLayout(new OrbitLayout(0,25); // for some spacing
int orbit = (choose an orbit here from 0 to n, where 0 is the center)
con.add(com, new Integer(orbit));
Try adding a few components (buttons work best) to a few orbits (although the center orbit 0 can only hold 1 component).
Please tell me what you think!!!
Micah
<code>
import java.util.*;
import java.awt.*;
/**
* This layout manager is used to display components in concentric circles surrounding
* a central component (or no central component). For the purposes of this
* source file, each circle is an orbit and the components are satellites.
*
* @author Micah Wedemeyer
* @version 1.0
*/
public class OrbitLayout implements LayoutManager2 {
/**
* A constant used to deliniate the central component that all other components orbit.
*/
final public static int CENTER = 0;
/**
* The minimum spacing between components in the same orbit.
*/
private int satGap;
/**
* The minimum spacing between each orbit.
*/
private int orbitGap;
/**
* The current outer-most orbit.
*/
private int outerOrbit;
/**
* A list of orbits. Each element in the list is a list itself of satellites
* in that orbit. The center object is at orbits[0],
* the first orbit at orbits[1], and the nth at orbits[n].
*/
private ArrayList orbits;
/**
* A central component that all the satellites orbit
*/
private Component center;
/**
* Creates an OrbitLayout with the specified spacing.
* @param satGap The minimum distance between satellites in the same orbit.
* @param orbitGap The minimum distance between concentric orbits.
*/
public OrbitLayout(int satGap, int orbitGap) {
this.satGap = satGap;
this.orbitGap = orbitGap;
outerOrbit = 0;
center = null;
orbits = new ArrayList();
orbits.add(new OrbitList(0));
}
/**
* Does not use, must be overridden for interface.
*/
public void addLayoutComponent(
String s, Component c) {
}
/**
* Adds a component to the layout.
* @param c The component to be added.
* @param constraints (an Integer object) The orbit to add the component to.
*/
public void addLayoutComponent(Component c, Object constraints) {
if (constraints instanceof Integer) {
Integer orbit = (Integer)constraints;
addLayoutComponent(c, orbit.intValue());
}
}
/**
* Adds a component to the specified orbit.
* @param comp The component to be added.
* @param orbit The orbit to add the component to.
* @see OrbitLayout#CENTER
*/
public void addLayoutComponent (Component comp, int orbit) {
if (orbit < 0)<br /> throw new IllegalArgumentException("Orbit must be non-negative.");<br /> /* Special Case: The orbit is actually the center. */<br /> if (orbit == CENTER) {<br /> center = comp;<br /> // sets orbits[0] to an OrbitList containing only the center<br /> OrbitList o = new OrbitList(0);<br /> o.add(center);<br /> orbits.set(0, o);<br /> invalidateAllOrbits();<br /> return;<br /> }<br /> /**<br /> * If the new orbit is higher than the outerOrbit, allocate a new ArrayList for<br /> * the new orbit and all intervening orbits. This keeps the orbits List from<br /> * having null slots for empty orbits. This will make iteration easier.<br /> */<br /> if (orbit > outerOrbit) {
for (int i = outerOrbit + 1; i <= orbit; i++)<br /> orbits.add(new OrbitList(i));<br /> outerOrbit = orbit;<br /> }<br /> // Adds the component to its correct orbit.<br /> ((OrbitList)orbits.get(orbit)).add(comp);<br /> // Forces resizing of all external orbits.<br /> invalidateOrbits(orbit);<br /> }<br /> /**<br /> * Removes a component from the layout.<br /> * @param comp The component to be removed from this layout.<br /> */<br /> public void removeLayoutComponent (Component comp) {<br /> if (comp == center) {<br /> center = null;<br /> ((OrbitList)orbits.get(0)).clear();<br /> invalidateAllOrbits();<br /> return;<br /> }<br /> for (Iterator i = orbits.iterator(); i.hasNext(); ) {<br /> OrbitList orb = (OrbitList)i.next();<br /> for (Iterator j = orb.iterator(); j.hasNext(); ) {<br /> Component c = (Component)j.next();<br /> if (c == comp) {<br /> int currentOrbit = orbits.indexOf(orb);<br /> invalidateOrbits(currentOrbit);<br /> j.remove();<br /> return;<br /> }<br /> }<br /> }<br /> }<br /> /**<br /> * =================================<br /> * NEED TO FIX<br /> * ================================<br /> */<br /> public Dimension preferredLayoutSize(Container target) {<br /> // Not sure what to do here, yet.<br /> return target.getPreferredSize();<br /> }<br /> /**<br /> * ===============================<br /> * NEED TO FIX<br /> * ===========================<br /> */<br /> public Dimension minimumLayoutSize(Container target) {<br /> // Not sure what to do here, yet<br /> return target.getMinimumSize();<br /> }<br /> /**<br /> * The maximum size for this layout manager.<br /> * @return the max size for this layout manager.<br /> */<br /> public Dimension maximumLayoutSize(Container target) {<br /> return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);<br /> }<br /> /**<br /> * Returns the alignment along the x axis. This specifies how<br /> * the component would like to be aligned relative to other<br /> * components. The value should be a number between 0 and 1<br /> * where 0 represents alignment along the origin, 1 is aligned<br /> * the furthest away from the origin, 0.5 is centered, etc.<br /> */<br /> public float getLayoutAlignmentX(Container parent) {<br /> return 0.5f;<br /> }<br /> /**<br /> * Returns the alignment along the y axis. This specifies how<br /> * the component would like to be aligned relative to other<br /> * components. The value should be a number between 0 and 1<br /> * where 0 represents alignment along the origin, 1 is aligned<br /> * the furthest away from the origin, 0.5 is centered, etc.<br /> */<br /> public float getLayoutAlignmentY(Container parent) {<br /> return 0.5f;<br /> }<br /> /**<br /> * Invalidates the layout, flushing cached data.<br /> */<br /> public void invalidateLayout(Container parent) {<br /> /* Nothing to do here...I think. */<br /> }<br /> /**<br /> * Lays out the components in the specified container.<br /> * @param parent the container which needs to be laid out.<br /> */<br /> public void layoutContainer (Container parent) {<br /> Insets insets = parent.getInsets();<br /> // In order to make calculations easy, we will assume that (0,0)<br /> // is at the center of the container. We can do conversions at the end.<br /> Point origin = new Point();<br /> origin.x = (parent.getWidth() - insets.left - insets.right) / 2;<br /> origin.y = (parent.getHeight() - insets.top - insets.bottom) / 2;<br /> for (Iterator i = orbits.iterator(); i.hasNext(); ) {<br /> OrbitList currentOrb = (OrbitList)i.next();<br /> if (currentOrb.size() != 0) {<br /> // Calculate the angle in radians between the centerpoints of each<br /> // component. Since there are 2*pi radians in a circle, to evenly space<br /> // the components, we just space them by 2*pi/# of components.<br /> // Additional spacing (satGap) must be added later<br /> double spaceAngle = (Math.PI * 2) / currentOrb.size();<br /> // A multiplier for the spaceAngle. The first component will be placed<br /> // at Theta=0, the second at Theta=(spaceAngle), the third at<br /> // Theta=(spaceAngle * 2), etc.<br /> int angleMult = 0;<br /> for (Iterator j = currentOrb.iterator(); j.hasNext(); ) {<br /> Component c = (Component)j.next();<br /> polarLayout(c, currentOrb.getRadius(), spaceAngle * angleMult, origin);<br /> angleMult++;<br /> }<br /> }<br /> }<br /> }<br /> /**<br /> * Lays out a single component in the container using polar to cartesian<br /> * coordinate conversion.<br /> * @param c The component to be laid out.<br /> * @param radius The radius of the centerpoint of the component.<br /> * @param theta The angle from the origin (center of container) of the centerpoint<br /> * of the component.<br /> * @param origin The centerpoint of the container.<br /> */<br /> private void polarLayout(Component c, int radius, double theta, Point origin) {<br /> Dimension d = c.getPreferredSize();<br /> // Determine the centerpoint for the component in Cartesian coordinates<br /> // using the standard formula for polar -> Cartesian conversion.
Point centerpoint = new Point();
centerpoint.x = (int)(radius * Math.cos(theta)) + origin.x;
centerpoint.y = (int)(radius * Math.sin(theta)) + origin.y;
// Use the preferred size and the centerpoint to calculate the top left
// corner of where the component should be laid out.
Rectangle bounds = new Rectangle();
bounds.x = centerpoint.x - (d.width / 2);
bounds.y = centerpoint.y - (d.height / 2);
bounds.width = d.width;
bounds.height = d.height;
c.setBounds(bounds);
}
/**
* Invalidates all orbits at and above a specified orbit.
* This will force a recalculation at the next sizing or layout.
* This method is called when an orbit is changed (satellite added or removed).
* Each orbit above a changed orbit must also be recalculated, so this invalidates
* all orbits above.
*/
private void invalidateOrbits(int orb) {
for (ListIterator i = orbits.listIterator(orb); i.hasNext(); ) {
OrbitList o = (OrbitList)i.next();
o.invalidate();
}
}
/**
* Invalidates the radius of every orbit.
* @see invalidateRadii(int)
*/
private void invalidateAllOrbits() {
invalidateOrbits(0);
}
/**
* Draws circles to represent the orbits.
* @param parent The container that is being laid out.
*/
public void drawOrbits(Container parent) {
Insets insets = parent.getInsets();
Graphics g = parent.getGraphics();
// In order to make calculations easy, we will assume that (0,0)
// is at the center of the container. We can do conversions at the end.
Point origin = new Point();
origin.x = (parent.getWidth() - insets.left - insets.right) / 2;
origin.y = (parent.getHeight() - insets.top - insets.bottom) / 2;
for (Iterator i = orbits.iterator(); i.hasNext(); ) {
OrbitList currentOrb = (OrbitList)i.next();
int r = currentOrb.getRadius();
Point topLeft = new Point();
topLeft.x = origin.x - r;
topLeft.y = origin.y - r;
g.drawOval(topLeft.x, topLeft.y, r * 2, r * 2);
}
// Draw a small orbit for the centerpoint
g.fillOval(origin.x - 2, origin.y - 2, 4, 4);
}
/**
*
A class to represent each orbit. Each OrbitList is a list of satellites,
* plus the radius at which these satellites should be placed. In order to cut
* down execution time, it will not recalculate radius and maxOverlap each time an element is added.
*
*
Since radius is a function of the radii of closer
* orbits, this sets up a recursive relation. In order to escape the nasty time requirements
* of recursive relations, I made it possible to only recalculate the radii of those orbits
* which are invalid.
*
*
It is important to remember that when adding or removing satellites from an
* orbit, the orbit MUST BE invalidated. This will prevent resizing problems.
*
*
NOTE: This is not for a list of orbits, it is to represent a single orbit.
*/
class OrbitList extends ArrayList {
/**
* The orbit that this list represents.
*/
private int orbit;
/**
* The radius of current orbit in pixels.
*/
private int radius;
/**
* This is 1/2 the length of the main diagonal of the largest component in this orbit.
* This is important because this is the maximum amount that extends past an
* orbit. Therefore, if orbits are spaced at least that much, there will be
* no overlap.
*/
private int maxOverlap;
/**
* Tells whether the current radius and maxOverlap is correct.
*/
private boolean valid;
/**
* Creates an empty OrbitList without setting a correct radius. This will
* be done later, during calls to <code>getRadius()</code>.
* @param orbit The orbit to be represented.
*/
public OrbitList(int orbit) {
super();
this.orbit = orbit;
maxOverlap = 0;
radius = 0;
valid = false;
}
/**
* Gets the current valid radius. If the current radius is invalid, it computes
* and returns the valid one.
* NOTE: This is not a simple O(1) get method. There may be time consuming
* calculations involved. I tried to make it as efficient as possible.
* @return The current valid radius.
*/
public int getRadius() {
calculateRadius();
return radius;
}
/**
* Returns the maximum overlap.
* @return The maximum overlap of this orbit.
*/
public int getMaxOverlap() {
if (!isValid())
calculateOverlap();
return maxOverlap;
}
/**
* Calculates and sets the correct radius.
*/
private void calculateRadius() {
// If valid, don't do anything.
if (isValid())
return;
else if (orbit == 0) {
radius = 0;
}
else {
OrbitList previous = (OrbitList)orbits.get(orbit - 1);
radius = previous.getRadius() + previous.getMaxOverlap() + this.getMaxOverlap() + orbitGap;
}
valid = true;
}
/**
* Calculates and sets the maximum overlap of this orbit. This is 1/2 the length
* of the main diagonal of the largest component. This is important because
* this is the maximum amount that extends past an orbit. Therefore, if orbits
* are spaced at least that much, there will be no overlap.
*/
private void calculateOverlap () {
maxOverlap = 0;
for (Iterator i = this.iterator(); i.hasNext(); ) {
Component c = (Component)i.next();
Dimension d = c.getPreferredSize();
int w = d.width;
int h = d.height;
int over = (int)(Math.sqrt((w * w) + (h * h)) / 2);
maxOverlap = Math.max(maxOverlap, over);
}
}
/**
* Invalidates the current orbit, forcing radius and maxOverlap to be recalculated
* during the next call to <code>getRadius()</code> or <code>getMaxOverlap()</code>.
*/
public void invalidate() {
valid = false;
}
/**
* Determines if the current radius and maxOverlap are valid.
* @return true if the radius and maxOverlap are valid, false otherwise.
*/
public boolean isValid() {
return valid;
}
}
}
</code>