| CellSpanLayoutDemo.java |
1 /****************************************************************************
2 * This demo file is part of yFiles for Java 2.14.
3 * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4 * 72070 Tuebingen, Germany. All rights reserved.
5 *
6 * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7 * of demo files in source code or binary form, with or without
8 * modification, is not permitted.
9 *
10 * Owners of a valid software license for a yFiles for Java version that this
11 * demo is shipped with are allowed to use the demo source code as basis
12 * for their own yFiles for Java powered applications. Use of such programs is
13 * governed by the rights and conditions as set out in the yFiles for Java
14 * license agreement.
15 *
16 * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19 * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 ***************************************************************************/
28 package demo.layout.hierarchic;
29
30 import demo.view.DemoBase;
31 import y.io.GraphMLIOHandler;
32 import y.view.EditMode;
33 import y.view.Graph2D;
34 import y.view.Graph2DView;
35 import y.view.Graph2DViewActions;
36 import y.view.MoveSelectionMode;
37 import y.view.hierarchy.HierarchyManager;
38 import y.view.tabular.TableGroupNodeRealizer;
39 import y.view.tabular.TableGroupNodeRealizer.Column;
40 import y.view.tabular.TableGroupNodeRealizer.Row;
41 import y.view.tabular.TableGroupNodeRealizer.Table;
42
43 import java.awt.Color;
44 import java.awt.EventQueue;
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.Collections;
48 import java.util.Comparator;
49 import java.util.HashMap;
50 import java.util.HashSet;
51 import java.util.Iterator;
52 import java.util.Locale;
53 import java.util.Map;
54 import javax.swing.Action;
55 import javax.swing.ActionMap;
56 import javax.swing.JMenu;
57 import javax.swing.JMenuBar;
58 import javax.swing.JToggleButton;
59 import javax.swing.JToolBar;
60
61 /**
62 * Demonstrates {@link y.layout.hierarchic.IncrementalHierarchicLayouter}'s
63 * support for multi-cells in {@link y.layout.grid.PartitionGrid}s.
64 * <p>
65 * Multi-cells impose less restrictions on node placement than normal cells:
66 * A node that belongs to a multi-cell may be placed in each of the multi-cell's
67 * columns and rows.
68 * </p><p>
69 * A new cell span may be created by dragging the mouse across the cells to
70 * combine while holding down <code>CTRL</code>.
71 * An existing span may be removed by dragging the mouse across the combined
72 * cells while holding down <code>CTRL</code> and <code>ALT</code>.
73 * </p>
74 *
75 */
76 public class CellSpanLayoutDemo extends DemoBase {
77 // registers the configuration used for cell designer table nodes
78 static {
79 CellSpanRealizerFactory.initConfigurations();
80 }
81
82 /**
83 * Initializes a new <code>CellSpanLayoutDemo</code> instance.
84 * Displays a sample diagram by default.
85 */
86 public CellSpanLayoutDemo() {
87 this(null);
88 }
89
90 /**
91 * Initializes a new <code>CellSpanLayoutDemo</code> instance.
92 * Displays sample 1 and the documentation referenced by the given file path.
93 * @param helpFilePath the file path for the HTML documentation to display.
94 */
95 public CellSpanLayoutDemo( final String helpFilePath ) {
96 initGraph(view.getGraph2D());
97 addHelpPane(helpFilePath);
98 }
99
100 /**
101 * Creates a new {@link HierarchyManager} for the displayed graph.
102 */
103 protected void initialize() {
104 new HierarchyManager(view.getGraph2D());
105 }
106
107 /**
108 * Creates a custom delete selection action that prevents table nodes from
109 * being deleted.
110 */
111 protected Action createDeleteSelectionAction() {
112 return CellSpanActionFactory.newDeleteSelection(view);
113 }
114
115 /**
116 * Registers keyboard actions.
117 * Overwritten to remove grouping related keyboard shortcuts.
118 * This demo requires exactly two hierarchy levels with a single top-level
119 * table node and an arbitrary number of normal child nodes.
120 * With grouping keyboard shortcuts, this assumption would be easily violated.
121 */
122 protected void registerViewActions() {
123 super.registerViewActions();
124
125 final ActionMap amap = view.getCanvasComponent().getActionMap();
126 if (amap != null) {
127 final Object[] keys = {
128 Graph2DViewActions.CLOSE_GROUPS,
129 Graph2DViewActions.OPEN_FOLDERS,
130 Graph2DViewActions.GROUP_SELECTION,
131 Graph2DViewActions.FOLD_SELECTION,
132 Graph2DViewActions.UNGROUP_SELECTION,
133 Graph2DViewActions.UNFOLD_SELECTION,
134 };
135 for (int i = 0; i < keys.length; ++i) {
136 amap.remove(keys[i]);
137 }
138 }
139 }
140
141 /**
142 * Creates a custom mode for interactive editing.
143 * This custom mode ...
144 * <ul>
145 * <li>
146 * ... uses marquee selection with <code>CTRL</code> pressed to color
147 * table cells
148 * </li><li>
149 * ... uses marquee selection with <code>CTRL</code> and <code>ALT</code>
150 * pressed to reset the color of table cells
151 * </li><li>
152 * ... provides a custom context menu with actions for creating and
153 * removing table columns and rows
154 * </li><li>
155 * ... prevents node creation outside of group/table nodes
156 * </li>
157 * </ul>
158 */
159 protected EditMode createEditMode() {
160 final EditMode editMode = configureEditMode(
161 CellSpanControllerFactory.newCellEditMode());
162
163 // prevents nodes from being moved out of the demo's table node
164 final MoveSelectionMode msm = new MoveSelectionMode();
165 msm.setGroupReassignmentEnabled(false);
166 editMode.setMoveSelectionMode(msm);
167
168 // enable resizing table columns and rows
169 editMode.getMouseInputMode().setNodeSearchingEnabled(true);
170
171 // enable node creation when clicking on a table/group node
172 // necessary because CellEditMode prevents node creation when clicking
173 // on empty space
174 editMode.setChildNodeCreationEnabled(true);
175
176 return editMode;
177 }
178
179 /**
180 * Creates a {@link GraphMLIOHandler} that supports reading/writing
181 * the individual background colors of table nodes stored in
182 * {@link CellColorManager} instances.
183 */
184 protected GraphMLIOHandler createGraphMLIOHandler() {
185 return CellSpanIoSupport.configure(super.createGraphMLIOHandler());
186 }
187
188 /**
189 * Turns off clipboard support.
190 * This demo assumes a single top-level table node. With clipboard support,
191 * this assumption would be easily violated.
192 */
193 protected boolean isClipboardEnabled() {
194 return false;
195 }
196
197 /**
198 * Provides controls for displaying different sample diagrams.
199 */
200 protected JMenuBar createMenuBar() {
201 final JMenu jm = new JMenu("Samples");
202 jm.add(CellSpanActionFactory.newSampleAction(
203 "Sample 1", this, "resource/CellSpanLayoutDemoS01.graphml"));
204 jm.add(CellSpanActionFactory.newSampleAction(
205 "Sample 2", this, "resource/CellSpanLayoutDemoS02.graphml"));
206 jm.add(CellSpanActionFactory.newSampleAction(
207 "Sample 3", this, "resource/CellSpanLayoutDemoS03.graphml"));
208 jm.add(CellSpanActionFactory.newSampleAction(
209 "Sample 4", this, "resource/CellSpanLayoutDemoS04.graphml"));
210 jm.add(CellSpanActionFactory.newSampleAction(
211 "Sample 5", this, "resource/CellSpanLayoutDemoS05.graphml"));
212
213 final JMenuBar jmb = super.createMenuBar();
214 jmb.add(jm);
215 return jmb;
216 }
217
218 /**
219 * Provides controls for switching from design to diagram and vice versa.
220 * In design mode, the tabular cell structure of the diagram may be modified.
221 * In diagram mode, the previously defined cell structure is laid out.
222 */
223 protected JToolBar createToolBar() {
224 final JToolBar jtb = super.createToolBar();
225 jtb.addSeparator();
226
227 // use two toggle buttons which enable/disable each other to signal clearly
228 // that it is possible to switch between laid out diagram and design view
229 final JToggleButton tb1 = new JToggleButton();
230 final JToggleButton tb2 = new JToggleButton();
231 tb1.setAction(CellSpanActionFactory.newSwitchViewStateAction("Diagram", view, tb2));
232 jtb.add(tb1);
233 tb2.setSelected(true);
234 tb2.setAction(CellSpanActionFactory.newSwitchViewStateAction("Design", view, tb1));
235 tb2.setEnabled(false);
236 jtb.add(tb2);
237 return jtb;
238 }
239
240 /**
241 * Loads graph data from the resource with the given name.
242 * Overwritten for access from {@link CellSpanActionFactory}.
243 * @param resource the path name of the resource to load.
244 */
245 protected void loadGraph( final String resource ) {
246 super.loadGraph(resource);
247 }
248
249 /**
250 * Returns the main diagram view.
251 * Exists for access from {@link CellSpanActionFactory}.
252 * @return the main diagram view.
253 */
254 protected Graph2DView getView() {
255 return view;
256 }
257
258 /**
259 * Displays a sample diagram.
260 */
261 private void initGraph(final Graph2D graph) {
262 graph.clear();
263 loadGraph("resource/CellSpanLayoutDemoS01.graphml");
264 getUndoManager().resetQueue();
265 }
266
267 public static void main(String[] args) {
268 EventQueue.invokeLater(new Runnable() {
269 public void run() {
270 Locale.setDefault(Locale.ENGLISH);
271 initLnF();
272 (new CellSpanLayoutDemo("resource/cellspanlayouthelp.html")).start();
273 }
274 });
275 }
276
277
278 /**
279 * Returns the graph holding the given table structure.
280 * @return the graph holding the given table structure.
281 */
282 static Graph2D getGraph2D( final Table table ) {
283 return (Graph2D) table.getRealizer().getNode().getGraph();
284 }
285
286
287 /**
288 * Stores the background color for each table cell.
289 * Provides methods for managing stored colors and choosing new colors.
290 * Background colors are mapped to partition cell spans in class
291 * {@link CellSpanActionFactory.CellLayoutConfigurator}.
292 * <p>
293 * Instances of this class are stored as
294 * {@link y.view.GenericNodeRealizer#setUserData(Object) user data} in
295 * {@link TableGroupNodeRealizer} instances and are copied from one realizer
296 * instance to another on undo/redo, clipboard operations, etc. Since
297 * <code>TableGroupNodeRealizer</code>'s user data copying is inherited from
298 * its super class {@link y.view.GenericNodeRealizer}, user data copying
299 * happens before table structure copying. Consequently, storing references
300 * to columns and rows of a <code>TableGroupNodeRealizer</code> does not work
301 * for copy operations. For this reason, cell to background mappings in this
302 * class are based on column and row indices.
303 * </p>
304 */
305 static final class CellColorManager {
306 /** All possible background colors. */
307 private static final Color[] COLORS = newColors();
308
309
310 /**
311 * Stores the background color for each cell.
312 * Cells are stored as pairs of column index and row index. This is
313 * necessary, to be able to copy <code>CellColorManager</code> instances
314 * from one {@link TableGroupNodeRealizer} instance to another.
315 */
316 private final Map data;
317
318 /**
319 * Initializes a new <code>CellColorManager</code> instance with no
320 * background colors.
321 */
322 CellColorManager() {
323 data = new HashMap();
324 }
325
326 /**
327 * Initializes a new <code>CellColorManager</code> instance as copy of
328 * the given prototype instance.
329 */
330 CellColorManager( final CellColorManager prototype ) {
331 data = new HashMap(prototype.data);
332 }
333
334 /**
335 * Returns a background color that is not yet used in this instance.
336 * @return a background color that is not yet used in this instance or
337 * <code>null</code> if all background colors are already in use.
338 */
339 Color nextUnused() {
340 final HashSet used = new HashSet(data.values());
341 Color color = null;
342 for (int i = 0; i < COLORS.length; ++i) {
343 if (!used.contains(COLORS[i])) {
344 color = COLORS[i];
345 break;
346 }
347 }
348 return color;
349 }
350
351 /**
352 * Returns the background color for the given cell.
353 * @param col the horizontal position of the cell.
354 * @param row the vertical position of the cell.
355 * @return the background color for the given cell or <code>null</code>
356 * if the given cell has no background color.
357 */
358 Color getCellColor( final Column col, final Row row ) {
359 if (col != null && row != null) {
360 return (Color) data.get(new CellKey(col, row));
361 } else {
362 return null;
363 }
364 }
365
366 /**
367 * Sets the background color for the given cell.
368 * @param col the horizontal position of the cell.
369 * @param row the vertical position of the cell.
370 * @param color the new background color for the given cell. May be
371 * <code>null</code>.
372 */
373 void setCellColor( final Column col, final Row row, final Color color ) {
374 if (col != null && row != null) {
375 if (color == null) {
376 data.remove(new CellKey(col, row));
377 } else {
378 data.put(new CellKey(col, row), color);
379 }
380 }
381 }
382
383 /**
384 * Sets the background color for the given cell span.
385 * @param span the cell span for which to set the background color.
386 * @param color the new background color for the given cell. May be
387 * <code>null</code>.
388 */
389 void setCellColor( final Span span, final Color color ) {
390 if (color == null) {
391 for (int i = span.minCol, n = span.maxCol + 1; i < n; ++i) {
392 for (int j = span.minRow, m = span.maxRow + 1; j < m; ++j) {
393 data.remove(new CellKey(i, j));
394 }
395 }
396 } else {
397 for (int i = span.minCol, n = span.maxCol + 1; i < n; ++i) {
398 for (int j = span.minRow, m = span.maxRow + 1; j < m; ++j) {
399 data.put(new CellKey(i, j), color);
400 }
401 }
402 }
403 }
404
405 /**
406 * Adjusts the column indices in this manager's cell to color mappings
407 * for added/removed columns.
408 * @param column the column that was added or will be removed.
409 * @param up if <code>true</code>, indices will be adjusted for added
410 * columns otherwise for removed columns.
411 */
412 void shift( final Column column, final boolean up ) {
413 shiftImpl(column.getIndex(), false, up);
414 }
415
416 /**
417 * Adjusts the row indices in this manager's cell to color mappings
418 * for added/removed rows.
419 * @param row the row that was added or will be removed.
420 * @param up if <code>true</code>, indices will be adjusted for added
421 * rows otherwise for removed rows.
422 */
423 void shift( final Row row, final boolean up ) {
424 shiftImpl(row.getIndex(), true, up);
425 }
426
427 /**
428 * Adjusts the indices in this manager's cell to color mappings
429 * for added/removed columns or rows.
430 * @param idx the index of the column or row that was added or will be
431 * removed.
432 * @param row if <code>true</code>, indices will be adjusted for rows;
433 * otherwise for columns.
434 * @param up if <code>true</code> indices will be adjusted for added
435 * columns/rows; otherwise for removed columns/rows.
436 */
437 private void shiftImpl( final int idx, final boolean row, final boolean up ) {
438 final ArrayList keys = new ArrayList(data.keySet());
439 Collections.sort(keys, new CellComparator(row, up));
440 final int offset = up ? 1 : -1;
441 for (Iterator it = keys.iterator(); it.hasNext();) {
442 final CellKey key = (CellKey) it.next();
443 final int value = row ? key.row : key.column;
444 if (value >= idx) {
445 final Object color = data.remove(key);
446 if (up || value > idx) {
447 if (row) {
448 data.put(new CellKey(key.column, key.row + offset), color);
449 } else {
450 data.put(new CellKey(key.column + offset, key.row), color);
451 }
452 }
453 }
454 }
455 }
456
457 /**
458 * Returns the <code>CellColorManager</code> instance that stores the
459 * background colors for the cells in the given table.
460 */
461 static CellColorManager getInstance( final Table table ) {
462 final TableGroupNodeRealizer tgnr = table.getRealizer();
463 return (CellColorManager) tgnr.getUserData();
464 }
465
466 /**
467 * Creates a small set of colors for use as cell background colors.
468 */
469 private static Color[] newColors() {
470 return new Color[] {
471 new Color(192, 0, 0),
472 new Color(255, 102, 0),
473 new Color(251, 176, 7),
474 new Color( 0, 153, 57),
475 new Color( 0, 137, 205),
476
477 new Color(238, 53, 81),
478 new Color(255, 134, 56),
479 new Color(242, 228, 21),
480 new Color(149, 209, 39),
481 new Color( 17, 175, 252),
482 };
483
484 // alternative color set
485 // return new Color[] {
486 // new Color(181, 137, 0),
487 // new Color(203, 75, 22),
488 // new Color(220, 50, 47),
489 // new Color(211, 54, 130),
490 // new Color(108, 113, 196),
491 // new Color( 38, 139, 210),
492 // new Color( 42, 161, 152),
493 // new Color(133, 153, 0),
494 // };
495 }
496 }
497
498 /**
499 * Orders {@link CellKey} instances by their column or row index.
500 */
501 private static final class CellComparator implements Comparator {
502 private final boolean row;
503 private final int lessThan;
504 private int greaterThan;
505
506 CellComparator( final boolean row, final boolean descending ) {
507 this.row = row;
508 lessThan = descending ? 1 : -1;
509 greaterThan = descending ? -1 : 1;
510 }
511
512 public int compare( final Object o1, final Object o2 ) {
513 final int v1 = getValue((CellKey) o1);
514 final int v2 = getValue((CellKey) o2);
515 if (v1 < v2) {
516 return lessThan;
517 } else if (v1 > v2) {
518 return greaterThan;
519 } else {
520 return 0;
521 }
522 }
523
524 int getValue( final CellKey key ) {
525 return row ? key.row : key.column;
526 }
527 }
528
529 /**
530 * Represents a table cell by storing the cell's column and row indices.
531 */
532 private static final class CellKey {
533 // The horizontal position of the cell.
534 private final int column;
535 // The vertical position of the cell.
536 private final int row;
537
538 CellKey( final Column column, final Row row ) {
539 this(column.getIndex(), row.getIndex());
540 }
541
542 CellKey( final int column, final int row ) {
543 this.column = column;
544 this.row = row;
545 }
546
547 public boolean equals( final Object o ) {
548 if (this == o) return true;
549 if (o == null || getClass() != o.getClass()) return false;
550
551 final CellKey cellKey = (CellKey) o;
552
553 if (column != cellKey.column) return false;
554 if (row != cellKey.row) return false;
555
556 return true;
557 }
558
559 public int hashCode() {
560 int result = column;
561 result = 31 * result + row;
562 return result;
563 }
564
565 public String toString() {
566 return "[c=" + column + ";" + "r=" + row + "]";
567 }
568 }
569
570 /**
571 * Represents a table cell by storing the cell's column and row instances.
572 */
573 static final class Cell {
574 // The horizontal position of the cell.
575 private final Column column;
576 // The vertical position of the cell.
577 private final Row row;
578
579 Cell( final Column column, final Row row ) {
580 this.column = column;
581 this.row = row;
582 }
583
584 public boolean equals( final Object o ) {
585 if (this == o) return true;
586 if (o == null || getClass() != o.getClass()) return false;
587
588 final Cell cell = (Cell) o;
589
590 if (!column.equals(cell.column)) return false;
591 if (!row.equals(cell.row)) return false;
592
593 return true;
594 }
595
596 public int hashCode() {
597 int result = column.hashCode();
598 result = 31 * result + row.hashCode();
599 return result;
600 }
601
602 public String toString() {
603 return "[c=" + column.getIndex() + ";" + "r=" + row.getIndex() + "]";
604 }
605 }
606
607 /**
608 * Represents a "rectangular", two-dimensional cell range.
609 */
610 static final class Span {
611 // The index of the left-most column in this span.
612 final int minCol;
613 // The index of the right-most column in this span.
614 final int maxCol;
615 // The index of the top-most row in this span.
616 final int minRow;
617 // The index of the bottom-most row in this span.
618 final int maxRow;
619
620 private Span(
621 final int minCol, final int maxCol,
622 final int minRow, final int maxRow
623 ) {
624 this.minCol = minCol;
625 this.maxCol = maxCol;
626 this.minRow = minRow;
627 this.maxRow = maxRow;
628 }
629
630 /**
631 * Determines whether or not the given cell is included in this cell span.
632 * @param col the column (index) of the cell to check.
633 * @param row the row (index) of the cell to check.
634 * @return <code>true</code> if the given cell is included in this cell
635 * span; <code>false</code> otherwise.
636 */
637 boolean contains( final Column col, final Row row ) {
638 final int cIdx = col.getIndex();
639 final int rIdx = row.getIndex();
640 return minCol <= cIdx && cIdx <= maxCol &&
641 minRow <= rIdx && rIdx <= maxRow;
642 }
643
644 /**
645 * Determines whether or not this cell span contains all the cells of the
646 * given span.
647 * @param span the cell span to check.
648 * @return <code>true</code> if this cell span contains all the cells of the
649 * given span; <code>false</code> otherwise.
650 */
651 boolean contains( final Span span ) {
652 return minCol <= span.minCol && span.maxCol <= maxCol &&
653 minRow <= span.minRow && span.maxRow <= maxRow;
654 }
655
656 /**
657 * Creates a new cell span instance by finding the greatest cell span that
658 * includes the given cell and has only cells of the given color.
659 * Note, the color of the given cell is ignored and may differ from the
660 * given color.
661 * @param table the table structure to check.
662 * @param col the column (index) of the starting cell.
663 * @param row the row (index) of the starting cell.
664 * @param color the color of the cells to be included in the span.
665 * @return a new cell span instance.
666 */
667 static Span find(
668 final Table table, final Column col, final Row row,
669 final Color color
670 ) {
671 final CellColorManager manager = CellColorManager.getInstance(table);
672
673 int minCIdx = col.getIndex();
674 int maxCIdx = minCIdx;
675 int minRIdx = row.getIndex();
676 int maxRIdx = minRIdx;
677
678 // view the table as graph structure with each cell representing a node
679 // and consider cell A to be connected to cell B if
680 // B is directly above/below/to the left/to the right of A and
681 // B has the given color
682 // with this assumption the below code is essentially the well-known
683 // graph algorithm depth first search
684 final HashSet seen = new HashSet();
685 final ArrayList stack = new ArrayList();
686 stack.add(new Cell(col, row));
687 while (!stack.isEmpty()) {
688 final Cell cell = (Cell) stack.remove(stack.size() - 1);
689 if (seen.add(cell)) {
690 final Column c = cell.column;
691 final int cIdx = c.getIndex();
692 final Row r = cell.row;
693 final int rIdx = r.getIndex();
694
695 if (rIdx > 0) {
696 final Row north = table.getRow(rIdx - 1);
697 if (color.equals(manager.getCellColor(c, north))) {
698 stack.add(new Cell(c, north));
699
700 if (minRIdx > rIdx - 1) {
701 minRIdx = rIdx - 1;
702 }
703 }
704 }
705 if (cIdx > 0) {
706 final Column west = table.getColumn(cIdx - 1);
707 if (color.equals(manager.getCellColor(west, r))) {
708 stack.add(new Cell(west, r));
709
710 if (minCIdx > cIdx - 1) {
711 minCIdx = cIdx - 1;
712 }
713 }
714 }
715 if (rIdx + 1 < table.rowCount()) {
716 final Row south = table.getRow(rIdx + 1);
717 if (color.equals(manager.getCellColor(c, south))) {
718 stack.add(new Cell(c, south));
719
720 if (maxRIdx < rIdx + 1) {
721 maxRIdx = rIdx + 1;
722 }
723 }
724 }
725 if (cIdx + 1 < table.columnCount()) {
726 final Column east = table.getColumn(cIdx + 1);
727 if (color.equals(manager.getCellColor(east, r))) {
728 stack.add(new Cell(east, r));
729
730 if (maxCIdx < cIdx + 1) {
731 maxCIdx = cIdx + 1;
732 }
733 }
734 }
735 }
736 }
737
738 return new Span(minCIdx, maxCIdx, minRIdx, maxRIdx);
739 }
740
741 /**
742 * Creates a new cell span instance that includes the given cells.
743 * @param cells a collection of {@link Cell} instances.
744 * @return a new cell span instance.
745 */
746 static Span span( final Collection cells ) {
747 final Iterator it = cells.iterator();
748 final Cell first = (Cell) it.next();
749
750 int minCIdx = first.column.getIndex();
751 int maxCIdx = minCIdx;
752 int minRIdx = first.row.getIndex();
753 int maxRIdx = minRIdx;
754
755 while (it.hasNext()) {
756 final Cell cell = (Cell) it.next();
757
758 final int cIdx = cell.column.getIndex();
759 final int rIdx = cell.row.getIndex();
760 if (minCIdx > cIdx) {
761 minCIdx = cIdx;
762 }
763 if (maxCIdx < cIdx) {
764 maxCIdx = cIdx;
765 }
766 if (minRIdx > rIdx) {
767 minRIdx = rIdx;
768 }
769 if (maxRIdx < rIdx) {
770 maxRIdx = rIdx;
771 }
772 }
773
774 return new Span(minCIdx, maxCIdx, minRIdx, maxRIdx);
775 }
776
777 /**
778 * Creates a new cell span instance.
779 * @param minCol the index of the left-most column in this span.
780 * @param maxCol the index of the right-most column in this span.
781 * @param minRow the index of the top-most row in this span.
782 * @param maxRow the index of the bottom-most row in this span.
783 * @return a new cell span instance.
784 */
785 static Span manual(
786 final int minCol, final int maxCol,
787 final int minRow, final int maxRow
788 ) {
789 return new Span(minCol, maxCol, minRow, maxRow);
790 }
791 }
792 }
793