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.layout.hierarchic.CellSpanLayoutDemo.Cell;
31  import demo.layout.hierarchic.CellSpanLayoutDemo.CellColorManager;
32  import demo.layout.hierarchic.CellSpanLayoutDemo.Span;
33  
34  import y.base.Node;
35  import y.base.NodeCursor;
36  import y.base.NodeList;
37  import y.geom.YInsets;
38  import y.view.EditMode;
39  import y.view.Graph2D;
40  import y.view.HitInfo;
41  import y.view.NodeRealizer;
42  import y.view.PopupMode;
43  import y.view.SelectionBoxMode;
44  import y.view.hierarchy.HierarchyManager;
45  import y.view.tabular.TableGroupNodeRealizer;
46  import y.view.tabular.TableGroupNodeRealizer.Column;
47  import y.view.tabular.TableGroupNodeRealizer.Row;
48  import y.view.tabular.TableGroupNodeRealizer.Table;
49  
50  import java.awt.Color;
51  import java.awt.event.MouseEvent;
52  import java.awt.geom.Rectangle2D;
53  import java.util.ArrayList;
54  import java.util.Collection;
55  import javax.swing.JPopupMenu;
56  
57  /**
58   * Provides custom user interaction for cell designer table nodes.
59   *
60   */
61  class CellSpanControllerFactory {
62    /**
63     * Prevents instantiation of factory class.
64     */
65    private CellSpanControllerFactory() {
66    }
67  
68  
69    /**
70     * Creates a new instance of {@link CellEditMode}. The returned
71     * instance will use {@link CellPopupMode} for custom context menus and
72     * {@link CellColorMode} for custom marquee selection behavior.
73     * @return an instance of {@link CellEditMode}.
74     */
75    static EditMode newCellEditMode() {
76      final CellEditMode editMode = new CellEditMode();
77      editMode.setPopupMode(new CellPopupMode());
78      editMode.setSelectionBoxMode(new CellColorMode());
79      return editMode;
80    }
81  
82    /**
83     * Determines whether or not the <code>CTRL</code> key was pressed when
84     * the given mouse event was fired.
85     * @param e the mouse event to check.
86     * @return <code>true</code> if the <code>CTRL</code> key was pressed when
87     * the given mouse event was fired; <code>false</code> otherwise.
88     */
89    static boolean isCtrlDown( final MouseEvent e ) {
90      final int mask = MouseEvent.CTRL_DOWN_MASK;
91      return e != null && (e.getModifiersEx() & mask) == mask;
92    }
93  
94  
95    /**
96     * Prevents edge creation while <code>CTRL</code> is pressed by triggering
97     * marquee selection instead and prevents creation of "free" nodes, that is
98     * nodes without a parent group/table node.
99     */
100   private static final class CellEditMode extends EditMode {
101     /**
102      * Prevents edge creation while <code>CTRL</code> is pressed.
103      * With this customization, the selection box subordinate mode is triggered
104      * when dragging the mouse over a node while pressing <code>CTRL</code>.
105      * @param lastPress the last press event
106      * @param lastDrag the last drag event
107      * @return <code>false</code> if <code>CTRL</code> is pressed or
108      * the super implementation result if <code>CTRL</code> is not pressed.
109      * @see CellColorMode
110      */
111     protected boolean isCreateEdgeGesture(
112             final MouseEvent lastPress, final MouseEvent lastDrag
113     ) {
114       if (isCtrlDown(lastPress) || isCtrlDown(lastDrag)) {
115         return false;
116       } else {
117         return super.isCreateEdgeGesture(lastPress, lastDrag);
118       }
119     }
120 
121     /**
122      * Prevents node creation if the specified parent node is <code>null</code>.
123      * @param graph the graph which resided in the canvas
124      * @param x the x coordinate where the mouse was clicked
125      * @param y the y coordinate where the mouse was clicked
126      * @param parent the parent group node for the newly created node.
127      * If <code>null</code>, the new node will be a top level node.
128      * @return <code>null</code> if the specified parent node is
129      * <code>null</code> or the super implementation result if the specified
130      * parent node is not <code>null</code>. 
131      */
132     protected Node createNode(
133             final Graph2D graph, final double x, final double y, final Node parent
134     ) {
135       if (parent == null) {
136         return null;
137       } else {
138         return super.createNode(graph, x, y, parent);
139       }
140     }
141   }
142 
143   /**
144    * Provides actions for adding and removing columns and rows in a table
145    * nodes.
146    */
147   private static class CellPopupMode extends PopupMode {
148     /**
149      * Initializes a new <code>CellPopupMode</code> instance.
150      * This mode does not select elements when opening a context menu.
151      */
152     CellPopupMode() {
153       setSelectSubject(false);
154     }
155 
156     /**
157      * Returns the context menu to be opened by this mode.
158      * Overwritten to take the world (graph) coordinates of the triggering
159      * mouse event into account when populating the context menu for table
160      * nodes.
161      * @see #getNodePopup(y.base.Node, double, double) 
162      */
163     protected JPopupMenu getPopup(
164             final HitInfo hitInfo,
165             final double x, final double y,
166             final int popupType
167     ) {
168       if (POPUP_TYPE_NODE == popupType) {
169         return getNodePopup(hitInfo.getHitNode(), x, y);
170       } else {
171         return super.getPopup(hitInfo, x, y, popupType);
172       }
173     }
174 
175     /**
176      * Returns the context menu for nodes.
177      * For table nodes, the context menu will provide actions for adding and
178      * removing columns and rows.
179      * For other (normal) nodes, the context menu will be empty (and will not
180      * be displayed).
181      */
182     JPopupMenu getNodePopup(
183             final Node node, final double x, final double y
184     ) {
185       final JPopupMenu jpm = super.getNodePopup(node);
186 
187       final Graph2D graph = getGraph2D();
188       final NodeRealizer nr = graph.getRealizer(node);
189       if (nr instanceof TableGroupNodeRealizer) {
190         final TableGroupNodeRealizer tgnr = (TableGroupNodeRealizer) nr;
191         final Table table = tgnr.getTable();
192         final Column col = table.columnAt(x, y);
193         if (col != null) {
194           final Row row = table.rowAt(x, y);
195           if (row != null) {
196             jpm.add(CellSpanActionFactory.newAddBefore(view, table, col));
197             jpm.add(CellSpanActionFactory.newAddAfter(view, table, col));
198             jpm.add(CellSpanActionFactory.newRemoveColumn(graph, table, col));
199             jpm.addSeparator();
200             jpm.add(CellSpanActionFactory.newAddBefore(view, table, row));
201             jpm.add(CellSpanActionFactory.newAddAfter(view, table, row));
202             jpm.add(CellSpanActionFactory.newRemoveRow(graph, table, row));
203           }
204         }
205       }
206 
207       return jpm;
208     }
209   }
210 
211   /**
212    * Colors the background of table cells when pressing <code>CTRL</code> while
213    * using marquee selection.
214    * Removes the background color table cells when pressing <code>CTRL</code>
215    * and <code>ALT</code> while using marquee selection.
216    */
217   private static final class CellColorMode extends SelectionBoxMode {
218     /**
219      * Handles marquee selection.
220      * Overwritten to check whether or not <code>CTRL</code> and
221      * <code>ALT</code> were pressed when ending the marquee selection
222      * to trigger background coloring instead of element selection.
223      * @param sb The position and size of the selection box.
224      * @param shiftMode <code>true</code> if shift was pressed when
225      */
226     protected void selectionBoxAction(
227             final Rectangle2D.Double sb, final boolean shiftMode
228     ) {
229       final MouseEvent e = lastReleaseEvent;
230       if (isCtrlDown(e)) {
231         final int mask = MouseEvent.ALT_DOWN_MASK;
232         final boolean clear = (e.getModifiersEx() & mask) == mask;
233 
234         final Graph2D graph = getGraph2D();
235         final HierarchyManager hm = graph.getHierarchyManager();
236         for (NodeCursor nc = hm.getChildren(null); nc.ok(); nc.next()) {
237           final Node node = nc.node();
238           if (hm.isGroupNode(node)) {
239             final NodeRealizer nr = graph.getRealizer(node);
240             if (nr instanceof TableGroupNodeRealizer) {
241               setColor((TableGroupNodeRealizer) nr, sb, clear);
242               break;
243             }
244           }
245         }
246         graph.updateViews();
247       } else {
248         super.selectionBoxAction(sb, shiftMode);
249       }
250     }
251 
252     /**
253      * Sets a new background color for the table cells that intersect the
254      * specified selection box rectangle.
255      * @param tgnr the realizer holding the table structure.
256      * @param sb the selection box rectangle.
257      * @param clear if <code>true</code>, the background color of the cells
258      * in the selection box is cleared; otherwise an new background color is
259      * set. 
260      */
261     private void setColor(
262             final TableGroupNodeRealizer tgnr, final Rectangle2D sb, final boolean clear
263     ) {
264       final Table table = tgnr.getTable();
265 
266       // get a background color that was not yet used
267       final Color color = CellColorManager.getInstance(table).nextUnused();
268       if (!clear && color == null) {
269         return;
270       }
271 
272 
273       // determine all cells that intersect the given selection box rectangle
274       // this code relies on the fact that CellSpanLayoutDemo does not provide
275       // a way to created columns in columns or rows in rows
276       final Rectangle2D.Double tmp = new Rectangle2D.Double();
277 
278       double x = tgnr.getX();
279       final YInsets insets = table.getInsets();
280       x += insets.left;
281 
282       final ArrayList cells = new ArrayList();
283       for (int i = 0, n = table.columnCount(); i < n; ++i) {
284         final Column col = table.getColumn(i);
285         final double w = col.getWidth();
286 
287         double y = tgnr.getY();
288         y += insets.top;
289         for (int j = 0, m = table.rowCount(); j < m; ++j) {
290           final Row row = table.getRow(j);
291           final double h = row.getHeight();
292 
293           tmp.setFrame(x, y, w, h);
294           if (sb.intersects(tmp)) {
295             cells.add(new Cell(col, row));
296           }
297 
298           y += h;
299         }
300         x += w;
301       }
302 
303       // now set or erase the background color for the cells
304       if (!cells.isEmpty()) {
305         getGraph2D().backupRealizers((new NodeList(tgnr.getNode())).nodes());
306         if (clear) {
307           setColor(table, cells, null);
308         } else {
309           setColor(table, cells, color);
310         }
311       }
312     }
313 
314     /**
315      * Sets the given background color for the given table cells.
316      * This method ensures that the resulting coloring defines valid
317      * rectangular cell spans.
318      * <p>
319      * E.g. suppose cells <code>(c1, r2)</code>, <code>(c2, r2)</code>, and
320      * <code>(c3, r2)</code> are colored red and the cells <code>(c2, r1)</code>
321      * and <code>(c2, r2)</code> should be newly colored blue, this method will
322      * erase the background color from cell <code>(c1, r2)</code> to prevent two
323      * disjoint red cell spans.
324      * </p><p>
325      * Alternatively, suppose cells <code>(c1, r1)</code>,
326      * <code>(c2, r1)</code>, <code>(c1, r2)</code>, and <code>(c2, r2)</code>
327      * are colored red and cell <code>(c2, r2)</code> should be newly colored
328      * blue, this method will erase the background color from cell
329      * <code>(c1, r2)</code> to prevent a non-rectangular red cell span. 
330      * </p>
331      * @param table the table holding the cells whose background color is set.
332      * @param cells the cells whose background color is set.
333      * @param newColor the new background color.
334      */
335     private void setColor(
336             final Table table, final Collection cells, final Color newColor
337     ) {
338       final Span newSpan = Span.span(cells);
339 
340       final CellColorManager manager = CellColorManager.getInstance(table);
341       for (int i = newSpan.minCol, n = newSpan.maxCol + 1; i < n; ++i) {
342         for (int j = newSpan.minRow, m = newSpan.maxRow + 1; j < m; ++j) {
343           final Column col = table.getColumn(i);
344           final Row row = table.getRow(j);
345           final Color oldColor = manager.getCellColor(col, row);
346           // if the oldColor is null, cell (i,j) does not belong to another
347           // cell span and may be colored with no adverse effects
348           // otherwise, cell (i,j) belongs to a cell span that may need to
349           // be adjusted to remain valid
350           if (oldColor != null) {
351             final Span oldSpan = Span.find(table, col, row, oldColor);
352             if (newSpan.contains(oldSpan)) {
353               // replace the color of the entire span
354               manager.setCellColor(oldSpan, null);
355             } else {
356               // determine the cells whose background has to be erased
357               // for the old cell span to remain a valid
358               final Span cut = cut(newSpan, oldSpan);
359               manager.setCellColor(cut, null);
360             }
361           }
362           manager.setCellColor(col, row, newColor);
363         }
364       }
365     }
366 
367     /**
368      * Determines the cells in <code>oldSpan</code> whose background
369      * has to be erased when coloring the cells in <code>newSpan</code>
370      * with a different background color.
371      */
372     private static Span cut( final Span newSpan, final Span oldSpan ) {
373       if (oldSpan.contains(newSpan)) {
374         if (newSpan.contains(oldSpan)) {
375           return oldSpan;
376         } else {
377           // top cut
378           int minSize = size(newSpan, oldSpan, CUT_T);
379           Span min = newCut(newSpan, oldSpan, CUT_T);
380           // bottom cut
381           final int bSize = size(newSpan, oldSpan, CUT_B);
382           if (minSize > bSize) {
383             minSize = bSize;
384             min = newCut(newSpan, oldSpan, CUT_B);
385           }
386           // left cut
387           final int lSize = size(newSpan, oldSpan, CUT_L);
388           if (minSize > lSize) {
389             minSize = lSize;
390             min = newCut(newSpan, oldSpan, CUT_L);
391           }
392           // right cut
393           final int rSize = size(newSpan, oldSpan, CUT_R);
394           if (minSize > rSize) {
395             minSize = rSize;
396             min = newCut(newSpan, oldSpan, CUT_R);
397           }
398 
399           return min;
400         }
401       }
402 
403 
404       // whether or not the new span starts vertically inside the old span
405       final boolean inT = oldSpan.minRow < newSpan.minRow;
406       // whether or not the new span ends vertically inside the old span
407       final boolean inB = newSpan.maxRow < oldSpan.maxRow;
408       // whether or not the new span starts horizontally inside the old span
409       final boolean inL = oldSpan.minCol < newSpan.minCol;
410       // whether or not the new span ends horizontally inside the old span
411       final boolean inR = newSpan.maxCol < oldSpan.maxCol;
412       // whether or not the new span is completely inside the old span's
413       // horizontal range
414       final boolean inH = inL && inR;
415       // whether or not the new span is completely inside the old span's
416       // vertical range
417       final boolean inV = inT && inB;
418 
419       // whether or not the new span starts vertically outside the old span
420       final boolean outT = newSpan.minRow <= oldSpan.minRow;
421       // whether or not the new span ends vertically outside the old span
422       final boolean outB = oldSpan.maxRow <= newSpan.maxRow;
423       // whether or not the new span starts horizontally outside the old span
424       final boolean outL = newSpan.minCol <= oldSpan.minCol;
425       // whether or not the new span ends horizontally outside the old span
426       final boolean outR = oldSpan.maxCol <= newSpan.maxCol;
427       // whether or not the new span completely covers the old span's
428       // horizontal range
429       final boolean outH = outL && outR;
430       // whether or not the new span completely covers the old span's
431       // vertical range
432       final boolean outV = outT && outB;
433 
434       // whether or not the new span starts outside and ends inside the old
435       // span's vertical range
436       final boolean cutT = outT && inB;
437       // whether or not the new span starts inside and ends outside the old
438       // span's vertical range
439       final boolean cutB = inT && outB;
440       // whether or not the new span starts outside and ends inside the old
441       // span's horizontal range
442       final boolean cutL = outL && inR;
443       // whether or not the new span starts inside and ends outside the old
444       // span's horizontal range
445       final boolean cutR = inL && outR;
446 
447 
448       // case 1:
449       //    +-----------+
450       //    |           |
451       // ---+-----------+---
452       // ###################
453       // ---+-----------+---
454       //    |           |
455       //    +-----------+
456       if (outH && inV) {
457         return newCut(newSpan, oldSpan, CUT_T, CUT_B);
458       }
459 
460       // case 2:
461       //    +-----------+             ##########|
462       //    |           |             ##########|
463       // ---+-----+     |             ##########+----+
464       // #########|                   ##########|    |
465       // ---+-----+     |             ##########|    |
466       //    |           |             ##########|    |
467       //    +-----------+             ##########|    |
468       //                              ##########+----+
469       //                              ##########|
470       //                              ##########|
471       if (cutL && (inV || outV)) {
472         return newCut(newSpan, oldSpan, CUT_L);
473       }
474 
475       // case 3:
476       //    +-----------+                  |##########
477       //    |           |                  |##########
478       //    |     +-----+---          +----+##########
479       //    |     |#########          |    |##########
480       //    |     +-----+---          |    |##########
481       //    |           |             |    |##########
482       //    +-----------+             |    |##########
483       //                              +----+##########
484       //                                   |##########
485       //                                   |##########
486       if (cutR && (inV || outV)) {
487         return newCut(newSpan, oldSpan, CUT_R);
488       }
489 
490       // case 4:
491       //         |#|
492       //         |#|
493       //    +----+#+----+
494       //    |    |#|    |
495       //    |    |#|    |
496       //    |    |#|    |
497       //    |    |#|    |
498       //    +----+#+----+
499       //         |#|
500       //         |#|
501       if (inH && outV) {
502         return newCut(newSpan, oldSpan, CUT_L, CUT_R);
503       }
504 
505       // case 5:
506       //         |#|                  ###################
507       //         |#|                  ###################
508       //    +----+#+----+             ###################
509       //    |    |#|    |             ###################
510       //    |    |#|    |             ###################
511       //    |    +-+    |             ---+-----------+---
512       //    |           |                |           |
513       //    +-----------+                +-----------+
514       //
515       //
516       if ((inH || outH) && cutT) {
517         return newCut(newSpan, oldSpan, CUT_T);
518       }
519 
520       // case 6:
521       //
522       //
523       //    +-----------+                +-----------+
524       //    |           |                |           |
525       //    |    +-+    |             ---+-----------+---
526       //    |    |#|    |             ###################
527       //    |    |#|    |             ###################
528       //    +----+#+----+             ###################
529       //         |#|                  ###################
530       //         |#|                  ###################
531       if ((inH || outH) && cutB) {
532         return newCut(newSpan, oldSpan, CUT_B);
533       }
534 
535 
536       // case 7:
537       // ##########|
538       // ##########|
539       // ##########+----+
540       // ##########|    |
541       // ##########|    |
542       // ---+------+    |
543       //    |           |
544       //    +-----------+
545       //
546       //
547       if (cutL && cutT) {
548         return newCut(newSpan, oldSpan, CUT_T, CUT_L);
549       }
550 
551       // case 8:
552       //         |##########
553       //         |##########
554       //    +----+##########
555       //    |    |##########
556       //    |    |##########
557       //    |    +------+---
558       //    |           |
559       //    +-----------+
560       //
561       //
562       if (cutR && cutT) {
563         return newCut(newSpan, oldSpan, CUT_T, CUT_R);
564       }
565 
566       // case 9:
567       //
568       //
569       //    +-----------+
570       //    |           |
571       //    |    +------+---
572       //    |    |##########
573       //    |    |##########
574       //    +----+##########
575       //         |##########
576       //         |##########
577       if (cutR && cutB) {
578         return newCut(newSpan, oldSpan, CUT_B, CUT_R);
579       }
580 
581       // case 10:
582       //
583       //
584       //    +-----------+
585       //    |           |
586       // ---+------+    |
587       // ##########|    |
588       // ##########|    |
589       // ##########+----+
590       // ##########|
591       // ##########|
592       if (cutL && cutB) {
593         return newCut(newSpan, oldSpan, CUT_B, CUT_L);
594       }
595 
596 
597       // should never happen
598       return oldSpan;
599     }
600 
601 
602     /** Top cut. */
603     private static final byte CUT_T = 1;
604     /** Bottom cut. */
605     private static final byte CUT_B = 2;
606     /** Left cut. */
607     private static final byte CUT_L = 3;
608     /** Right cut. */
609     private static final byte CUT_R = 4;
610 
611     /**
612      * Calculates the number of cells in a directional intersection of
613      * the two given cell spans. 
614      * @param newSpan the new cell span to which the direction applies.
615      * @param oldSpan the old cell span.
616      * @param cut the direction of the intersection. Has to be one of
617      * <ul>
618      * <li>{@link #CUT_T},</li>
619      * <li>{@link #CUT_B},</li>
620      * <li>{@link #CUT_L}, or</li>
621      * <li>{@link #CUT_R}.</li>
622      * </ul>
623      * @return the number of cells in a directional intersection of
624      * the two given cell spans.
625      */
626     private static int size( final Span newSpan, final Span oldSpan, final byte cut ) {
627       final int maxCol = CUT_L == cut ? newSpan.maxCol : oldSpan.maxCol;
628       final int minCol = CUT_R == cut ? newSpan.minCol : oldSpan.minCol;
629       final int maxRow = CUT_T == cut ? newSpan.maxRow : oldSpan.maxRow;
630       final int minRow = CUT_B == cut ? newSpan.minRow : oldSpan.minRow;
631       return (maxCol - minCol + 1) * (maxRow - minRow + 1);
632     }
633 
634     /**
635      * Calculates the cells in a directional intersection of the two given cell
636      * spans. 
637      * @param newSpan the new cell span to which the direction applies.
638      * @param oldSpan the old cell span.
639      * @param cut the direction of the intersection. Has to be one of
640      * <ul>
641      * <li>{@link #CUT_T},</li>
642      * <li>{@link #CUT_B},</li>
643      * <li>{@link #CUT_L}, or</li>
644      * <li>{@link #CUT_R}.</li>
645      * </ul>
646      * @return the cells in a directional intersection of the two given cell
647      * spans.
648      */
649     private static Span newCut( final Span newSpan, final Span oldSpan, final byte cut ) {
650       final int maxCol = CUT_L == cut ? newSpan.maxCol : oldSpan.maxCol;
651       final int minCol = CUT_R == cut ? newSpan.minCol : oldSpan.minCol;
652       final int maxRow = CUT_T == cut ? newSpan.maxRow : oldSpan.maxRow;
653       final int minRow = CUT_B == cut ? newSpan.minRow : oldSpan.minRow;
654       return Span.manual(minCol, maxCol, minRow, maxRow);
655     }
656 
657     /**
658      * Calculates the smaller of the two desired directional intersections
659      * of the two given cell spans.
660      * @param newSpan the new cell span to which the direction applies.
661      * @param oldSpan the old cell span.
662      * @param cut1 the first possible cut direction. Has to be one of
663      * {@link #CUT_T}, {@link #CUT_B}, {@link #CUT_L}, or {@link #CUT_R}.
664      * @param cut2 the second possible cut direction. Has to be one of
665      * {@link #CUT_T}, {@link #CUT_B}, {@link #CUT_L}, or {@link #CUT_R}.
666      * @return the smaller of the two desired directional intersections
667      * of the two given cell spans.
668      */
669     private static Span newCut(
670             final Span newSpan, final Span oldSpan,
671             final byte cut1, final byte cut2
672     ) {
673       return size(newSpan, oldSpan, cut2) < size(newSpan, oldSpan, cut1)
674              ? newCut(newSpan, oldSpan, cut2)
675              : newCut(newSpan, oldSpan, cut1);
676     }
677   }
678 }
679