1 /* 2 * Copyright (c) 2012, 2014, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.gridpane; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.tring.Quad; 35 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.util.CardinalPoint; 36 import com.oracle.javafx.scenebuilder.kit.metadata.util.ColorEncoder; 37 import com.oracle.javafx.scenebuilder.kit.util.Deprecation; 38 import java.util.ArrayList; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Set; 42 import javafx.geometry.BoundingBox; 43 import javafx.geometry.Bounds; 44 import javafx.scene.Cursor; 45 import javafx.scene.Group; 46 import javafx.scene.Node; 47 import javafx.scene.control.Label; 48 import javafx.scene.layout.GridPane; 49 import javafx.scene.layout.Region; 50 import javafx.scene.paint.Color; 51 import javafx.scene.shape.Line; 52 import javafx.scene.shape.Path; 53 import javafx.scene.shape.Rectangle; 54 55 /** 56 * 57 */ 58 class GridPaneMosaic { 59 60 public final static double NORTH_TRAY_SIZE = 22; 61 public final static double SOUTH_TRAY_SIZE = NORTH_TRAY_SIZE; 62 public final static double WEST_TRAY_SIZE = 24; 63 public final static double EAST_TRAY_SIZE = WEST_TRAY_SIZE; 64 65 private final Group topGroup = new Group(); 66 private final Path gridPath = new Path(); 67 private final Group hgapLinesGroup = new Group(); 68 private final Group vgapLinesGroup = new Group(); 69 private final Group northTrayGroup = new Group(); 70 private final Group southTrayGroup = new Group(); 71 private final Group westTrayGroup = new Group(); 72 private final Group eastTrayGroup = new Group(); 73 private final Rectangle targetCellShadow = new Rectangle(); 74 private final Line targetGapShadowV = new Line(); 75 private final Line targetGapShadowH= new Line(); 76 private final Group hgapSensorsGroup = new Group(); 77 private final Group vgapSensorsGroup = new Group(); 78 79 private final Quad gridAreaQuad = new Quad(); 80 private final List<Quad> gridHoleQuads = new ArrayList<>(); 81 82 private final String baseStyleClass; 83 private final boolean shouldShowTrays; 84 private final boolean shouldCreateSensors; 85 86 private GridPane gridPane; 87 private int columnCount; 88 private int rowCount; 89 private List<Bounds> cellBounds = new ArrayList<>(); 90 private final Set<Integer> selectedColumnIndexes = new HashSet<>(); 91 private final Set<Integer> selectedRowIndexes = new HashSet<>(); 92 private int targetColumnIndex = -1; 93 private int targetRowIndex = -1; 94 private int targetGapColumnIndex = -1; 95 private int targetGapRowIndex = -1; 96 private Color trayColor; 97 98 public GridPaneMosaic(String baseStyleClass, boolean shouldShowTrays, boolean shouldCreateSensors) { 99 assert baseStyleClass != null; 100 101 this.baseStyleClass = baseStyleClass; 102 this.shouldShowTrays = shouldShowTrays; 103 this.shouldCreateSensors = shouldCreateSensors; 104 105 final List<Node> topChildren = topGroup.getChildren(); 106 topChildren.add(gridPath); // Mouse transparent 107 topChildren.add(hgapLinesGroup); // Mouse transparent 108 topChildren.add(vgapLinesGroup); // Mouse transparent 109 topChildren.add(northTrayGroup); 110 topChildren.add(southTrayGroup); 111 topChildren.add(westTrayGroup); 112 topChildren.add(eastTrayGroup); 113 topChildren.add(targetCellShadow); // Mouse transparent 114 topChildren.add(targetGapShadowV); // Mouse transparent 115 topChildren.add(targetGapShadowH); // Mouse transparent 116 topChildren.add(hgapSensorsGroup); 117 topChildren.add(vgapSensorsGroup); 118 gridAreaQuad.addToPath(gridPath); 119 120 gridPath.setMouseTransparent(true); 121 gridPath.getStyleClass().add("gap"); 122 gridPath.getStyleClass().add(baseStyleClass); 123 124 hgapLinesGroup.setMouseTransparent(true); 125 vgapLinesGroup.setMouseTransparent(true); 126 127 targetCellShadow.setMouseTransparent(true); 128 targetCellShadow.getStyleClass().add("gap"); 129 targetCellShadow.getStyleClass().add("selected"); 130 targetCellShadow.getStyleClass().add(baseStyleClass); 131 132 targetGapShadowV.setMouseTransparent(true); 133 targetGapShadowV.getStyleClass().add("gap"); 134 targetGapShadowV.getStyleClass().add("hilit"); 135 targetGapShadowV.getStyleClass().add(baseStyleClass); 136 137 targetGapShadowH.setMouseTransparent(true); 138 targetGapShadowH.getStyleClass().add("gap"); 139 targetGapShadowH.getStyleClass().add("hilit"); 140 targetGapShadowH.getStyleClass().add(baseStyleClass); 141 } 142 143 public Group getTopGroup() { 144 return topGroup; 145 } 146 147 public GridPane getGridPane() { 148 return gridPane; 149 } 150 151 public void setGridPane(GridPane gridPane) { 152 this.gridPane = gridPane; 153 update(); 154 } 155 156 public void setTrayColor(Color trayColor) { 157 this.trayColor = trayColor; 158 if (shouldShowTrays) { 159 updateTrayColor(); 160 } 161 } 162 163 public void setSelectedColumnIndexes(Set<Integer> indexes) { 164 selectedColumnIndexes.clear(); 165 selectedColumnIndexes.addAll(indexes); 166 update(); 167 } 168 169 public void setSelectedRowIndexes(Set<Integer> indexes) { 170 selectedRowIndexes.clear(); 171 selectedRowIndexes.addAll(indexes); 172 update(); 173 } 174 175 public void setTargetCell(int targetColumnIndex, int targetRowIndex) { 176 assert (targetColumnIndex == -1) == (targetRowIndex == -1); 177 this.targetColumnIndex = targetColumnIndex; 178 this.targetRowIndex = targetRowIndex; 179 this.targetGapColumnIndex = -1; 180 this.targetGapRowIndex = -1; 181 update(); 182 } 183 184 public void setTargetGap(int targetGapColumnIndex, int targetGapRowIndex) { 185 assert (-1 <= targetGapColumnIndex) && (targetGapColumnIndex <= columnCount); 186 assert (-1 <= targetGapRowIndex) && (targetGapRowIndex <= rowCount); 187 188 this.targetGapColumnIndex = targetGapColumnIndex; 189 this.targetGapRowIndex = targetGapRowIndex; 190 this.targetColumnIndex = -1; 191 this.targetRowIndex = -1; 192 update(); 193 } 194 195 public void update() { 196 197 columnCount = Deprecation.getGridPaneColumnCount(gridPane); 198 rowCount = Deprecation.getGridPaneRowCount(gridPane); 199 if ((columnCount == 0) || (rowCount == 0)) { 200 columnCount = rowCount = 0; 201 } 202 this.cellBounds.clear(); 203 for (int c = 0; c < columnCount; c++) { 204 for (int r = 0; r < rowCount; r++) { 205 this.cellBounds.add(Deprecation.getGridPaneCellBounds(gridPane, c, r)); 206 } 207 } 208 209 gridAreaQuad.setBounds(gridPane.getLayoutBounds()); 210 adjustHoleItems(); 211 adjustHGapLines(); 212 adjustVGapLines(); 213 if (shouldShowTrays) { 214 adjustTrayItems(northTrayGroup.getChildren(), "north", columnCount); 215 adjustTrayItems(southTrayGroup.getChildren(), "south", columnCount); 216 adjustTrayItems(westTrayGroup.getChildren(), "west", rowCount); 217 adjustTrayItems(eastTrayGroup.getChildren(), "east", rowCount); 218 } 219 if (shouldCreateSensors) { 220 final int hgapSensorCount = Math.max(0, columnCount-1); 221 final int vgapSensorCount = Math.max(0, rowCount-1); 222 adjustGapSensors(hgapSensorsGroup.getChildren(), Cursor.H_RESIZE, hgapSensorCount); 223 adjustGapSensors(vgapSensorsGroup.getChildren(), Cursor.V_RESIZE, vgapSensorCount); 224 } 225 226 if (columnCount >= 1) { 227 assert rowCount >= 1; 228 229 updateHoleBounds(); 230 updateHGapLines(); 231 updateVGapLines(); 232 233 if (shouldShowTrays) { 234 updateNorthTrayBounds(); 235 updateSouthTrayBounds(); 236 updateWestTrayBounds(); 237 updateEastTrayBounds(); 238 239 updateSelection(northTrayGroup.getChildren(), selectedColumnIndexes); 240 updateSelection(southTrayGroup.getChildren(), selectedColumnIndexes); 241 updateSelection(westTrayGroup.getChildren(), selectedRowIndexes); 242 updateSelection(eastTrayGroup.getChildren(), selectedRowIndexes); 243 244 updateTrayColor(); 245 } 246 247 if (shouldCreateSensors) { 248 updateHGapSensors(); 249 updateVGapSensors(); 250 } 251 252 253 updateTargetCell(); 254 updateTargetGap(); 255 } 256 } 257 258 259 public List<Node> getNorthTrayNodes() { 260 return northTrayGroup.getChildren(); 261 } 262 263 public List<Node> getSouthTrayNodes() { 264 return southTrayGroup.getChildren(); 265 } 266 267 public List<Node> getWestTrayNodes() { 268 return westTrayGroup.getChildren(); 269 } 270 271 public List<Node> getEastTrayNodes() { 272 return eastTrayGroup.getChildren(); 273 } 274 275 public List<Node> getHgapSensorNodes() { 276 return hgapSensorsGroup.getChildren(); 277 } 278 279 public List<Node> getVgapSensorNodes() { 280 return vgapSensorsGroup.getChildren(); 281 } 282 283 284 /* 285 * Private 286 */ 287 288 289 private void adjustHoleItems() { 290 final int holeCount = columnCount * rowCount; 291 292 while (gridHoleQuads.size() < holeCount) { 293 final Quad holeQuad = new Quad(false /* clockwise */); // Counterclockwise !! 294 holeQuad.addToPath(gridPath); 295 gridHoleQuads.add(holeQuad); 296 } 297 while (holeCount < gridHoleQuads.size()) { 298 final int cellIndex = gridHoleQuads.size()-1; 299 gridHoleQuads.get(cellIndex).removeFromPath(gridPath); 300 gridHoleQuads.remove(cellIndex); 301 } 302 } 303 304 305 private void adjustHGapLines() { 306 final int hgapLineCount; 307 if (gridPane.getHgap() == 0) { 308 hgapLineCount = Math.max(0, columnCount-1); 309 } else { 310 hgapLineCount = 0; 311 } 312 final List<Node> children = hgapLinesGroup.getChildren(); 313 while (children.size() < hgapLineCount) { 314 children.add(makeGapLine()); 315 } 316 while (children.size() > hgapLineCount) { 317 children.remove(0); 318 } 319 } 320 321 private void adjustVGapLines() { 322 final int vgapLineCount; 323 if (gridPane.getVgap() == 0) { 324 vgapLineCount = Math.max(0, rowCount-1); 325 } else { 326 vgapLineCount = 0; 327 } 328 final List<Node> children = vgapLinesGroup.getChildren(); 329 while (children.size() < vgapLineCount) { 330 children.add(makeGapLine()); 331 } 332 while (children.size() > vgapLineCount) { 333 children.remove(0); 334 } 335 } 336 337 private Line makeGapLine() { 338 final Line result = new Line(); 339 result.getStyleClass().add("gap"); 340 result.getStyleClass().add("empty"); 341 result.getStyleClass().add(baseStyleClass); 342 return result; 343 } 344 345 346 private void adjustTrayItems(List<Node> trayChildren, String direction, int targetCount) { 347 348 while (trayChildren.size() < targetCount) { 349 final int trayIndex = trayChildren.size(); 350 trayChildren.add(makeTrayLabel(trayIndex, direction)); 351 } 352 while (targetCount < trayChildren.size()) { 353 final int trayIndex = trayChildren.size()-1; 354 trayChildren.remove(trayIndex); 355 } 356 } 357 358 private Label makeTrayLabel(int num, String direction) { 359 final Label result = new Label(); 360 result.getStyleClass().add("tray"); 361 result.getStyleClass().add(direction); 362 result.getStyleClass().add(baseStyleClass); 363 result.setText(String.valueOf(num)); 364 result.setMinWidth(Region.USE_PREF_SIZE); 365 result.setMaxWidth(Region.USE_PREF_SIZE); 366 result.setMinHeight(Region.USE_PREF_SIZE); 367 result.setMaxHeight(Region.USE_PREF_SIZE); 368 369 if (trayColor != null) { 370 final String webColor = ColorEncoder.encodeColorToRGBA(trayColor); 371 final String style = "-fx-background-color:"+ webColor +";";//NOI18N 372 result.setStyle(style); 373 } 374 375 return result; 376 } 377 378 private void updateTrayColor() { 379 final String style; 380 381 if (trayColor == null) { 382 style = "";//NOI18N 383 } else { 384 final String webColor = ColorEncoder.encodeColorToRGBA(trayColor); 385 style = "-fx-background-color:"+ webColor +";";//NOI18N 386 } 387 388 adjustTrayStyle(northTrayGroup.getChildren(), style); 389 adjustTrayStyle(southTrayGroup.getChildren(), style); 390 adjustTrayStyle(westTrayGroup.getChildren(), style); 391 adjustTrayStyle(eastTrayGroup.getChildren(), style); 392 } 393 394 395 private void adjustTrayStyle(List<Node> trayChildren, String style) { 396 397 for (Node tray : trayChildren) { 398 assert tray instanceof Label; 399 final Label trayLabel = (Label) tray; 400 trayLabel.setStyle(style); 401 } 402 } 403 404 405 private void adjustGapSensors(List<Node> gapSensors, Cursor cursor, int targetCount) { 406 while (gapSensors.size() < targetCount) { 407 gapSensors.add(makeGapSensor(cursor)); 408 } 409 while (targetCount < gapSensors.size()) { 410 final int gapIndex = gapSensors.size()-1; 411 gapSensors.remove(gapIndex); 412 } 413 } 414 415 private Line makeGapSensor(Cursor cursor) { 416 final Line result = new Line(); 417 result.setCursor(cursor); 418 result.setStroke(Color.TRANSPARENT); 419 420 return result; 421 } 422 423 424 425 private void updateHGapSensors() { 426 final List<Node> children = hgapSensorsGroup.getChildren(); 427 final int sensorCount = children.size(); 428 assert (sensorCount == 0) || (sensorCount == columnCount-1); 429 for (int i = 0; i < sensorCount; i++) { 430 /* 431 * x0 xm x1 432 * y0 +----------------+ +-----------------+ 433 * | topLeftCell | | topRightCell | 434 * +----------------+ +-----------------+ 435 * 436 * ... 437 * 438 * +----------------+ +-----------------+ 439 * | bottomLeftCell | | | 440 * y1 +----------------+ +-----------------+ 441 */ 442 443 final Bounds topLeftCellBounds = getCellBounds(i, 0); 444 final Bounds topRightCellBounds = getCellBounds(i+1, 0); 445 final Bounds bottomLeftCellBounds = getCellBounds(i, rowCount-1); 446 final double x0 = topLeftCellBounds.getMaxX(); 447 final double x1 = topRightCellBounds.getMinX(); 448 final double xm = (x0 + x1) / 2.0; 449 final double y0 = topLeftCellBounds.getMinY(); 450 final double y1 = bottomLeftCellBounds.getMaxY(); 451 final double strokeWidth = Math.max(8.0, x1 - x0); 452 final Line line = (Line) children.get(i); 453 line.setStartX(xm); 454 line.setStartY(y0); 455 line.setEndX(xm); 456 line.setEndY(y1); 457 line.setStrokeWidth(strokeWidth); 458 } 459 } 460 461 private void updateVGapSensors() { 462 final List<Node> children = vgapSensorsGroup.getChildren(); 463 final int sensorCount = children.size(); 464 assert (sensorCount == 0) || (sensorCount == rowCount-1); 465 for (int i = 0; i < sensorCount; i++) { 466 467 /* 468 * x0 x1 469 * +----------------+ +-----------------+ 470 * | topLeftCell | ... | topRightCell | 471 * y0 +----------------+ +-----------------+ 472 * ym 473 * y1 +----------------+ +-----------------+ 474 * | bottomLeftCell | ... | | 475 * +----------------+ +-----------------+ 476 */ 477 478 final Bounds topLeftCellBounds = getCellBounds(0, i); 479 final Bounds bottomLeftCellBounds = getCellBounds(0, i+1); 480 final Bounds topRightCellBounds = getCellBounds(columnCount-1, i); 481 final double x0 = topLeftCellBounds.getMinX(); 482 final double x1 = topRightCellBounds.getMaxX(); 483 final double y0 = topLeftCellBounds.getMaxY(); 484 final double y1 = bottomLeftCellBounds.getMinY(); 485 final double ym = (y0 + y1) / 2.0; 486 final double strokeWidth = Math.max(8.0, y1 - y0); 487 final Line line = (Line) children.get(i); 488 line.setStartX(x0); 489 line.setStartY(ym); 490 line.setEndX(x1); 491 line.setEndY(ym); 492 line.setStrokeWidth(strokeWidth); 493 } 494 } 495 496 private void updateHoleBounds() { 497 for (int c = 0; c < columnCount; c++) { 498 for (int r = 0; r < rowCount; r++) { 499 final Bounds cb = getCellBounds(c, r); 500 gridHoleQuads.get(getCellIndex(c, r)).setBounds(cb); 501 } 502 } 503 } 504 505 private void updateHGapLines() { 506 final List<Node> children = hgapLinesGroup.getChildren(); 507 final int lineCount = children.size(); 508 assert (lineCount == 0) || (lineCount == columnCount-1); 509 for (int i = 0; i < lineCount; i++) { 510 final Bounds topLeftCellBounds = getCellBounds(i, 0); 511 final Bounds topRightCellBounds = getCellBounds(i+1, 0); 512 final Bounds bottomLeftCellBounds = getCellBounds(i, rowCount-1); 513 final double startX = (topLeftCellBounds.getMaxX() + topRightCellBounds.getMinX()) / 2.0; 514 final double startY = topLeftCellBounds.getMinY(); 515 final double endY = bottomLeftCellBounds.getMaxY(); 516 final double snappedX = Math.round(startX) + 0.5; 517 final Line line = (Line) children.get(i); 518 line.setStartX(snappedX); 519 line.setStartY(startY); 520 line.setEndX(snappedX); 521 line.setEndY(endY); 522 } 523 } 524 525 private void updateVGapLines() { 526 final List<Node> children = vgapLinesGroup.getChildren(); 527 final int lineCount = children.size(); 528 assert (lineCount == 0) || (lineCount == rowCount-1); 529 for (int i = 0; i < lineCount; i++) { 530 final Bounds topLeftCellBounds = getCellBounds(0, i); 531 final Bounds bottomLeftCellBounds = getCellBounds(0, i+1); 532 final Bounds topRightCellBounds = getCellBounds(columnCount-1, i); 533 final double startX = topLeftCellBounds.getMinX(); 534 final double startY = (topLeftCellBounds.getMaxY() + bottomLeftCellBounds.getMinY()) / 2.0; 535 final double endX = topRightCellBounds.getMaxX(); 536 final double snappedY = Math.round(startY) + 0.5; 537 final Line line = (Line) children.get(i); 538 line.setStartX(startX); 539 line.setStartY(snappedY); 540 line.setEndX(endX); 541 line.setEndY(snappedY); 542 } 543 } 544 545 546 private void updateNorthTrayBounds() { 547 final List<Node> northTrayChildren = northTrayGroup.getChildren(); 548 assert northTrayChildren.size() == columnCount; 549 550 for (int c = 0; c < columnCount; c++) { 551 updateNorthTrayBounds(c, (Label)northTrayChildren.get(c)); 552 } 553 } 554 555 556 private void updateNorthTrayBounds(int column, Label label) { 557 final Bounds gb = gridPane.getLayoutBounds(); 558 final Bounds cb = getCellBounds(column, 0); 559 560 561 /* 562 * x0 x1 563 * +-----------------+ 564 * | north(c) | 565 * y0 ....---+-----------------+---... 566 * | padding.top | 567 * ....---+-----------------+---... 568 * | | 569 * | cell(c, 0) | 570 * | | 571 * y1 ....---+-----------------+---... 572 */ 573 574 final double x0 = cb.getMinX(); 575 final double x1 = cb.getMaxX(); 576 final double y0 = gb.getMinY(); 577 final double y1 = cb.getMaxY(); 578 assert x0 <= x1; 579 assert y0 <= y1; 580 581 label.setPrefWidth(x1 - x0); 582 label.setPrefHeight(NORTH_TRAY_SIZE); 583 584 final Bounds area = new BoundingBox(x0, y0, x1-x0, y1-y0); 585 relocateNode(label, area, CardinalPoint.N); 586 } 587 588 589 private void updateSouthTrayBounds() { 590 final List<Node> trayChildren = southTrayGroup.getChildren(); 591 assert trayChildren.size() == columnCount; 592 593 for (int c = 0; c < columnCount; c++) { 594 updateSouthTrayBounds(c, (Label)trayChildren.get(c)); 595 } 596 } 597 598 599 private void updateSouthTrayBounds(int column, Label label) { 600 final Bounds gb = gridPane.getLayoutBounds(); 601 final Bounds cb = getCellBounds(column, 0); 602 603 604 /* 605 * x0 x1 606 * y0 ....---+-----------------+---... 607 * | | 608 * | cell(c, n-1) | 609 * | | 610 * ....---+-----------------+---... 611 * | padding.bottom | 612 * y1 ....---+-----------------+---... 613 * | south(c) | 614 * +-----------------+ 615 */ 616 617 final double x0 = cb.getMinX(); 618 final double x1 = cb.getMaxX(); 619 final double y0 = cb.getMinY(); 620 final double y1 = gb.getMaxY(); 621 assert x0 <= x1; 622 assert y0 <= y1; 623 624 label.setPrefWidth(x1 - x0); 625 label.setPrefHeight(SOUTH_TRAY_SIZE); 626 627 final Bounds area = new BoundingBox(x0, y0, x1-x0, y1-y0); 628 relocateNode(label, area, CardinalPoint.S); 629 } 630 631 632 private void updateWestTrayBounds() { 633 final List<Node> trayChildren = westTrayGroup.getChildren(); 634 assert trayChildren.size() == rowCount; 635 636 for (int r = 0; r < rowCount; r++) { 637 updateWestTrayBounds(r, (Label)trayChildren.get(r)); 638 } 639 } 640 641 642 private void updateWestTrayBounds(int row, Label label) { 643 final Bounds gb = gridPane.getLayoutBounds(); 644 final Bounds cb = getCellBounds(0,row); 645 646 647 /* 648 * x0 x1 649 * . . . 650 * . . . 651 * . . . 652 * | | | 653 * y0 +----+---+-----------------+... 654 * | | | | 655 * | | | | 656 * | | | | 657 * | | | cell(0, row) | 658 * | | | | 659 * | | | | 660 * | | | | 661 * y1 +----+---+-----------------+... 662 * | | | 663 * . . . 664 * . . . 665 * . . . 666 * ^ ^ 667 * | | 668 * | padding.left 669 * | 670 * west(r) 671 */ 672 673 final double x0 = gb.getMinX(); 674 final double x1 = cb.getMaxX(); 675 final double y0 = cb.getMinY(); 676 final double y1 = cb.getMaxY(); 677 assert x0 <= x1; 678 assert y0 <= y1; 679 680 label.setPrefWidth(y1 - y0); 681 label.setPrefHeight(WEST_TRAY_SIZE); 682 683 final Bounds area = new BoundingBox(x0, y0, x1-x0, y1-y0); 684 relocateNode(label, area, CardinalPoint.W); 685 } 686 687 688 689 690 private void updateEastTrayBounds() { 691 final List<Node> trayChildren = eastTrayGroup.getChildren(); 692 assert trayChildren.size() == rowCount; 693 694 for (int r = 0; r < rowCount; r++) { 695 updateEastTrayBounds(r, (Label)trayChildren.get(r)); 696 } 697 } 698 699 700 private void updateEastTrayBounds(int row, Label label) { 701 final Bounds gb = gridPane.getLayoutBounds(); 702 final Bounds cb = getCellBounds(0,row); 703 704 705 /* 706 * x0 x1 707 * . . . 708 * . . . 709 * . . . 710 * | | | 711 * y0 +-----------------+---+----+... 712 * | | | | 713 * | | | | 714 * | | | | 715 * | cell(0, row) | | | 716 * | | | | 717 * | | | | 718 * | | | | 719 * y1 +-----------------+---+----+... 720 * | | 721 * . . 722 * . . 723 * . . 724 * ^ ^ 725 * | | 726 * | west(r) 727 * | 728 * padding.right 729 */ 730 731 final double x0 = cb.getMinX(); 732 final double x1 = gb.getMaxX(); 733 final double y0 = cb.getMinY(); 734 final double y1 = cb.getMaxY(); 735 assert x0 <= x1; 736 assert y0 <= y1; 737 738 label.setPrefWidth(y1 - y0); 739 label.setPrefHeight(EAST_TRAY_SIZE); 740 741 final Bounds area = new BoundingBox(x0, y0, x1-x0, y1-y0); 742 relocateNode(label, area, CardinalPoint.E); 743 } 744 745 746 private void relocateNode(Label node, Bounds area, CardinalPoint cp) { 747 assert node != null; 748 749 final double nodeW = node.getPrefWidth(); 750 final double nodeH = node.getPrefHeight(); 751 final double areaW = area.getWidth(); 752 final double areaH = area.getHeight(); 753 754 /* 755 * From 756 * 757 * +----------+ 758 * | node |--------------------+ 759 * +----------+ | 760 * | | 761 * | | 762 * | area | 763 * | | 764 * | | 765 * | | 766 * +--------------------------+ 767 * 768 * 769 * to North 770 * +----------+ 771 * | node | 772 * +-------+----------+-------+ 773 * | | 774 * | | 775 * | | rotation = 0° 776 * | area | translateX = +areaW/2 777 * | | translateY = -nodeH/2 778 * | | 779 * | | 780 * +--------------------------+ 781 * 782 * to South 783 * +--------------------------+ 784 * | | 785 * | | 786 * | | rotation = 0° 787 * | area | translateX = +areaW/2 788 * | | translateY = +areaW+nodeH/2 789 * | | 790 * | | 791 * +-------+----------+-------+ 792 * | node | 793 * +----------+ 794 * 795 * to West 796 * +--------------------------+ 797 * | | 798 * +----+ | 799 * | | | rotation = -90° 800 * |node| area | translateX = -nodeH/2 801 * | | | translateY = +areaH/2 802 * +----+ | 803 * | | 804 * +--------------------------+ 805 * 806 * to East 807 * +--------------------------+ 808 * | | 809 * | |----+ 810 * | | | rotation = +90° 811 * | area |node| translateX = +areaW+nodeH/2 812 * | | | translateY = +areaH/2 813 * | |----+ 814 * | | 815 * +--------------------------+ 816 */ 817 818 final double rotation, translateX, translateY; 819 switch(cp) { 820 case N: 821 rotation = 0.0; 822 translateX = +areaW/2.0; 823 translateY = -nodeH/2.0; 824 break; 825 case S: 826 rotation = 0.0; 827 translateX = +areaW/2.0; 828 translateY = +areaH + nodeH/2.0; 829 break; 830 case W: 831 rotation = -90.0; 832 translateX = -nodeH/2.0; 833 translateY = +areaH/2.0; 834 break; 835 case E: 836 rotation = +90.0; 837 translateX = +areaW + nodeH/2.0; 838 translateY = +areaH/2.0; 839 break; 840 default: 841 assert false; 842 rotation = translateX = translateY = 0; 843 break; 844 } 845 846 final double nodeCenterX = nodeW / 2.0; 847 final double nodeCenterY = nodeH / 2.0; 848 final double layoutDX = area.getMinX() - nodeCenterX + translateX; 849 final double layoutDY = area.getMinY() - nodeCenterY + translateY; 850 851 node.setLayoutX(layoutDX); 852 node.setLayoutY(layoutDY); 853 node.setRotate(rotation); 854 } 855 856 857 858 private void updateSelection(List<Node> trayChildren, Set<Integer> selectedIndexes) { 859 final String selectedClass = "selected"; 860 861 for (int i = 0, count = trayChildren.size(); i < count; i++) { 862 final List<String> trayStyleClasses = trayChildren.get(i).getStyleClass(); 863 if (selectedIndexes.contains(i)) { 864 if (trayStyleClasses.contains(selectedClass) == false) { 865 trayStyleClasses.add(selectedClass); 866 } 867 } else { 868 if (trayStyleClasses.contains(selectedClass)) { 869 trayStyleClasses.remove(selectedClass); 870 } 871 } 872 } 873 } 874 875 876 private void updateTargetCell() { 877 if (targetColumnIndex == -1) { 878 assert targetRowIndex == -1; 879 targetCellShadow.setVisible(false); 880 } else { 881 targetCellShadow.setVisible(true); 882 final Bounds tb = getCellBounds(targetColumnIndex, targetRowIndex); 883 targetCellShadow.setX(tb.getMinX()); 884 targetCellShadow.setY(tb.getMinY()); 885 targetCellShadow.setWidth(tb.getWidth()); 886 targetCellShadow.setHeight(tb.getHeight()); 887 } 888 } 889 890 891 private Bounds getCellBounds(int c, int r) { 892 final int cellIndex = getCellIndex(c, r); 893 assert cellIndex < cellBounds.size(); 894 return cellBounds.get(cellIndex); 895 } 896 897 private int getCellIndex(int c, int r) { 898 return c * rowCount + r; 899 } 900 901 902 private static final double MIN_STROKE_WIDTH = 8; 903 904 private void updateTargetGap() { 905 906 /* 907 * targetGapShadowV 908 */ 909 if (targetGapColumnIndex == -1) { 910 targetGapShadowV.setVisible(false); 911 } else { 912 targetGapShadowV.setVisible(true); 913 914 final double startX, startY, endY, strokeWidth; 915 if (targetGapColumnIndex < columnCount) { 916 final Bounds topCellBounds = getCellBounds(targetGapColumnIndex, 0); 917 final Bounds bottomCellBounds = getCellBounds(targetGapColumnIndex, rowCount-1); 918 startY = topCellBounds.getMinY(); 919 endY = bottomCellBounds.getMaxY(); 920 if (targetGapColumnIndex == 0) { 921 startX = topCellBounds.getMinX(); 922 strokeWidth = MIN_STROKE_WIDTH; 923 } else { 924 assert targetGapColumnIndex >= 1; 925 final Bounds leftTopCellBounds = getCellBounds(targetGapColumnIndex-1, 0); 926 startX = (leftTopCellBounds.getMaxX() + topCellBounds.getMinX()) / 2.0; 927 strokeWidth = Math.abs(leftTopCellBounds.getMaxX() - topCellBounds.getMinX()); 928 } 929 } else { 930 final Bounds topCellBounds = getCellBounds(columnCount-1, 0); 931 final Bounds bottomCellBounds = getCellBounds(columnCount-1, rowCount-1); 932 startX = topCellBounds.getMaxX(); 933 startY = topCellBounds.getMinY(); 934 endY = bottomCellBounds.getMaxY(); 935 strokeWidth = MIN_STROKE_WIDTH; 936 } 937 targetGapShadowV.setStartX(startX); 938 targetGapShadowV.setStartY(startY); 939 targetGapShadowV.setEndX(startX); 940 targetGapShadowV.setEndY(endY); 941 targetGapShadowV.setStrokeWidth(Math.max(strokeWidth, MIN_STROKE_WIDTH)); 942 } 943 944 /* 945 * targetGapShadowH 946 */ 947 if (targetGapRowIndex == -1) { 948 targetGapShadowH.setVisible(false); 949 } else { 950 targetGapShadowH.setVisible(true); 951 952 final double startX, endX, startY, strokeWidth; 953 if (targetGapRowIndex < rowCount) { 954 final Bounds leftCellBounds = getCellBounds(0, targetGapRowIndex); 955 final Bounds rightCellBounds = getCellBounds(columnCount-1, targetGapRowIndex); 956 startX = leftCellBounds.getMinX(); 957 endX = rightCellBounds.getMaxX(); 958 if (targetGapRowIndex == 0) { 959 startY = leftCellBounds.getMinY(); 960 strokeWidth = MIN_STROKE_WIDTH; 961 } else { 962 assert targetGapRowIndex >= 1; 963 final Bounds aboveLeftCellBounds = getCellBounds(0, targetGapRowIndex-1); 964 startY = (aboveLeftCellBounds.getMaxY() + leftCellBounds.getMinY()) / 2.0; 965 strokeWidth = Math.abs(aboveLeftCellBounds.getMaxY() - leftCellBounds.getMinY()); 966 } 967 } else { 968 final Bounds leftCellBounds = getCellBounds(0, rowCount-1); 969 final Bounds rightCellBounds = getCellBounds(columnCount-1, rowCount-1); 970 startX = leftCellBounds.getMinX(); 971 endX = rightCellBounds.getMaxX(); 972 startY = leftCellBounds.getMaxY(); 973 strokeWidth = MIN_STROKE_WIDTH; 974 } 975 targetGapShadowH.setStartX(startX); 976 targetGapShadowH.setStartY(startY); 977 targetGapShadowH.setEndX(endX); 978 targetGapShadowH.setEndY(startY); 979 targetGapShadowH.setStrokeWidth(Math.max(strokeWidth, MIN_STROKE_WIDTH)); 980 } 981 982 } 983 }