| CellSpanControllerFactory.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.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