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.handles;
  33 
  34 import java.util.List;
  35 
  36 import javafx.beans.value.ChangeListener;
  37 import javafx.geometry.Bounds;
  38 import javafx.geometry.Point2D;
  39 import javafx.scene.Cursor;
  40 import javafx.scene.Group;
  41 import javafx.scene.Node;
  42 import javafx.scene.control.TableColumn;
  43 import javafx.scene.control.TableView;
  44 import javafx.scene.paint.Color;
  45 import javafx.scene.shape.Line;
  46 
  47 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.AbstractResilientHandles;
  48 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.ContentPanelController;
  49 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.TableViewDesignInfoX;
  50 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.AbstractGesture;
  51 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.mouse.ResizeTableColumnGesture;
  52 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance;
  53 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject;
  54 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask;
  55 
  56 /**
  57  *
  58  *
  59  */
  60 
  61 public class TableColumnHandles extends AbstractResilientHandles<Object> {
  62 
  63     /*
  64      * Handles for TableColumn need a special treatment.
  65      *
  66      * A TableColumn instance can be transiently disconnected from its parent TableView:
  67      *  - TableColumn.getTableView() returns null
  68      *  - TableView.getColumns().contains() returns false
  69      *
  70      * When the TableColumn is disconnected, handles cannot be drawn.
  71      * This Handles class inherits from AbstractResilientHandles to take
  72      * care of this singularity.
  73      */
  74 
  75     private final Group grips = new Group();
  76     private final TableViewDesignInfoX tableViewDesignInfo
  77             = new TableViewDesignInfoX();
  78     private TableView<?> tableView;
  79     private Node columnHeaderNode;
  80 
  81     public TableColumnHandles(ContentPanelController contentPanelController,
  82             FXOMInstance fxomInstance) {
  83         super(contentPanelController, fxomInstance, Object.class);
  84         assert fxomInstance.getSceneGraphObject() instanceof TableColumn;
  85 
  86         getRootNode().getChildren().add(grips); // Above handles
  87 
  88         getTableColumn().tableViewProperty().addListener(
  89                 (ChangeListener<Object>) (ov, v1, v2) -> tableViewOrVisibilityDidChange());
  90         getTableColumn().visibleProperty().addListener(
  91                 (ChangeListener<Object>) (ov, v1, v2) -> tableViewOrVisibilityDidChange());
  92 
  93         tableViewOrVisibilityDidChange();
  94     }
  95 
  96     public FXOMInstance getFxomInstance() {
  97         return (FXOMInstance) getFxomObject();
  98     }
  99 
 100     /*
 101      * AbstractGenericHandles
 102      */
 103     @Override
 104     public Bounds getSceneGraphObjectBounds() {
 105         assert isReady();
 106         assert tableView != null;
 107         assert getTableColumn().isVisible();
 108         return tableViewDesignInfo.getColumnBounds(getTableColumn());
 109     }
 110 
 111     @Override
 112     public Node getSceneGraphObjectProxy() {
 113         assert isReady();
 114         assert tableView != null;
 115         return tableView;
 116     }
 117 
 118     @Override
 119     protected void startListeningToSceneGraphObject() {
 120         assert isReady();
 121         assert tableView != null;
 122         startListeningToLayoutBounds(tableView);
 123         startListeningToLocalToSceneTransform(tableView);
 124 
 125         assert columnHeaderNode == null;
 126         columnHeaderNode = tableViewDesignInfo.getColumnNode(getTableColumn());
 127         startListeningToBoundsInParent(columnHeaderNode);
 128     }
 129 
 130     @Override
 131     protected void stopListeningToSceneGraphObject() {
 132         assert isReady();
 133         assert tableView != null;
 134         stopListeningToLayoutBounds(tableView);
 135         stopListeningToLocalToSceneTransform(tableView);
 136 
 137         assert columnHeaderNode != null;
 138         stopListeningToBoundsInParent(columnHeaderNode);
 139         columnHeaderNode = null;
 140     }
 141 
 142     @Override
 143     protected void layoutDecoration() {
 144         assert tableView != null;
 145 
 146         super.layoutDecoration();
 147 
 148         // Adjusts the number of grip lines to the number of dividers
 149         adjustGripCount();
 150 
 151         // Updates grip positions
 152         for (int i = 0, count = getTableColumns().size(); i < count; i++) {
 153             layoutGrip(i);
 154         }
 155     }
 156 
 157     @Override
 158     public AbstractGesture findGesture(Node node) {
 159         final AbstractGesture result;
 160 
 161         final int gripIndex = grips.getChildren().indexOf(node);
 162         if (gripIndex != -1) {
 163             final FXOMObject parentObject = getFxomInstance().getParentObject();
 164             final DesignHierarchyMask m = new DesignHierarchyMask(parentObject);
 165             final FXOMObject columnObject = m.getSubComponentAtIndex(gripIndex);
 166             assert columnObject instanceof FXOMInstance;
 167             result = new ResizeTableColumnGesture(getContentPanelController(),
 168                     (FXOMInstance)columnObject);
 169         } else {
 170             result = super.findGesture(node);
 171         }
 172 
 173         return result;
 174     }
 175 
 176 
 177     /*
 178      * Private
 179      */
 180 
 181     private TableColumn<?,?> getTableColumn() {
 182         assert getSceneGraphObject() instanceof TableColumn;
 183         return (TableColumn<?,?>) getSceneGraphObject();
 184     }
 185 
 186     private void tableViewOrVisibilityDidChange() {
 187         tableView = getTableColumn().getTableView();
 188         setReady((tableView != null) && getTableColumn().isVisible());
 189     }
 190 
 191     private List<?> getTableColumns() {
 192         final List<?> result;
 193 
 194         final TableColumn<?,?> tableColumn = getTableColumn();
 195         if (tableColumn.getParentColumn() == null) {
 196             result = tableView.getColumns();
 197         } else {
 198             result = tableColumn.getParentColumn().getColumns();
 199         }
 200 
 201         return result;
 202     }
 203 
 204 
 205 
 206     /*
 207      * Private (grips)
 208      */
 209 
 210     private void adjustGripCount() {
 211         assert tableView != null;
 212 
 213         final int columnCount = getTableColumns().size();
 214         final List<Node> gripChildren = grips.getChildren();
 215 
 216         while (gripChildren.size() < columnCount) {
 217             gripChildren.add(makeGripLine());
 218         }
 219         while (gripChildren.size() > columnCount) {
 220             gripChildren.remove(gripChildren.size()-1);
 221         }
 222     }
 223 
 224     private Line makeGripLine() {
 225         final Line result = new Line();
 226         result.setStrokeWidth(SELECTION_HANDLES_SIZE);
 227         result.setStroke(Color.TRANSPARENT);
 228         result.setCursor(Cursor.H_RESIZE);
 229         attachHandles(result);
 230         return result;
 231     }
 232 
 233     private void layoutGrip(int gripIndex) {
 234         assert grips.getChildren().get(gripIndex) instanceof Line;
 235         assert getTableColumns().get(gripIndex) instanceof TableColumn<?,?>;
 236 
 237         final List<?> columns = getTableColumns();
 238         final TableColumn<?,?> tc = (TableColumn<?,?>)columns.get(gripIndex);
 239         if (tc.isVisible()) {
 240             final TableViewDesignInfoX di = new TableViewDesignInfoX();
 241             final Bounds b = di.getColumnHeaderBounds(tc);
 242             final double startX = b.getMaxX();
 243             final double startY = b.getMinY();
 244             final double endY = b.getMaxY();
 245 
 246             final boolean snapToPixel = true;
 247             final Point2D startPoint = sceneGraphObjectToDecoration(startX, startY, snapToPixel);
 248             final Point2D endPoint = sceneGraphObjectToDecoration(startX, endY, snapToPixel);
 249 
 250             final Line gripLine = (Line) grips.getChildren().get(gripIndex);
 251             gripLine.setVisible(true);
 252             gripLine.setManaged(true);
 253             gripLine.setStartX(startPoint.getX());
 254             gripLine.setStartY(startPoint.getY());
 255             gripLine.setEndX(endPoint.getX());
 256             gripLine.setEndY(endPoint.getY());
 257         } else {
 258             final Line gripLine = (Line) grips.getChildren().get(gripIndex);
 259             gripLine.setVisible(false);
 260             gripLine.setManaged(false);
 261         }
 262     }
 263 
 264 
 265     /*
 266      * Wrapper to avoid the 'leaking this in constructor' warning emitted by NB.
 267      */
 268     private void attachHandles(Node node) {
 269         attachHandles(node, this);
 270     }
 271 }