1. /* ===========================================================
  2. * JFreeChart : a free chart library for the Java(tm) platform
  3. * ===========================================================
  4. *
  5. * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
  6. *
  7. * Project Info: http://www.jfree.org/jfreechart/index.html
  8. *
  9. * This library is free software; you can redistribute it and/or modify it
  10. * under the terms of the GNU Lesser General Public License as published by
  11. * the Free Software Foundation; either version 2.1 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This library is distributed in the hope that it will be useful, but
  15. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  16. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
  17. * License for more details.
  18. *
  19. * You should have received a copy of the GNU Lesser General Public License
  20. * along with this library; if not, write to the Free Software Foundation,
  21. * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  22. *
  23. * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
  24. * in the United States and other countries.]
  25. *
  26. * --------------------
  27. * FlowArrangement.java
  28. * --------------------
  29. * (C) Copyright 2004, 2005, by Object Refinery Limited.
  30. *
  31. * Original Author: David Gilbert (for Object Refinery Limited);
  32. * Contributor(s): -;
  33. *
  34. * $Id: FlowArrangement.java,v 1.9 2005/03/08 11:37:04 mungady Exp $
  35. *
  36. * Changes:
  37. * --------
  38. * 22-Oct-2004 : Version 1 (DG);
  39. * 04-Feb-2005 : Implemented equals() and made serializable (DG);
  40. * 08-Feb-2005 : Updated for changes in RectangleConstraint (DG);
  41. *
  42. */
  43. package org.jfree.chart.block;
  44. import java.awt.Graphics2D;
  45. import java.awt.geom.Rectangle2D;
  46. import java.io.Serializable;
  47. import java.util.ArrayList;
  48. import java.util.List;
  49. import org.jfree.ui.HorizontalAlignment;
  50. import org.jfree.ui.Size2D;
  51. import org.jfree.ui.VerticalAlignment;
  52. /**
  53. * Arranges blocks in a flow layout. This class is immutable.
  54. */
  55. public class FlowArrangement implements Arrangement, Serializable {
  56. /** The horizontal alignment of blocks. */
  57. private HorizontalAlignment horizontalAlignment;
  58. /** The vertical alignment of blocks within each row. */
  59. private VerticalAlignment verticalAlignment;
  60. /** The horizontal gap between items within rows. */
  61. private double horizontalGap;
  62. /** The vertical gap between rows. */
  63. private double verticalGap;
  64. /**
  65. * Creates a new instance.
  66. */
  67. public FlowArrangement() {
  68. this(HorizontalAlignment.CENTER, VerticalAlignment.CENTER, 2.0, 2.0);
  69. }
  70. /**
  71. * Creates a new instance.
  72. *
  73. * @param hAlign the horizontal alignment (currently ignored).
  74. * @param vAlign the vertical alignment (currently ignored).
  75. * @param hGap the horizontal gap.
  76. * @param vGap the vertical gap.
  77. */
  78. public FlowArrangement(HorizontalAlignment hAlign, VerticalAlignment vAlign,
  79. double hGap, double vGap) {
  80. this.horizontalAlignment = hAlign;
  81. this.verticalAlignment = vAlign;
  82. this.horizontalGap = hGap;
  83. this.verticalGap = vGap;
  84. }
  85. /**
  86. * Adds a block to be managed by this instance. This method is usually
  87. * called by the {@link BlockContainer}, you shouldn't need to call it
  88. * directly.
  89. *
  90. * @param block the block.
  91. * @param key a key that controls the position of the block.
  92. */
  93. public void add(Block block, Object key) {
  94. // since the flow layout is relatively straightforward,
  95. // no information needs to be recorded here
  96. }
  97. /**
  98. * Calculates and sets the bounds of all the items in the specified
  99. * container, subject to the given constraint. The <code>Graphics2D</code>
  100. * can be used by some items (particularly items containing text) to
  101. * calculate sizing parameters.
  102. *
  103. * @param container the container whose items are being arranged.
  104. * @param constraint the size constraint.
  105. * @param g2 the graphics device.
  106. *
  107. * @return The size of the container after arrangement of the contents.
  108. */
  109. public Size2D arrange(BlockContainer container,
  110. RectangleConstraint constraint, Graphics2D g2) {
  111. LengthConstraintType w = constraint.getWidthConstraintType();
  112. LengthConstraintType h = constraint.getHeightConstraintType();
  113. if (w == LengthConstraintType.NONE) {
  114. if (h == LengthConstraintType.NONE) {
  115. return arrangeNN(container, g2);
  116. }
  117. else if (h == LengthConstraintType.FIXED) {
  118. throw new RuntimeException("Not implemented.");
  119. }
  120. else if (h == LengthConstraintType.RANGE) {
  121. throw new RuntimeException("Not implemented.");
  122. }
  123. }
  124. else if (w == LengthConstraintType.FIXED) {
  125. if (h == LengthConstraintType.NONE) {
  126. return arrangeFN(container, constraint, g2);
  127. }
  128. else if (h == LengthConstraintType.FIXED) {
  129. throw new RuntimeException("Not implemented.");
  130. }
  131. else if (h == LengthConstraintType.RANGE) {
  132. throw new RuntimeException("Not implemented.");
  133. }
  134. }
  135. else if (w == LengthConstraintType.RANGE) {
  136. if (h == LengthConstraintType.NONE) {
  137. return arrangeRN(container, constraint, g2);
  138. }
  139. else if (h == LengthConstraintType.FIXED) {
  140. return arrangeRF(container, constraint, g2);
  141. }
  142. else if (h == LengthConstraintType.RANGE) {
  143. return arrangeRR(container, constraint, g2);
  144. }
  145. }
  146. return new Size2D(); // TODO: complete this
  147. }
  148. /**
  149. * Arranges the blocks in the container with a fixed width and no height
  150. * constraint.
  151. *
  152. * @param container the container.
  153. * @param constraint the constraint.
  154. * @param g2 the graphics device.
  155. *
  156. * @return The size.
  157. */
  158. protected Size2D arrangeFN(BlockContainer container,
  159. RectangleConstraint constraint, Graphics2D g2) {
  160. List blocks = container.getBlocks();
  161. double width = constraint.getWidth();
  162. double x = 0.0;
  163. double y = 0.0;
  164. double maxHeight = 0.0;
  165. List itemsInRow = new ArrayList();
  166. for (int i = 0; i < blocks.size(); i++) {
  167. Block block = (Block) blocks.get(i);
  168. Size2D size = block.arrange(g2, RectangleConstraint.NONE);
  169. if (x + size.width <= width) {
  170. itemsInRow.add(block);
  171. block.setBounds(
  172. new Rectangle2D.Double(x, y, size.width, size.height)
  173. );
  174. x = x + size.width + this.horizontalGap;
  175. maxHeight = Math.max(maxHeight, size.height);
  176. }
  177. else {
  178. if (itemsInRow.isEmpty()) {
  179. // place in this row (truncated) anyway
  180. block.setBounds(
  181. new Rectangle2D.Double(
  182. x, y, Math.min(size.width, width - x), size.height
  183. )
  184. );
  185. x = 0.0;
  186. y = y + size.height + this.verticalGap;
  187. }
  188. else {
  189. // start new row
  190. itemsInRow.clear();
  191. x = 0.0;
  192. y = y + maxHeight + this.verticalGap;
  193. maxHeight = size.height;
  194. block.setBounds(
  195. new Rectangle2D.Double(
  196. x, y, Math.min(size.width, width), size.height
  197. )
  198. );
  199. x = size.width + this.horizontalGap;
  200. itemsInRow.add(block);
  201. }
  202. }
  203. }
  204. return new Size2D(constraint.getWidth(), y + maxHeight);
  205. }
  206. /**
  207. * Arranges the blocks in the container with a fixed with and a range
  208. * constraint on the height.
  209. *
  210. * @param container the container.
  211. * @param constraint the constraint.
  212. * @param g2 the graphics device.
  213. *
  214. * @return The size following the arrangement.
  215. */
  216. protected Size2D arrangeFR(BlockContainer container,
  217. RectangleConstraint constraint, Graphics2D g2) {
  218. Size2D s = arrangeFN(container, constraint, g2);
  219. if (constraint.getHeightRange().contains(s.height)) {
  220. return s;
  221. }
  222. else {
  223. RectangleConstraint c = constraint.toFixedHeight(
  224. constraint.getHeightRange().constrain(s.getHeight())
  225. );
  226. return arrangeFF(container, c, g2);
  227. }
  228. }
  229. /**
  230. * Arranges the blocks in the container with the overall height and width
  231. * specified as fixed constraints.
  232. *
  233. * @param container the container.
  234. * @param constraint the constraint.
  235. * @param g2 the graphics device.
  236. *
  237. * @return The size following the arrangement.
  238. */
  239. protected Size2D arrangeFF(BlockContainer container,
  240. RectangleConstraint constraint, Graphics2D g2) {
  241. // TODO: implement this properly
  242. return arrangeFN(container, constraint, g2);
  243. }
  244. /**
  245. * Arranges the blocks with the overall width and height to fit within
  246. * specified ranges.
  247. *
  248. * @param container the container.
  249. * @param constraint the constraint.
  250. * @param g2 the graphics device.
  251. *
  252. * @return The size after the arrangement.
  253. */
  254. protected Size2D arrangeRR(BlockContainer container,
  255. RectangleConstraint constraint, Graphics2D g2) {
  256. // first arrange without constraints, and see if this fits within
  257. // the required ranges...
  258. Size2D s1 = arrangeNN(container, g2);
  259. if (constraint.getWidthRange().contains(s1.width)) {
  260. return s1; // TODO: we didn't check the height yet
  261. }
  262. else {
  263. RectangleConstraint c = constraint.toFixedWidth(
  264. constraint.getWidthRange().getUpperBound()
  265. );
  266. return arrangeFR(container, c, g2);
  267. }
  268. }
  269. /**
  270. * Arranges the blocks in the container with a range constraint on the
  271. * width and a fixed height.
  272. *
  273. * @param container the container.
  274. * @param constraint the constraint.
  275. * @param g2 the graphics device.
  276. *
  277. * @return The size following the arrangement.
  278. */
  279. protected Size2D arrangeRF(BlockContainer container,
  280. RectangleConstraint constraint, Graphics2D g2) {
  281. Size2D s = arrangeNF(container, constraint, g2);
  282. if (constraint.getWidthRange().contains(s.width)) {
  283. return s;
  284. }
  285. else {
  286. RectangleConstraint c = constraint.toFixedWidth(
  287. constraint.getWidthRange().constrain(s.getWidth())
  288. );
  289. return arrangeFF(container, c, g2);
  290. }
  291. }
  292. /**
  293. * Arranges the block with a range constraint on the width, and no
  294. * constraint on the height.
  295. *
  296. * @param container the container.
  297. * @param constraint the constraint.
  298. * @param g2 the graphics device.
  299. *
  300. * @return The size following the arrangement.
  301. */
  302. protected Size2D arrangeRN(BlockContainer container,
  303. RectangleConstraint constraint, Graphics2D g2) {
  304. // first arrange without constraints, then see if the width fits
  305. // within the required range...if not, call arrangeFN() at max width
  306. Size2D s1 = arrangeNN(container, g2);
  307. if (constraint.getWidthRange().contains(s1.width)) {
  308. return s1;
  309. }
  310. else {
  311. RectangleConstraint c = constraint.toFixedWidth(
  312. constraint.getWidthRange().getUpperBound()
  313. );
  314. return arrangeFN(container, c, g2);
  315. }
  316. }
  317. /**
  318. * Arranges the blocks without any constraints. This puts all blocks
  319. * into a single row.
  320. *
  321. * @param container the container.
  322. * @param g2 the graphics device.
  323. *
  324. * @return The size after the arrangement.
  325. */
  326. protected Size2D arrangeNN(BlockContainer container, Graphics2D g2) {
  327. double x = 0.0;
  328. double width = 0.0;
  329. double maxHeight = 0.0;
  330. List blocks = container.getBlocks();
  331. int blockCount = blocks.size();
  332. if (blockCount > 0) {
  333. Size2D[] sizes = new Size2D[blocks.size()];
  334. for (int i = 0; i < blocks.size(); i++) {
  335. Block block = (Block) blocks.get(i);
  336. sizes[i] = block.arrange(g2, RectangleConstraint.NONE);
  337. width = width + sizes[i].getWidth();
  338. maxHeight = Math.max(sizes[i].height, maxHeight);
  339. block.setBounds(
  340. new Rectangle2D.Double(
  341. x, 0.0, sizes[i].width, sizes[i].height
  342. )
  343. );
  344. x = x + sizes[i].width + this.horizontalGap;
  345. }
  346. if (blockCount > 1) {
  347. width = width + this.horizontalGap * (blockCount - 1);
  348. }
  349. if (this.verticalAlignment != VerticalAlignment.TOP) {
  350. for (int i = 0; i < blocks.size(); i++) {
  351. Block b = (Block) blocks.get(i);
  352. if (this.verticalAlignment == VerticalAlignment.CENTER) {
  353. //TODO: shift block down by half
  354. }
  355. else if (this.verticalAlignment == VerticalAlignment.BOTTOM) {
  356. //TODO: shift block down to bottom
  357. }
  358. }
  359. }
  360. }
  361. return new Size2D(width, maxHeight);
  362. }
  363. /**
  364. * Arranges the blocks with no width constraint and a fixed height
  365. * constraint. This puts all blocks into a single row.
  366. *
  367. * @param container the container.
  368. * @param constraint the constraint.
  369. * @param g2 the graphics device.
  370. *
  371. * @return The size after the arrangement.
  372. */
  373. protected Size2D arrangeNF(BlockContainer container,
  374. RectangleConstraint constraint, Graphics2D g2) {
  375. // TODO: for now we are ignoring the height constraint
  376. return arrangeNN(container, g2);
  377. }
  378. /**
  379. * Clears any cached information.
  380. */
  381. public void clear() {
  382. // no action required.
  383. }
  384. /**
  385. * Tests this instance for equality with an arbitrary object.
  386. *
  387. * @param obj the object (<code>null</code> permitted).
  388. *
  389. * @return A boolean.
  390. */
  391. public boolean equals(Object obj) {
  392. if (obj == this) {
  393. return true;
  394. }
  395. if (!(obj instanceof FlowArrangement)) {
  396. return false;
  397. }
  398. FlowArrangement that = (FlowArrangement) obj;
  399. if (this.horizontalAlignment != that.horizontalAlignment) {
  400. return false;
  401. }
  402. if (this.verticalAlignment != that.verticalAlignment) {
  403. return false;
  404. }
  405. if (this.horizontalGap != that.horizontalGap) {
  406. return false;
  407. }
  408. if (this.verticalGap != that.verticalGap) {
  409. return false;
  410. }
  411. return true;
  412. }
  413. }