1 /* 2 * PlanComponent.js 3 * 4 * Sweet Home 3D, Copyright (c) 2024 Space Mushrooms <info@sweethome3d.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 21 /** 22 * Creates a new plan that displays <code>home</code>. 23 * @param {string} containerOrCanvasId the ID of a HTML DIV or CANVAS 24 * @param {Home} home the home to display 25 * @param {UserPreferences} preferences user preferences to retrieve used unit, grid visibility... 26 * @param {Object} [object3dFactory] a factory able to create 3D objects from <code>home</code> furniture. 27 * The {@link Object3DFactory#createObject3D(Home, Selectable, boolean) createObject3D} of 28 * this factory is expected to return an instance of {@link Object3DBranch} in current implementation. 29 * @param {PlanController} controller the optional controller used to manage home items modification 30 * @constructor 31 * @author Emmanuel Puybaret 32 * @author Renaud Pawlak 33 */ 34 function PlanComponent(containerOrCanvasId, home, preferences, object3dFactory, controller) { 35 this.home = home; 36 this.preferences = preferences; 37 if (controller == null) { 38 controller = object3dFactory; 39 object3dFactory = new Object3DBranchFactory(); 40 } 41 this.object3dFactory = object3dFactory; 42 43 var plan = this; 44 this.pointerType = View.PointerType.MOUSE; 45 this.canvasNeededRepaint = false; 46 this.container = document.getElementById(containerOrCanvasId); 47 var computedStyle = window.getComputedStyle(this.container); 48 this.font = [computedStyle.fontStyle, computedStyle.fontSize, computedStyle.fontFamily].join(' '); 49 if(computedStyle.position != "absolute") { 50 this.container.style.position = "relative"; 51 } 52 if (this.container instanceof HTMLCanvasElement) { 53 this.canvas = this.view = this.container; // No scrollPane 54 this.canvas.width = this.canvas.clientWidth; 55 this.canvas.height = this.canvas.clientHeight; 56 } else { 57 this.canvas = document.createElement("canvas"); 58 this.canvas.setAttribute("id", containerOrCanvasId + ".canvas"); 59 this.canvas.style.width = "100%"; // computedStyle.width; 60 this.canvas.style.height = "100%"; // computedStyle.height; 61 if (PlanComponent.initialBackgroundColor === undefined) { 62 PlanComponent.initialBackgroundColor = computedStyle.backgroundColor; 63 PlanComponent.initialForegroundColor = computedStyle.color; 64 } 65 this.canvas.style.backgroundColor = PlanComponent.initialBackgroundColor; // /!\ computedStyle.backgroundColor and color may change when reseting home 66 this.canvas.style.color = PlanComponent.initialForegroundColor; 67 this.canvas.style.font = computedStyle.font; 68 this.scrollPane = document.createElement("div"); 69 this.scrollPane.setAttribute("id", containerOrCanvasId + ".scrollPane"); 70 this.scrollPane.style.width = "100%"; // computedStyle.width; 71 this.scrollPane.style.height = "100%"; // computedStyle.height; 72 if (this.container.style.overflow) { 73 this.scrollPane.style.overflow = this.container.style.overflow; 74 } else { 75 this.scrollPane.style.overflow = "scroll"; 76 } 77 this.view = document.createElement("div"); 78 this.view.setAttribute("id", containerOrCanvasId + ".view"); 79 this.container.appendChild(this.scrollPane); 80 this.container.appendChild(this.canvas); 81 this.scrollPane.appendChild(this.view); 82 this.canvas.style.position = "absolute"; 83 this.canvas.style.left = "0px"; 84 this.canvas.style.top = "0px"; 85 this.scrollPane.style.position = "absolute"; 86 this.scrollPane.style.left = "0px"; 87 this.scrollPane.style.top = "0px"; 88 this.scrollPane.addEventListener("scroll", function(ev) { 89 plan.repaint(); 90 }); 91 } 92 93 this.windowResizeListener = function() { 94 plan.revalidate(); 95 }; 96 window.addEventListener("resize", this.windowResizeListener); 97 this.tooltip = document.createElement("div"); 98 this.tooltip.style.position = "absolute"; 99 this.tooltip.style.visibility = "hidden"; 100 this.tooltip.style.backgroundColor = ColorTools.toRGBAStyle(this.getBackground(), 0.7); 101 this.tooltip.style.borderWidth = "2px"; 102 this.tooltip.style.paddingLeft = "2px"; 103 this.tooltip.style.borderStyle = "solid"; 104 this.tooltip.style.whiteSpace = "nowrap"; 105 this.tooltip.style.borderColor = ColorTools.toRGBAStyle(this.getForeground(), 0.7); 106 this.tooltip.style.font = this.font; 107 this.tooltip.style.color = this.canvas.style.color; 108 this.tooltip.style.zIndex = 101; 109 document.body.appendChild(this.tooltip); 110 111 this.touchOverlay = document.createElement("div"); 112 this.touchOverlay.classList.add("touch-overlay-timer"); 113 this.touchOverlay.style.position = "absolute"; 114 this.touchOverlay.style.top = "0px"; 115 this.touchOverlay.style.left = "0px"; 116 this.touchOverlay.innerHTML = '<div id="plan-touch-overlay-timer-content" class="touch-overlay-timer-content"></div><div class="touch-overlay-timer-bg"></div><div class="touch-overlay-timer-hidder"></div><div class="touch-overlay-timer-loader1"></div><div class="touch-overlay-timer-loader2"></div>'; 117 document.body.appendChild(this.touchOverlay); 118 for (var i = 0; i < this.touchOverlay.children.length; i++) { 119 var item = this.touchOverlay.children.item(i); 120 if (item.classList.contains("overlay-timer-loader1") 121 || item.classList.contains("overlay-timer-loader2")) { 122 item.style.borderTopColor = this.getSelectionColor(); 123 item.style.borderRightColor = this.getSelectionColor(); 124 } 125 if (item.classList.contains("touch-overlay-timer-content")) { 126 item.style.color = this.getForeground(); 127 } 128 item.style.animationDuration = (PlanComponent.LONG_TOUCH_DURATION_AFTER_DELAY) + "ms"; 129 } 130 131 this.resolutionScale = this.scrollPane ? PlanComponent.HIDPI_SCALE_FACTOR : 1.; 132 this.selectedItemsOutlinePainted = true; 133 this.backgroundPainted = true; 134 this.planBoundsCacheValid = false; 135 136 this.setOpaque(true); 137 this.addModelListeners(home, preferences, controller); 138 if (controller != null) { 139 this.addMouseListeners(controller); 140 this.addFocusListener(controller); 141 this.addControllerListener(controller); 142 this.createActions(controller); 143 this.installDefaultKeyboardActions(); 144 } 145 146 this.rotationCursor = PlanComponent.createCustomCursor('rotation', 'alias'); 147 this.elevationCursor = PlanComponent.createCustomCursor('elevation', 'row-resize'); 148 this.heightCursor = PlanComponent.createCustomCursor('height', 'ns-resize'); 149 this.powerCursor = PlanComponent.createCustomCursor('power', 'cell'); 150 this.resizeCursor = PlanComponent.createCustomCursor('resize', 'ew-resize'); 151 this.moveCursor = PlanComponent.createCustomCursor('move', 'move'); 152 this.panningCursor = PlanComponent.createCustomCursor('panning', 'move'); 153 this.duplicationCursor = 'copy'; 154 155 this.patternImagesCache = {}; 156 157 this.setScale(0.5); 158 159 setTimeout(this.windowResizeListener); 160 } 161 162 PlanComponent["__interfaces"] = ["com.eteks.sweethome3d.viewcontroller.PlanView", "com.eteks.sweethome3d.viewcontroller.View", "com.eteks.sweethome3d.viewcontroller.ExportableView", "com.eteks.sweethome3d.viewcontroller.TransferableView"]; 163 164 /** 165 * @private 166 */ 167 PlanComponent.initStatics = function() { 168 PlanComponent.MARGIN = 40; 169 170 PlanComponent.INDICATOR_STROKE = new java.awt.BasicStroke(1.5); 171 PlanComponent.POINT_STROKE = new java.awt.BasicStroke(2.0); 172 173 PlanComponent.WALL_STROKE_WIDTH = 1.5; 174 PlanComponent.BORDER_STROKE_WIDTH = 1.0; 175 PlanComponent.ALIGNMENT_LINE_OFFSET = 25; 176 177 PlanComponent.ERROR_TEXTURE_IMAGE = null; 178 PlanComponent.WAIT_TEXTURE_IMAGE = null; 179 180 // TODO Generic resolution support (see https://stackoverflow.com/questions/15661339/how-do-i-fix-blurry-text-in-my-html5-canvas) 181 PlanComponent.HIDPI_SCALE_FACTOR = 2; 182 183 PlanComponent.POINT_INDICATOR = new java.awt.geom.Ellipse2D.Float(-1.5, -1.5, 3, 3); 184 185 PlanComponent.FURNITURE_ROTATION_INDICATOR = new java.awt.geom.GeneralPath(); 186 PlanComponent.FURNITURE_ROTATION_INDICATOR.append(PlanComponent.POINT_INDICATOR, false); 187 PlanComponent.FURNITURE_ROTATION_INDICATOR.append(new java.awt.geom.Arc2D.Float(-8, -8, 16, 16, 45, 180, java.awt.geom.Arc2D.OPEN), false); 188 PlanComponent.FURNITURE_ROTATION_INDICATOR.moveTo(2.66, -5.66); 189 PlanComponent.FURNITURE_ROTATION_INDICATOR.lineTo(5.66, -5.66); 190 PlanComponent.FURNITURE_ROTATION_INDICATOR.lineTo(4.0, -8.3); 191 192 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR = new java.awt.geom.GeneralPath(); 193 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.append(PlanComponent.POINT_INDICATOR, false); 194 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.moveTo(-4.5, 0); 195 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.lineTo(-5.2, 0); 196 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.moveTo(-9.0, 0); 197 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.lineTo(-10, 0); 198 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.append(new java.awt.geom.Arc2D.Float(-12, -8, 5, 16, 200, 320, java.awt.geom.Arc2D.OPEN), false); 199 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.moveTo(-10.0, -4.5); 200 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.lineTo(-12.3, -2.0); 201 PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.lineTo(-12.8, -5.8); 202 203 var transform = java.awt.geom.AffineTransform.getRotateInstance(-Math.PI / 2); 204 transform.concatenate(java.awt.geom.AffineTransform.getScaleInstance(1, -1)); 205 PlanComponent.FURNITURE_ROLL_ROTATION_INDICATOR = PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.createTransformedShape(transform); 206 207 PlanComponent.ELEVATION_POINT_INDICATOR = new java.awt.geom.Rectangle2D.Float(-1.5, -1.5, 3.0, 3.0); 208 209 PlanComponent.ELEVATION_INDICATOR = new java.awt.geom.GeneralPath(); 210 PlanComponent.ELEVATION_INDICATOR.moveTo(0, -5); 211 PlanComponent.ELEVATION_INDICATOR.lineTo(0, 5); 212 PlanComponent.ELEVATION_INDICATOR.moveTo(-2.5, 5); 213 PlanComponent.ELEVATION_INDICATOR.lineTo(2.5, 5); 214 PlanComponent.ELEVATION_INDICATOR.moveTo(-1.2, 1.5); 215 PlanComponent.ELEVATION_INDICATOR.lineTo(0, 4.5); 216 PlanComponent.ELEVATION_INDICATOR.lineTo(1.2, 1.5); 217 218 PlanComponent.HEIGHT_POINT_INDICATOR = new java.awt.geom.Rectangle2D.Float(-1.5, -1.5, 3.0, 3.0); 219 220 PlanComponent.FURNITURE_HEIGHT_INDICATOR = new java.awt.geom.GeneralPath(); 221 PlanComponent.FURNITURE_HEIGHT_INDICATOR.moveTo(0, -6); 222 PlanComponent.FURNITURE_HEIGHT_INDICATOR.lineTo(0, 6); 223 PlanComponent.FURNITURE_HEIGHT_INDICATOR.moveTo(-2.5, -6); 224 PlanComponent.FURNITURE_HEIGHT_INDICATOR.lineTo(2.5, -6); 225 PlanComponent.FURNITURE_HEIGHT_INDICATOR.moveTo(-2.5, 6); 226 PlanComponent.FURNITURE_HEIGHT_INDICATOR.lineTo(2.5, 6); 227 PlanComponent.FURNITURE_HEIGHT_INDICATOR.moveTo(-1.2, -2.5); 228 PlanComponent.FURNITURE_HEIGHT_INDICATOR.lineTo(0.0, -5.5); 229 PlanComponent.FURNITURE_HEIGHT_INDICATOR.lineTo(1.2, -2.5); 230 PlanComponent.FURNITURE_HEIGHT_INDICATOR.moveTo(-1.2, 2.5); 231 PlanComponent.FURNITURE_HEIGHT_INDICATOR.lineTo(0.0, 5.5); 232 PlanComponent.FURNITURE_HEIGHT_INDICATOR.lineTo(1.2, 2.5); 233 234 PlanComponent.LIGHT_POWER_POINT_INDICATOR = new java.awt.geom.Rectangle2D.Float(-1.5, -1.5, 3.0, 3.0); 235 236 PlanComponent.LIGHT_POWER_INDICATOR = new java.awt.geom.GeneralPath(); 237 PlanComponent.LIGHT_POWER_INDICATOR.moveTo(-8, 0); 238 PlanComponent.LIGHT_POWER_INDICATOR.lineTo(-6.0, 0); 239 PlanComponent.LIGHT_POWER_INDICATOR.lineTo(-6.0, -1); 240 PlanComponent.LIGHT_POWER_INDICATOR.closePath(); 241 PlanComponent.LIGHT_POWER_INDICATOR.moveTo(-3, 0); 242 PlanComponent.LIGHT_POWER_INDICATOR.lineTo(-1.0, 0); 243 PlanComponent.LIGHT_POWER_INDICATOR.lineTo(-1.0, -2.5); 244 PlanComponent.LIGHT_POWER_INDICATOR.lineTo(-3.0, -1.8); 245 PlanComponent.LIGHT_POWER_INDICATOR.closePath(); 246 PlanComponent.LIGHT_POWER_INDICATOR.moveTo(2, 0); 247 PlanComponent.LIGHT_POWER_INDICATOR.lineTo(4, 0); 248 PlanComponent.LIGHT_POWER_INDICATOR.lineTo(4.0, -3.5); 249 PlanComponent.LIGHT_POWER_INDICATOR.lineTo(2.0, -2.8); 250 PlanComponent.LIGHT_POWER_INDICATOR.closePath(); 251 252 PlanComponent.FURNITURE_RESIZE_INDICATOR = new java.awt.geom.GeneralPath(); 253 PlanComponent.FURNITURE_RESIZE_INDICATOR.append(new java.awt.geom.Rectangle2D.Float(-1.5, -1.5, 3.0, 3.0), false); 254 PlanComponent.FURNITURE_RESIZE_INDICATOR.moveTo(5, -4); 255 PlanComponent.FURNITURE_RESIZE_INDICATOR.lineTo(7, -4); 256 PlanComponent.FURNITURE_RESIZE_INDICATOR.lineTo(7, 7); 257 PlanComponent.FURNITURE_RESIZE_INDICATOR.lineTo(-4, 7); 258 PlanComponent.FURNITURE_RESIZE_INDICATOR.lineTo(-4, 5); 259 PlanComponent.FURNITURE_RESIZE_INDICATOR.moveTo(3.5, 3.5); 260 PlanComponent.FURNITURE_RESIZE_INDICATOR.lineTo(9, 9); 261 PlanComponent.FURNITURE_RESIZE_INDICATOR.moveTo(7, 9.5); 262 PlanComponent.FURNITURE_RESIZE_INDICATOR.lineTo(10, 10); 263 PlanComponent.FURNITURE_RESIZE_INDICATOR.lineTo(9.5, 7); 264 265 PlanComponent.WALL_ORIENTATION_INDICATOR = new java.awt.geom.GeneralPath(); 266 PlanComponent.WALL_ORIENTATION_INDICATOR.moveTo(-4, -4); 267 PlanComponent.WALL_ORIENTATION_INDICATOR.lineTo(4, 0); 268 PlanComponent.WALL_ORIENTATION_INDICATOR.lineTo(-4, 4); 269 270 PlanComponent.WALL_POINT = new java.awt.geom.Ellipse2D.Float(-3, -3, 6, 6); 271 272 PlanComponent.WALL_ARC_EXTENT_INDICATOR = new java.awt.geom.GeneralPath(); 273 PlanComponent.WALL_ARC_EXTENT_INDICATOR.append(new java.awt.geom.Arc2D.Float(-4, 1, 8, 5, 210, 120, java.awt.geom.Arc2D.OPEN), false); 274 PlanComponent.WALL_ARC_EXTENT_INDICATOR.moveTo(0, 6); 275 PlanComponent.WALL_ARC_EXTENT_INDICATOR.lineTo(0, 11); 276 PlanComponent.WALL_ARC_EXTENT_INDICATOR.moveTo(-1.8, 8.7); 277 PlanComponent.WALL_ARC_EXTENT_INDICATOR.lineTo(0, 12); 278 PlanComponent.WALL_ARC_EXTENT_INDICATOR.lineTo(1.8, 8.7); 279 280 PlanComponent.WALL_AND_LINE_RESIZE_INDICATOR = new java.awt.geom.GeneralPath(); 281 PlanComponent.WALL_AND_LINE_RESIZE_INDICATOR.moveTo(5, -2); 282 PlanComponent.WALL_AND_LINE_RESIZE_INDICATOR.lineTo(5, 2); 283 PlanComponent.WALL_AND_LINE_RESIZE_INDICATOR.moveTo(6, 0); 284 PlanComponent.WALL_AND_LINE_RESIZE_INDICATOR.lineTo(11, 0); 285 PlanComponent.WALL_AND_LINE_RESIZE_INDICATOR.moveTo(8.7, -1.8); 286 PlanComponent.WALL_AND_LINE_RESIZE_INDICATOR.lineTo(12, 0); 287 PlanComponent.WALL_AND_LINE_RESIZE_INDICATOR.lineTo(8.7, 1.8); 288 289 transform = java.awt.geom.AffineTransform.getRotateInstance(-Math.PI / 4); 290 PlanComponent.CAMERA_YAW_ROTATION_INDICATOR = PlanComponent.FURNITURE_ROTATION_INDICATOR.createTransformedShape(transform); 291 292 transform = java.awt.geom.AffineTransform.getRotateInstance(Math.PI); 293 PlanComponent.CAMERA_PITCH_ROTATION_INDICATOR = PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR.createTransformedShape(transform); 294 295 PlanComponent.CAMERA_ELEVATION_INDICATOR = new java.awt.geom.GeneralPath(); 296 PlanComponent.CAMERA_ELEVATION_INDICATOR.moveTo(0, -4); 297 PlanComponent.CAMERA_ELEVATION_INDICATOR.lineTo(0, 4); 298 PlanComponent.CAMERA_ELEVATION_INDICATOR.moveTo(-2.5, 4); 299 PlanComponent.CAMERA_ELEVATION_INDICATOR.lineTo(2.5, 4); 300 PlanComponent.CAMERA_ELEVATION_INDICATOR.moveTo(-1.2, 0.5); 301 PlanComponent.CAMERA_ELEVATION_INDICATOR.lineTo(0, 3.5); 302 PlanComponent.CAMERA_ELEVATION_INDICATOR.lineTo(1.2, 0.5); 303 304 var cameraHumanBodyAreaPath = new java.awt.geom.GeneralPath(); 305 cameraHumanBodyAreaPath.append(new java.awt.geom.Ellipse2D.Float(-0.5, -0.425, 1.0, 0.85), false); 306 cameraHumanBodyAreaPath.append(new java.awt.geom.Ellipse2D.Float(-0.5, -0.3, 0.24, 0.6), false); 307 cameraHumanBodyAreaPath.append(new java.awt.geom.Ellipse2D.Float(0.26, -0.3, 0.24, 0.6), false); 308 PlanComponent.CAMERA_HUMAN_BODY = new java.awt.geom.Area(cameraHumanBodyAreaPath); 309 310 var cameraHumanHeadAreaPath = new java.awt.geom.GeneralPath(); 311 cameraHumanHeadAreaPath.append(new java.awt.geom.Ellipse2D.Float(-0.18, -0.45, 0.36, 1.0), false); 312 cameraHumanHeadAreaPath.moveTo(-0.04, 0.55); 313 cameraHumanHeadAreaPath.lineTo(0, 0.65); 314 cameraHumanHeadAreaPath.lineTo(0.04, 0.55); 315 cameraHumanHeadAreaPath.closePath(); 316 PlanComponent.CAMERA_HUMAN_HEAD = new java.awt.geom.Area(cameraHumanHeadAreaPath); 317 318 var cameraBodyAreaPath = new java.awt.geom.GeneralPath(); 319 cameraBodyAreaPath.moveTo(0.5, 0.3); 320 cameraBodyAreaPath.lineTo(0.45, 0.35); 321 cameraBodyAreaPath.lineTo(0.2, 0.35); 322 cameraBodyAreaPath.lineTo(0.2, 0.5); 323 cameraBodyAreaPath.lineTo(-0.2, 0.5); 324 cameraBodyAreaPath.lineTo(-0.2, 0.35); 325 cameraBodyAreaPath.lineTo(-0.3, 0.35); 326 cameraBodyAreaPath.lineTo(-0.35, 0.5); 327 cameraBodyAreaPath.lineTo(-0.5, 0.3); 328 cameraBodyAreaPath.lineTo(-0.5, -0.45); 329 cameraBodyAreaPath.lineTo(-0.45, -0.5); 330 cameraBodyAreaPath.lineTo(0.45, -0.5); 331 cameraBodyAreaPath.lineTo(0.5, -0.45); 332 cameraBodyAreaPath.closePath(); 333 PlanComponent.CAMERA_BODY = new java.awt.geom.Area(cameraBodyAreaPath); 334 335 PlanComponent.CAMERA_BUTTON = new java.awt.geom.Ellipse2D.Float(-0.37, -0.2, 0.15, 0.32); 336 337 PlanComponent.DIMENSION_LINE_MARK_END = new java.awt.geom.GeneralPath(); 338 PlanComponent.DIMENSION_LINE_MARK_END.moveTo(-5, 5); 339 PlanComponent.DIMENSION_LINE_MARK_END.lineTo(5, -5); 340 PlanComponent.DIMENSION_LINE_MARK_END.moveTo(0, 5); 341 PlanComponent.DIMENSION_LINE_MARK_END.lineTo(0, -5); 342 343 PlanComponent.VERTICAL_DIMENSION_LINE_DISC = new java.awt.geom.Ellipse2D.Float(-1.5, -1.5, 3, 3); 344 PlanComponent.VERTICAL_DIMENSION_LINE = new java.awt.geom.GeneralPath(); 345 PlanComponent.VERTICAL_DIMENSION_LINE.append(new java.awt.geom.Ellipse2D.Float(-5, -5, 10, 10), false); 346 347 PlanComponent.DIMENSION_LINE_HEIGHT_INDICATOR = PlanComponent.FURNITURE_HEIGHT_INDICATOR; 348 349 PlanComponent.TEXT_LOCATION_INDICATOR = new java.awt.geom.GeneralPath(); 350 PlanComponent.TEXT_LOCATION_INDICATOR.append(new java.awt.geom.Arc2D.Float(-2, 0, 4, 4, 190, 160, java.awt.geom.Arc2D.CHORD), false); 351 PlanComponent.TEXT_LOCATION_INDICATOR.moveTo(0, 4); 352 PlanComponent.TEXT_LOCATION_INDICATOR.lineTo(0, 12); 353 PlanComponent.TEXT_LOCATION_INDICATOR.moveTo(-1.2, 8.5); 354 PlanComponent.TEXT_LOCATION_INDICATOR.lineTo(0.0, 11.5); 355 PlanComponent.TEXT_LOCATION_INDICATOR.lineTo(1.2, 8.5); 356 PlanComponent.TEXT_LOCATION_INDICATOR.moveTo(2.0, 3.0); 357 PlanComponent.TEXT_LOCATION_INDICATOR.lineTo(9, 6); 358 PlanComponent.TEXT_LOCATION_INDICATOR.moveTo(6, 6.5); 359 PlanComponent.TEXT_LOCATION_INDICATOR.lineTo(10, 7); 360 PlanComponent.TEXT_LOCATION_INDICATOR.lineTo(7.5, 3.5); 361 PlanComponent.TEXT_LOCATION_INDICATOR.moveTo(-2.0, 3.0); 362 PlanComponent.TEXT_LOCATION_INDICATOR.lineTo(-9, 6); 363 PlanComponent.TEXT_LOCATION_INDICATOR.moveTo(-6, 6.5); 364 PlanComponent.TEXT_LOCATION_INDICATOR.lineTo(-10, 7); 365 PlanComponent.TEXT_LOCATION_INDICATOR.lineTo(-7.5, 3.5); 366 367 PlanComponent.TEXT_ANGLE_INDICATOR = new java.awt.geom.GeneralPath(); 368 PlanComponent.TEXT_ANGLE_INDICATOR.append(new java.awt.geom.Arc2D.Float(-1.25, -1.25, 2.5, 2.5, 10, 160, java.awt.geom.Arc2D.CHORD), false); 369 PlanComponent.TEXT_ANGLE_INDICATOR.append(new java.awt.geom.Arc2D.Float(-8, -8, 16, 16, 30, 120, java.awt.geom.Arc2D.OPEN), false); 370 PlanComponent.TEXT_ANGLE_INDICATOR.moveTo(4.0, -5.2); 371 PlanComponent.TEXT_ANGLE_INDICATOR.lineTo(6.9, -4.0); 372 PlanComponent.TEXT_ANGLE_INDICATOR.lineTo(5.8, -7.0); 373 374 PlanComponent.LABEL_CENTER_INDICATOR = new java.awt.geom.Ellipse2D.Float(-1.0, -1.0, 2, 2); 375 376 PlanComponent.COMPASS_DISC = new java.awt.geom.Ellipse2D.Float(-0.5, -0.5, 1, 1); 377 var stroke = new java.awt.BasicStroke(0.01); 378 PlanComponent.COMPASS = new java.awt.geom.GeneralPath(stroke.createStrokedShape(PlanComponent.COMPASS_DISC)); 379 PlanComponent.COMPASS.append(stroke.createStrokedShape(new java.awt.geom.Line2D.Float(-0.6, 0, -0.5, 0)), false); 380 PlanComponent.COMPASS.append(stroke.createStrokedShape(new java.awt.geom.Line2D.Float(0.6, 0, 0.5, 0)), false); 381 PlanComponent.COMPASS.append(stroke.createStrokedShape(new java.awt.geom.Line2D.Float(0, 0.6, 0, 0.5)), false); 382 stroke = new java.awt.BasicStroke(0.04, java.awt.BasicStroke.CAP_ROUND, java.awt.BasicStroke.JOIN_ROUND); 383 PlanComponent.COMPASS.append(stroke.createStrokedShape(new java.awt.geom.Line2D.Float(0, 0, 0, 0)), false); 384 var compassNeedle = new java.awt.geom.GeneralPath(); 385 compassNeedle.moveTo(0, -0.47); 386 compassNeedle.lineTo(0.15, 0.46); 387 compassNeedle.lineTo(0, 0.32); 388 compassNeedle.lineTo(-0.15, 0.46); 389 compassNeedle.closePath(); 390 stroke = new java.awt.BasicStroke(0.03); 391 PlanComponent.COMPASS.append(stroke.createStrokedShape(compassNeedle), false); 392 var compassNorthDirection = new java.awt.geom.GeneralPath(); 393 compassNorthDirection.moveTo(-0.07, -0.55); 394 compassNorthDirection.lineTo(-0.07, -0.69); 395 compassNorthDirection.lineTo(0.07, -0.56); 396 compassNorthDirection.lineTo(0.07, -0.7); 397 PlanComponent.COMPASS.append(stroke.createStrokedShape(compassNorthDirection), false); 398 399 PlanComponent.COMPASS_ROTATION_INDICATOR = new java.awt.geom.GeneralPath(); 400 PlanComponent.COMPASS_ROTATION_INDICATOR.append(PlanComponent.POINT_INDICATOR, false); 401 PlanComponent.COMPASS_ROTATION_INDICATOR.append(new java.awt.geom.Arc2D.Float(-8, -7, 16, 16, 210, 120, java.awt.geom.Arc2D.OPEN), false); 402 PlanComponent.COMPASS_ROTATION_INDICATOR.moveTo(4.0, 5.66); 403 PlanComponent.COMPASS_ROTATION_INDICATOR.lineTo(7.0, 5.66); 404 PlanComponent.COMPASS_ROTATION_INDICATOR.lineTo(5.6, 8.3); 405 406 transform = java.awt.geom.AffineTransform.getRotateInstance(Math.PI / 2); 407 PlanComponent.DIMENSION_LINE_HEIGHT_ROTATION_INDICATOR = PlanComponent.COMPASS_ROTATION_INDICATOR.createTransformedShape(transform); 408 409 410 PlanComponent.COMPASS_RESIZE_INDICATOR = new java.awt.geom.GeneralPath(); 411 PlanComponent.COMPASS_RESIZE_INDICATOR.append(new java.awt.geom.Rectangle2D.Float(-1.5, -1.5, 3.0, 3.0), false); 412 PlanComponent.COMPASS_RESIZE_INDICATOR.moveTo(4, -6); 413 PlanComponent.COMPASS_RESIZE_INDICATOR.lineTo(6, -6); 414 PlanComponent.COMPASS_RESIZE_INDICATOR.lineTo(6, 6); 415 PlanComponent.COMPASS_RESIZE_INDICATOR.lineTo(4, 6); 416 PlanComponent.COMPASS_RESIZE_INDICATOR.moveTo(5, 0); 417 PlanComponent.COMPASS_RESIZE_INDICATOR.lineTo(9, 0); 418 PlanComponent.COMPASS_RESIZE_INDICATOR.moveTo(9, -1.5); 419 PlanComponent.COMPASS_RESIZE_INDICATOR.lineTo(12, 0); 420 PlanComponent.COMPASS_RESIZE_INDICATOR.lineTo(9, 1.5); 421 422 PlanComponent.ARROW = new java.awt.geom.GeneralPath(); 423 PlanComponent.ARROW.moveTo(-5, -2); 424 PlanComponent.ARROW.lineTo(0, 0); 425 PlanComponent.ARROW.lineTo(-5, 2); 426 427 PlanComponent.ERROR_TEXTURE_IMAGE = TextureManager.getInstance().getErrorImage(); 428 PlanComponent.WAIT_TEXTURE_IMAGE = TextureManager.getInstance().getWaitImage(); 429 430 PlanComponent.WEBGL_AVAILABLE = true; 431 var canvas = document.createElement("canvas"); 432 var gl = canvas.getContext("webgl"); 433 if (!gl) { 434 gl = canvas.getContext("experimental-webgl"); 435 if (!gl) { 436 PlanComponent.WEBGL_AVAILABLE = false; 437 } 438 } 439 440 PlanComponent.LONG_TOUCH_DELAY = 200; // ms 441 PlanComponent.LONG_TOUCH_DELAY_WHEN_DRAGGING = 400; // ms 442 PlanComponent.LONG_TOUCH_DURATION_AFTER_DELAY = 800; // ms 443 PlanComponent.DOUBLE_TOUCH_DELAY = 500; // ms 444 } 445 446 PlanComponent.initStatics(); 447 448 /** 449 * The circumstances under which the home items displayed by this component will be painted. 450 * @enum 451 * @property {PlanComponent.PaintMode} PAINT 452 * @property {PlanComponent.PaintMode} PRINT 453 * @property {PlanComponent.PaintMode} CLIPBOARD 454 * @property {PlanComponent.PaintMode} EXPORT 455 */ 456 PlanComponent.PaintMode = {}; 457 PlanComponent.PaintMode[PlanComponent.PaintMode["PAINT"] = 0] = "PAINT"; 458 PlanComponent.PaintMode[PlanComponent.PaintMode["PRINT"] = 1] = "PRINT"; 459 PlanComponent.PaintMode[PlanComponent.PaintMode["CLIPBOARD"] = 2] = "CLIPBOARD"; 460 PlanComponent.PaintMode[PlanComponent.PaintMode["EXPORT"] = 3] = "EXPORT"; 461 462 /** 463 * @private 464 */ 465 PlanComponent.ActionType = {}; 466 PlanComponent.ActionType[PlanComponent.ActionType["DELETE_SELECTION"] = 0] = "DELETE_SELECTION"; 467 PlanComponent.ActionType[PlanComponent.ActionType["ESCAPE"] = 1] = "ESCAPE"; 468 PlanComponent.ActionType[PlanComponent.ActionType["MOVE_SELECTION_LEFT"] = 2] = "MOVE_SELECTION_LEFT"; 469 PlanComponent.ActionType[PlanComponent.ActionType["MOVE_SELECTION_UP"] = 3] = "MOVE_SELECTION_UP"; 470 PlanComponent.ActionType[PlanComponent.ActionType["MOVE_SELECTION_DOWN"] = 4] = "MOVE_SELECTION_DOWN"; 471 PlanComponent.ActionType[PlanComponent.ActionType["MOVE_SELECTION_RIGHT"] = 5] = "MOVE_SELECTION_RIGHT"; 472 PlanComponent.ActionType[PlanComponent.ActionType["MOVE_SELECTION_FAST_LEFT"] = 6] = "MOVE_SELECTION_FAST_LEFT"; 473 PlanComponent.ActionType[PlanComponent.ActionType["MOVE_SELECTION_FAST_UP"] = 7] = "MOVE_SELECTION_FAST_UP"; 474 PlanComponent.ActionType[PlanComponent.ActionType["MOVE_SELECTION_FAST_DOWN"] = 8] = "MOVE_SELECTION_FAST_DOWN"; 475 PlanComponent.ActionType[PlanComponent.ActionType["MOVE_SELECTION_FAST_RIGHT"] = 9] = "MOVE_SELECTION_FAST_RIGHT"; 476 PlanComponent.ActionType[PlanComponent.ActionType["TOGGLE_MAGNETISM_ON"] = 10] = "TOGGLE_MAGNETISM_ON"; 477 PlanComponent.ActionType[PlanComponent.ActionType["TOGGLE_MAGNETISM_OFF"] = 11] = "TOGGLE_MAGNETISM_OFF"; 478 PlanComponent.ActionType[PlanComponent.ActionType["ACTIVATE_ALIGNMENT"] = 12] = "ACTIVATE_ALIGNMENT"; 479 PlanComponent.ActionType[PlanComponent.ActionType["DEACTIVATE_ALIGNMENT"] = 13] = "DEACTIVATE_ALIGNMENT"; 480 PlanComponent.ActionType[PlanComponent.ActionType["ACTIVATE_DUPLICATION"] = 14] = "ACTIVATE_DUPLICATION"; 481 PlanComponent.ActionType[PlanComponent.ActionType["DEACTIVATE_DUPLICATION"] = 15] = "DEACTIVATE_DUPLICATION"; 482 PlanComponent.ActionType[PlanComponent.ActionType["ACTIVATE_EDITIION"] = 16] = "ACTIVATE_EDITIION"; 483 PlanComponent.ActionType[PlanComponent.ActionType["DEACTIVATE_EDITIION"] = 17] = "DEACTIVATE_EDITIION"; 484 485 /** 486 * Indicator types that may be displayed on selected items. 487 * @constructor 488 */ 489 PlanComponent.IndicatorType = function(name) { 490 this.name = name; 491 } 492 493 /** 494 * @return {string} 495 */ 496 PlanComponent.IndicatorType.prototype.name = function() { 497 return this.name; 498 } 499 500 /** 501 * @return {string} 502 */ 503 PlanComponent.IndicatorType.prototype.toString = function() { 504 return this.name; 505 } 506 PlanComponent.IndicatorType.ROTATE = new PlanComponent.IndicatorType("ROTATE"); 507 PlanComponent.IndicatorType.RESIZE = new PlanComponent.IndicatorType("RESIZE"); 508 PlanComponent.IndicatorType.ELEVATE = new PlanComponent.IndicatorType("ELEVATE"); 509 PlanComponent.IndicatorType.RESIZE_HEIGHT = new PlanComponent.IndicatorType("RESIZE_HEIGHT"); 510 PlanComponent.IndicatorType.CHANGE_POWER = new PlanComponent.IndicatorType("CHANGE_POWER"); 511 PlanComponent.IndicatorType.MOVE_TEXT = new PlanComponent.IndicatorType("MOVE_TEXT"); 512 PlanComponent.IndicatorType.ROTATE_TEXT = new PlanComponent.IndicatorType("ROTATE_TEXT"); 513 PlanComponent.IndicatorType.ROTATE_PITCH = new PlanComponent.IndicatorType("ROTATE_PITCH"); 514 PlanComponent.IndicatorType.ROTATE_ROLL = new PlanComponent.IndicatorType("ROTATE_ROLL"); 515 PlanComponent.IndicatorType.ARC_EXTENT = new PlanComponent.IndicatorType("ARC_EXTENT"); 516 517 /** 518 * Returns the HTML element used to view this component at screen. 519 */ 520 PlanComponent.prototype.getHTMLElement = function() { 521 return this.container; 522 } 523 524 /** 525 * Adds home items and selection listeners on this component to receive 526 * changes notifications from home. 527 * @param {Home} home 528 * @param {UserPreferences} preferences 529 * @param {PlanController} controller 530 * @private 531 */ 532 PlanComponent.prototype.addModelListeners = function(home, preferences, controller) { 533 var plan = this; 534 var furnitureChangeListener = function(ev) { 535 if (plan.furnitureTopViewIconKeys != null 536 && ("MODEL" == ev.getPropertyName() 537 || "MODEL_ROTATION" == ev.getPropertyName() 538 || "MODEL_FLAGS" == ev.getPropertyName() 539 || "MODEL_TRANSFORMATIONS" == ev.getPropertyName() 540 || "ROLL" == ev.getPropertyName() 541 || "PITCH" == ev.getPropertyName() 542 || ("WIDTH_IN_PLAN" == ev.getPropertyName() 543 || "DEPTH_IN_PLAN" == ev.getPropertyName() 544 || "HEIGHT_IN_PLAN" == ev.getPropertyName()) 545 && (ev.getSource().isHorizontallyRotated() 546 || ev.getSource().getTexture() != null) 547 || "MODEL_MIRRORED" == ev.getPropertyName() 548 && ev.getSource().getRoll() != 0)) { 549 if ("HEIGHT_IN_PLAN" == ev.getPropertyName()) { 550 plan.sortedLevelFurniture = null; 551 } 552 if (!(ev.getSource() instanceof HomeFurnitureGroup)) { 553 if (controller == null || !controller.isModificationState()) { 554 plan.removeTopViewIconFromCache(ev.getSource()); 555 } else { 556 if (plan.invalidFurnitureTopViewIcons == null) { 557 plan.invalidFurnitureTopViewIcons = []; 558 var modificationStateListener = function(ev2) { 559 for (var i = 0; i < plan.invalidFurnitureTopViewIcons.length; i++) { 560 plan.removeTopViewIconFromCache(plan.invalidFurnitureTopViewIcons[i]); 561 } 562 plan.invalidFurnitureTopViewIcons = null; 563 plan.repaint(); 564 controller.removePropertyChangeListener("MODIFICATION_STATE", modificationStateListener); 565 }; 566 controller.addPropertyChangeListener("MODIFICATION_STATE", modificationStateListener); 567 } 568 if (plan.invalidFurnitureTopViewIcons.indexOf(ev.getSource()) < 0) { 569 plan.invalidFurnitureTopViewIcons.push(ev.getSource()); 570 } 571 } 572 } 573 plan.revalidate(); 574 } else if (plan.furnitureTopViewIconKeys != null 575 && ("PLAN_ICON" == ev.getPropertyName() 576 || "COLOR" == ev.getPropertyName() 577 || "TEXTURE" == ev.getPropertyName() 578 || "MODEL_MATERIALS" == ev.getPropertyName() 579 || "SHININESS" == ev.getPropertyName())) { 580 plan.removeTopViewIconFromCache(ev.getSource()); 581 plan.repaint(); 582 } else if ("ELEVATION" == ev.getPropertyName() 583 || "LEVEL" == ev.getPropertyName() 584 || "HEIGHT_IN_PLAN" == ev.getPropertyName()) { 585 plan.sortedLevelFurniture = null; 586 plan.repaint(); 587 } else if ("ICON" == ev.getPropertyName() 588 || "WALL_CUT_OUT_ON_BOTH_SIDES" == ev.getPropertyName()) { 589 plan.repaint(); 590 } else if (plan.doorOrWindowWallThicknessAreasCache != null 591 && ("WIDTH" == ev.getPropertyName() 592 || "DEPTH" == ev.getPropertyName() 593 || "ANGLE" == ev.getPropertyName() 594 || "MODEL_MIRRORED" == ev.getPropertyName() 595 || "X" == ev.getPropertyName() 596 || "Y" == ev.getPropertyName() 597 || "LEVEL" == ev.getPropertyName() 598 || "WALL_THICKNESS" == ev.getPropertyName() 599 || "WALL_DISTANCE" == ev.getPropertyName() 600 || "WALL_WIDTH" == ev.getPropertyName() 601 || "WALL_LEFT" == ev.getPropertyName() 602 || "CUT_OUT_SHAPE" == ev.getPropertyName()) 603 && CoreTools.removeFromMap(plan.doorOrWindowWallThicknessAreasCache, ev.getSource()) != null) { 604 plan.revalidate(); 605 } else { 606 plan.revalidate(); 607 } 608 }; 609 if (home.getFurniture() != null) { 610 home.getFurniture().forEach(function(piece) { 611 piece.addPropertyChangeListener(furnitureChangeListener); 612 if (piece instanceof HomeFurnitureGroup) { 613 piece.getAllFurniture().forEach(function(childPiece) { 614 childPiece.addPropertyChangeListener(furnitureChangeListener); 615 }); 616 } 617 }); 618 } 619 var furnitureChangeListenerRemover = function(piece) { 620 piece.removePropertyChangeListener(furnitureChangeListener); 621 plan.removeTopViewIconFromCache(piece); 622 if (piece instanceof HomeDoorOrWindow 623 && plan.doorOrWindowWallThicknessAreasCache != null) { 624 CoreTools.removeFromMap(plan.doorOrWindowWallThicknessAreasCache, piece); 625 } 626 }; 627 home.addFurnitureListener(function(ev) { 628 var piece = ev.getItem(); 629 if (ev.getType() === CollectionEvent.Type.ADD) { 630 piece.addPropertyChangeListener(furnitureChangeListener); 631 if (piece instanceof HomeFurnitureGroup) { 632 piece.getAllFurniture().forEach(function(childPiece) { 633 childPiece.addPropertyChangeListener(furnitureChangeListener); 634 }); 635 } 636 } else if (ev.getType() === CollectionEvent.Type.DELETE) { 637 furnitureChangeListenerRemover(piece); 638 if (piece instanceof HomeFurnitureGroup) { 639 piece.getAllFurniture().forEach(function(childPiece) { 640 furnitureChangeListenerRemover(childPiece); 641 }); 642 } 643 } 644 plan.sortedLevelFurniture = null; 645 plan.revalidate(); 646 }); 647 var wallChangeListener = function(ev) { 648 var propertyName = ev.getPropertyName(); 649 if ("X_START" == propertyName 650 || "X_END" == propertyName 651 || "Y_START" == propertyName 652 || "Y_END" == propertyName 653 || "WALL_AT_START" == propertyName 654 || "WALL_AT_END" == propertyName 655 || "THICKNESS" == propertyName 656 || "ARC_EXTENT" == propertyName 657 || "PATTERN" == propertyName) { 658 if (plan.home.isAllLevelsSelection()) { 659 plan.otherLevelsWallAreaCache = null; 660 plan.otherLevelsWallsCache = null; 661 } 662 plan.wallAreasCache = null; 663 plan.doorOrWindowWallThicknessAreasCache = null; 664 plan.revalidate(); 665 } else if ("LEVEL" == propertyName 666 || "HEIGHT" == propertyName 667 || "HEIGHT_AT_END" == propertyName) { 668 plan.otherLevelsWallAreaCache = null; 669 plan.otherLevelsWallsCache = null; 670 plan.wallAreasCache = null; 671 plan.repaint(); 672 } 673 }; 674 if (home.getWalls() != null) { 675 home.getWalls().forEach(function(wall) { 676 wall.addPropertyChangeListener(wallChangeListener); 677 }); 678 } 679 home.addWallsListener(function(ev) { 680 if (ev.getType() === CollectionEvent.Type.ADD) { 681 ev.getItem().addPropertyChangeListener(wallChangeListener); 682 } else if (ev.getType() === CollectionEvent.Type.DELETE) { 683 ev.getItem().removePropertyChangeListener(wallChangeListener); 684 } 685 plan.otherLevelsWallAreaCache = null; 686 plan.otherLevelsWallsCache = null; 687 plan.wallAreasCache = null; 688 plan.doorOrWindowWallThicknessAreasCache = null; 689 plan.revalidate(); 690 }); 691 var roomChangeListener = function(ev) { 692 var propertyName = ev.getPropertyName(); 693 if ("POINTS" == propertyName 694 || "NAME" == propertyName 695 || "NAME_X_OFFSET" == propertyName 696 || "NAME_Y_OFFSET" == propertyName 697 || "NAME_STYLE" == propertyName 698 || "NAME_ANGLE" == propertyName 699 || "AREA_VISIBLE" == propertyName 700 || "AREA_X_OFFSET" == propertyName 701 || "AREA_Y_OFFSET" == propertyName 702 || "AREA_STYLE" == propertyName 703 || "AREA_ANGLE" == propertyName 704 || "FLOOR_VISIBLE" == propertyName 705 || "CEILING_VISIBLE" == propertyName) { 706 plan.sortedLevelRooms = null; 707 plan.otherLevelsRoomAreaCache = null; 708 plan.otherLevelsRoomsCache = null; 709 plan.revalidate(); 710 } else if (plan.preferences.isRoomFloorColoredOrTextured() 711 && ("FLOOR_COLOR" == propertyName 712 || "FLOOR_TEXTURE" == propertyName 713 || "FLOOR_VISIBLE" == propertyName)) { 714 plan.repaint(); 715 } 716 }; 717 if (home.getRooms() != null) { 718 home.getRooms().forEach(function(room) { 719 return room.addPropertyChangeListener(roomChangeListener); 720 }); 721 } 722 home.addRoomsListener(function(ev) { 723 if (ev.getType() === CollectionEvent.Type.ADD) { 724 ev.getItem().addPropertyChangeListener(roomChangeListener); 725 } else if (ev.getType() === CollectionEvent.Type.DELETE) { 726 ev.getItem().removePropertyChangeListener(roomChangeListener); 727 } 728 plan.sortedLevelRooms = null; 729 plan.otherLevelsRoomAreaCache = null; 730 plan.otherLevelsRoomsCache = null; 731 plan.revalidate(); 732 }); 733 var changeListener = function(ev) { 734 var propertyName = ev.getPropertyName(); 735 if ("COLOR" == propertyName 736 || "DASH_STYLE" == propertyName) { 737 plan.repaint(); 738 } else { 739 plan.revalidate(); 740 } 741 }; 742 if (home.getPolylines() != null) { 743 home.getPolylines().forEach(function(polyline) { 744 return polyline.addPropertyChangeListener(changeListener); 745 }); 746 } 747 home.addPolylinesListener(function(ev) { 748 if (ev.getType() === CollectionEvent.Type.ADD) { 749 ev.getItem().addPropertyChangeListener(changeListener); 750 } else if (ev.getType() === CollectionEvent.Type.DELETE) { 751 ev.getItem().removePropertyChangeListener(changeListener); 752 } 753 plan.revalidate(); 754 }); 755 var dimensionLineChangeListener = function(ev) { 756 var propertyName = ev.getPropertyName(); 757 if ("X_START" == propertyName 758 || "X_END" == propertyName 759 || "Y_START" == propertyName 760 || "Y_END" == propertyName 761 || "ELEVATION_START" == propertyName 762 || "ELEVATION_END" == propertyName 763 || "OFFSET" == propertyName 764 || "END_MARK_SIZE" == propertyName 765 || "PITCH" == propertyName 766 || "LENGTH_STYLE" == propertyName) { 767 return plan.revalidate(); 768 } else if ("COLOR" == propertyName) { 769 plan.repaint(); 770 } 771 }; 772 if (home.getDimensionLines() != null) { 773 home.getDimensionLines().forEach(function(dimensionLine) { 774 return dimensionLine.addPropertyChangeListener(dimensionLineChangeListener); 775 }); 776 } 777 home.addDimensionLinesListener(function(ev) { 778 if (ev.getType() === CollectionEvent.Type.ADD) { 779 ev.getItem().addPropertyChangeListener(dimensionLineChangeListener); 780 } else if (ev.getType() === CollectionEvent.Type.DELETE) { 781 ev.getItem().removePropertyChangeListener(dimensionLineChangeListener); 782 } 783 plan.revalidate(); 784 }); 785 var labelChangeListener = function(ev) { 786 return plan.revalidate(); 787 }; 788 if (home.getLabels() != null) { 789 home.getLabels().forEach(function(label) { 790 return label.addPropertyChangeListener(labelChangeListener); 791 }); 792 } 793 home.addLabelsListener(function(ev) { 794 if (ev.getType() === CollectionEvent.Type.ADD) { 795 ev.getItem().addPropertyChangeListener(labelChangeListener); 796 } else if (ev.getType() === CollectionEvent.Type.DELETE) { 797 ev.getItem().removePropertyChangeListener(labelChangeListener); 798 } 799 plan.revalidate(); 800 }); 801 var levelChangeListener = function(ev) { 802 var propertyName = ev.getPropertyName(); 803 if ("BACKGROUND_IMAGE" == propertyName) { 804 plan.backgroundImageCache = null; 805 plan.revalidate(); 806 } else if ("ELEVATION" == propertyName 807 || "ELEVATION_INDEX" == propertyName 808 || "VIEWABLE" == propertyName) { 809 plan.clearLevelCache(); 810 plan.repaint(); 811 } 812 }; 813 if (home.getLevels() != null) { 814 home.getLevels().forEach(function(level) { 815 return level.addPropertyChangeListener(levelChangeListener); 816 }); 817 } 818 home.addLevelsListener(function(ev) { 819 var level = ev.getItem(); 820 if (ev.getType() === CollectionEvent.Type.ADD) { 821 level.addPropertyChangeListener(levelChangeListener); 822 } else if (ev.getType() === CollectionEvent.Type.DELETE) { 823 level.removePropertyChangeListener(levelChangeListener); 824 } 825 plan.revalidate(); 826 }); 827 home.addPropertyChangeListener("CAMERA", function(ev) { 828 return plan.revalidate(); 829 }); 830 home.getObserverCamera().addPropertyChangeListener(function(ev) { 831 var propertyName = ev.getPropertyName(); 832 if ("X" == propertyName 833 || "Y" == propertyName 834 || "FIELD_OF_VIEW" == propertyName 835 || "YAW" == propertyName 836 || "WIDTH" == propertyName 837 || "DEPTH" == propertyName 838 || "HEIGHT" == propertyName) { 839 plan.revalidate(); 840 } 841 }); 842 home.getCompass().addPropertyChangeListener(function(ev) { 843 var propertyName = ev.getPropertyName(); 844 if ("X" == propertyName 845 || "Y" == propertyName 846 || "NORTH_DIRECTION" == propertyName 847 || "DIAMETER" == propertyName 848 || "VISIBLE" == propertyName) { 849 plan.revalidate(); 850 } 851 }); 852 home.addSelectionListener({ 853 selectionChanged: function(ev) { 854 return plan.repaint(); 855 } 856 }); 857 home.addPropertyChangeListener("BACKGROUND_IMAGE", function(ev) { 858 plan.backgroundImageCache = null; 859 plan.repaint(); 860 }); 861 home.addPropertyChangeListener("SELECTED_LEVEL", function(ev) { 862 plan.clearLevelCache(); 863 plan.repaint(); 864 }); 865 866 this.preferencesListener = new PlanComponent.UserPreferencesChangeListener(this); 867 preferences.addPropertyChangeListener("UNIT", this.preferencesListener); 868 preferences.addPropertyChangeListener("LANGUAGE", this.preferencesListener); 869 preferences.addPropertyChangeListener("GRID_VISIBLE", this.preferencesListener); 870 preferences.addPropertyChangeListener("DEFAULT_FONT_NAME", this.preferencesListener); 871 preferences.addPropertyChangeListener("FURNITURE_VIEWED_FROM_TOP", this.preferencesListener); 872 preferences.addPropertyChangeListener("FURNITURE_MODEL_ICON_SIZE", this.preferencesListener); 873 preferences.addPropertyChangeListener("ROOM_FLOOR_COLORED_OR_TEXTURED", this.preferencesListener); 874 preferences.addPropertyChangeListener("WALL_PATTERN", this.preferencesListener); 875 } 876 877 /** 878 * Preferences property listener bound to this component with a weak reference to avoid 879 * strong link between preferences and this component. 880 * @param {PlanComponent} planComponent 881 * @constructor 882 * @private 883 */ 884 PlanComponent.UserPreferencesChangeListener = function(planComponent) { 885 this.planComponent = planComponent; 886 } 887 888 PlanComponent.UserPreferencesChangeListener.prototype.propertyChange = function(ev) { 889 var planComponent = this.planComponent; 890 var preferences = ev.getSource(); 891 var property = ev.getPropertyName(); 892 if (planComponent == null) { 893 preferences.removePropertyChangeListener(property, this); 894 } else { 895 switch ((property)) { 896 case "LANGUAGE": 897 case "UNIT": 898 if (planComponent.horizontalRuler != null) { 899 planComponent.horizontalRuler.repaint(); 900 } 901 if (planComponent.verticalRuler != null) { 902 planComponent.verticalRuler.repaint(); 903 } 904 break; 905 case "DEFAULT_FONT_NAME": 906 planComponent.fonts = null; 907 planComponent.fontsMetrics = null; 908 planComponent.revalidate(); 909 break; 910 case "WALL_PATTERN": 911 planComponent.wallAreasCache = null; 912 break; 913 case "FURNITURE_VIEWED_FROM_TOP": 914 if (planComponent.furnitureTopViewIconKeys != null && !preferences.isFurnitureViewedFromTop()) { 915 planComponent.furnitureTopViewIconKeys = null; 916 planComponent.furnitureTopViewIconsCache = null; 917 } 918 break; 919 case "FURNITURE_MODEL_ICON_SIZE": 920 planComponent.furnitureTopViewIconKeys = null; 921 planComponent.furnitureTopViewIconsCache = null; 922 break; 923 default: 924 break; 925 } 926 planComponent.repaint(); 927 } 928 } 929 930 /** 931 * Removes piece from maps handling top view icons. 932 * @private 933 */ 934 PlanComponent.prototype.removeTopViewIconFromCache = function(piece) { 935 if (this.furnitureTopViewIconKeys != null) { 936 // Explicitely remove deleted object from some maps since there's no WeakHashMap 937 var topViewIconKey = CoreTools.removeFromMap(this.furnitureTopViewIconKeys, piece); 938 if (topViewIconKey != null) { 939 // Update furnitureTopViewIconsCache too if topViewIconKey isn't used anymore 940 var keys = CoreTools.valuesFromMap(this.furnitureTopViewIconKeys); 941 var removedKeyFound = false; 942 for (var i = 0; i < keys.length; i++) { 943 if (keys [i].hashCode === topViewIconKey.hashCode // TODO Why prototype is lost? 944 && keys [i].equals(topViewIconKey)) { 945 removedKeyFound = true; 946 } 947 } 948 if (!removedKeyFound) { 949 CoreTools.removeFromMap(this.furnitureTopViewIconsCache, topViewIconKey); 950 } 951 } 952 } 953 } 954 955 /** 956 * Clears the cached information bound to level. 957 * @private 958 */ 959 PlanComponent.prototype.clearLevelCache = function() { 960 this.backgroundImageCache = null; 961 this.otherLevelsWallAreaCache = null; 962 this.otherLevelsWallsCache = null; 963 this.otherLevelsRoomAreaCache = null; 964 this.otherLevelsRoomsCache = null; 965 this.wallAreasCache = null; 966 this.doorOrWindowWallThicknessAreasCache = null; 967 this.sortedLevelRooms = null; 968 this.sortedLevelFurniture = null; 969 } 970 971 /** 972 * @private 973 */ 974 PlanComponent.prototype.isEnabled = function() { 975 return true; 976 } 977 978 PlanComponent.prototype.revalidate = function() { 979 this.invalidate(true); 980 this.validate(); 981 this.repaint(); 982 } 983 984 /** 985 * @private 986 */ 987 PlanComponent.prototype.invalidate = function(invalidatePlanBoundsCache) { 988 if (invalidatePlanBoundsCache) { 989 var planBoundsCacheWereValid = this.planBoundsCacheValid; 990 if (!this.invalidPlanBounds) { 991 this.invalidPlanBounds = this.getPlanBounds().getBounds2D(); 992 } 993 if (planBoundsCacheWereValid) { 994 this.planBoundsCacheValid = false; 995 } 996 } 997 } 998 999 /** 1000 * @private 1001 */ 1002 PlanComponent.prototype.validate = function() { 1003 if (this.invalidPlanBounds != null) { 1004 var size = this.getPreferredSize(); 1005 if (this.isScrolled()) { 1006 this.view.style.width = size.width + "px"; 1007 this.view.style.height = size.height + "px"; 1008 if (this.canvas.width !== this.scrollPane.clientWidth * this.resolutionScale 1009 || this.canvas.height !== this.scrollPane.clientHeight * this.resolutionScale) { 1010 this.canvas.width = this.scrollPane.clientWidth * this.resolutionScale; 1011 this.canvas.height = this.scrollPane.clientHeight * this.resolutionScale; 1012 this.canvas.style.width = this.scrollPane.clientWidth + "px"; 1013 this.canvas.style.height = this.scrollPane.clientHeight + "px"; 1014 } 1015 1016 var planBoundsNewMinX = this.getPlanBounds().getMinX(); 1017 var planBoundsNewMinY = this.getPlanBounds().getMinY(); 1018 // If plan bounds upper left corner diminished 1019 if (planBoundsNewMinX < this.invalidPlanBounds.getMinX() 1020 || planBoundsNewMinY < this.invalidPlanBounds.getMinY()) { 1021 // Update view position when scroll bars are visible 1022 if (this.scrollPane.clientWidth < this.view.clientWidth 1023 || this.scrollPane.clientHeight < this.view.clientHeight.height) { 1024 var deltaX = this.convertLengthToPixel(this.invalidPlanBounds.getMinX() - planBoundsNewMinX); 1025 var deltaY = this.convertLengthToPixel(this.invalidPlanBounds.getMinY() - planBoundsNewMinY); 1026 this.scrollPane.scrollLeft += deltaX; 1027 this.scrollPane.scrollTop += deltaY; 1028 } 1029 } 1030 } else if (this.canvas.width !== this.canvas.clientWidth 1031 || this.canvas.height !== this.canvas.clientHeight) { 1032 this.canvas.width = this.canvas.clientWidth; 1033 this.canvas.height = this.canvas.clientHeight; 1034 } 1035 } 1036 delete this.invalidPlanBounds; 1037 } 1038 1039 /** 1040 * @private 1041 */ 1042 PlanComponent.prototype.isScrolled = function() { 1043 return this.scrollPane !== undefined; 1044 } 1045 1046 /** 1047 * Adds mouse listeners to this component that calls back <code>controller</code> methods. 1048 * @param {PlanController} controller 1049 * @private 1050 */ 1051 PlanComponent.prototype.addMouseListeners = function(controller) { 1052 var plan = this; 1053 var mouseListener = { 1054 initialPointerLocation: null, 1055 lastPointerLocation: null, 1056 touchEventType : false, 1057 pointerTouches : {}, 1058 lastEventType : null, 1059 lastTargetTouches : [], 1060 distanceLastPinch: null, 1061 panningAfterPinch: false, 1062 firstTouchStartedTimeStamp: 0, 1063 longTouchStartTime: 0, 1064 autoScroll: null, 1065 longTouch: null, 1066 longTouchWhenDragged: false, 1067 actionStartedInPlanComponent: false, 1068 contextMenuEventType: false, 1069 mousePressed: function(ev) { 1070 if (!mouseListener.touchEventType 1071 && !mouseListener.contextMenuEventType 1072 && plan.isEnabled() && ev.button === 0) { 1073 mouseListener.updateCoordinates(ev, "mousePressed"); 1074 mouseListener.autoScroll = null; 1075 mouseListener.initialPointerLocation = [ev.canvasX, ev.canvasY]; 1076 mouseListener.lastPointerLocation = [ev.canvasX, ev.canvasY]; 1077 mouseListener.actionStartedInPlanComponent = true; 1078 controller.pressMouse(plan.convertXPixelToModel(ev.canvasX), plan.convertYPixelToModel(ev.canvasY), 1079 ev.clickCount, mouseListener.isShiftDown(ev), mouseListener.isAlignmentActivated(ev), 1080 mouseListener.isDuplicationActivated(ev), mouseListener.isMagnetismToggled(ev)); 1081 } 1082 ev.stopPropagation(); 1083 }, 1084 isShiftDown : function(ev) { 1085 return ev.shiftKey && !ev.altKey && !ev.ctrlKey && !ev.metaKey; 1086 }, 1087 isAlignmentActivated : function(ev) { 1088 return OperatingSystem.isWindows() || OperatingSystem.isMacOSX() 1089 ? ev.shiftKey 1090 : ev.shiftKey && !ev.altKey; 1091 }, 1092 isDuplicationActivated : function(ev) { 1093 return OperatingSystem.isMacOSX() 1094 ? ev.altKey 1095 : ev.ctrlKey; 1096 }, 1097 isMagnetismToggled : function(ev) { 1098 return OperatingSystem.isWindows() 1099 ? ev.altKey 1100 : (OperatingSystem.isMacOSX() 1101 ? ev.metaKey 1102 : ev.shiftKey && ev.altKey); 1103 }, 1104 mouseDoubleClicked: function(ev) { 1105 mouseListener.updateCoordinates(ev, "mouseDoubleClicked"); 1106 mouseListener.mousePressed(ev); 1107 }, 1108 windowMouseMoved: function(ev) { 1109 if (!mouseListener.touchEventType 1110 && !mouseListener.contextMenuEventType) { 1111 mouseListener.updateCoordinates(ev, "mouseMoved"); 1112 // Handle autoscroll 1113 if (mouseListener.lastPointerLocation != null) { 1114 if (mouseListener.autoScroll == null 1115 && !mouseListener.isInCanvas(ev)) { 1116 mouseListener.autoScroll = setInterval(function() { 1117 if (mouseListener.actionStartedInPlanComponent) { 1118 // Dispatch a copy of event (IE doesn't support dispatching with the same event) 1119 var ev2 = document.createEvent("Event"); 1120 ev2.initEvent("mousemove", true, true); 1121 ev2.clientX = ev.clientX; 1122 ev2.clientY = ev.clientY; 1123 window.dispatchEvent(ev2); 1124 } else { 1125 clearInterval(mouseListener.autoScroll); 1126 mouseListener.autoScroll = null; 1127 } 1128 }, 10); 1129 } 1130 if (mouseListener.autoScroll != null 1131 && mouseListener.isInCanvas(ev)) { 1132 clearInterval(mouseListener.autoScroll); 1133 mouseListener.autoScroll = null; 1134 } 1135 mouseListener.lastPointerLocation = [ev.canvasX, ev.canvasY]; 1136 } 1137 1138 if (mouseListener.initialPointerLocation != null 1139 && !(mouseListener.initialPointerLocation[0] === ev.canvasX 1140 && mouseListener.initialPointerLocation[1] === ev.canvasY)) { 1141 mouseListener.initialPointerLocation = null; 1142 } 1143 if (mouseListener.initialPointerLocation == null 1144 && (ev.buttons === 0 && mouseListener.isInCanvas(ev) 1145 || mouseListener.actionStartedInPlanComponent)) { 1146 if (plan.isEnabled()) { 1147 controller.moveMouse(plan.convertXPixelToModel(ev.canvasX), plan.convertYPixelToModel(ev.canvasY)); 1148 } 1149 } 1150 } 1151 }, 1152 windowMouseReleased: function(ev) { 1153 if (!mouseListener.touchEventType) { 1154 if (mouseListener.lastPointerLocation != null) { 1155 // Stop autoscroll 1156 if (mouseListener.autoScroll != null) { 1157 clearInterval(mouseListener.autoScroll); 1158 mouseListener.autoScroll = null; 1159 } 1160 1161 if (mouseListener.actionStartedInPlanComponent 1162 && plan.isEnabled() && ev.button === 0) { 1163 if (mouseListener.contextMenuEventType) { 1164 controller.releaseMouse(plan.convertXPixelToModel(mouseListener.initialPointerLocation[0]), 1165 plan.convertYPixelToModel(mouseListener.initialPointerLocation[1])); 1166 } else { 1167 mouseListener.updateCoordinates(ev, "mouseReleased"); 1168 controller.releaseMouse(plan.convertXPixelToModel(ev.canvasX), plan.convertYPixelToModel(ev.canvasY)); 1169 } 1170 } 1171 mouseListener.initialPointerLocation = null; 1172 mouseListener.lastPointerLocation = null; 1173 mouseListener.actionStartedInPlanComponent = false; 1174 } 1175 } 1176 mouseListener.contextMenuEventType = false; 1177 }, 1178 pointerPressed : function(ev) { 1179 if (ev.pointerType == "mouse") { 1180 mouseListener.mousePressed(ev); 1181 } else { 1182 // Multi touch support for IE and Edge 1183 mouseListener.copyPointerToTargetTouches(ev, false); 1184 mouseListener.touchStarted(ev); 1185 } 1186 }, 1187 pointerMousePressed : function(ev) { 1188 // Required to avoid click simulation 1189 ev.stopPropagation(); 1190 }, 1191 windowPointerMoved : function(ev) { 1192 if (ev.pointerType == "mouse") { 1193 mouseListener.windowMouseMoved(ev); 1194 } else { 1195 // Multi touch support for IE and Edge 1196 mouseListener.copyPointerToTargetTouches(ev, false) 1197 mouseListener.touchMoved(ev); 1198 } 1199 }, 1200 windowPointerReleased : function(ev) { 1201 if (ev.pointerType == "mouse") { 1202 mouseListener.windowMouseReleased(ev); 1203 } else { 1204 ev.preventDefault(); 1205 // Multi touch support for IE and legacy Edge 1206 mouseListener.copyPointerToTargetTouches(ev, true); 1207 mouseListener.touchEnded(ev); 1208 } 1209 }, 1210 contextMenuDisplayed : function(ev) { 1211 mouseListener.contextMenuEventType = true; 1212 }, 1213 touchStarted: function(ev) { 1214 // Do not prevent default behavior to ensure focus events will be fired if focus changed after a touch event 1215 // but track touch event types to avoid them to be managed also for mousedown and dblclick events 1216 mouseListener.touchEventType = ev.pointerType === undefined; 1217 plan.lastTouchEndX = undefined; 1218 plan.lastTouchEndY = undefined; 1219 if (plan.isEnabled()) { 1220 // Prevent default behavior to ensure a second touchstart event will be received 1221 // for double taps under iOS >= 15 1222 ev.preventDefault(); 1223 if (document.activeElement != plan.container) { 1224 // Request focus explicitly since default behavior is disabled 1225 plan.container.focus(); 1226 } 1227 mouseListener.updateCoordinates(ev, "touchStarted"); 1228 mouseListener.autoScroll = null; 1229 if (mouseListener.longTouch != null) { 1230 clearTimeout(mouseListener.longTouch); 1231 mouseListener.longTouch = null; 1232 plan.stopLongTouchAnimation(); 1233 } 1234 1235 if (ev.targetTouches.length === 1) { 1236 var clickCount = 1; 1237 if (mouseListener.initialPointerLocation != null 1238 && mouseListener.distance(ev.canvasX, ev.canvasY, 1239 mouseListener.initialPointerLocation [0], mouseListener.initialPointerLocation [1]) < 5 1240 && ev.timeStamp - mouseListener.firstTouchStartedTimeStamp <= PlanComponent.DOUBLE_TOUCH_DELAY) { 1241 clickCount = 2; 1242 mouseListener.firstTouchStartedTimeStamp = 0; 1243 mouseListener.initialPointerLocation = null; 1244 } else { 1245 mouseListener.firstTouchStartedTimeStamp = ev.timeStamp; 1246 mouseListener.initialPointerLocation = [ev.canvasX, ev.canvasY]; 1247 } 1248 1249 mouseListener.distanceLastPinch = null; 1250 mouseListener.lastPointerLocation = [ev.canvasX, ev.canvasY]; 1251 mouseListener.actionStartedInPlanComponent = true; 1252 mouseListener.longTouchWhenDragged = false; 1253 if (controller.getMode() !== PlanController.Mode.PANNING 1254 && clickCount == 1) { 1255 var character = controller.getMode() === PlanController.Mode.SELECTION 1256 ? '⇪' 1257 : (controller.getMode() === PlanController.Mode.POLYLINE_CREATION 1258 && !controller.isModificationState() 1259 ? 'S' : '2'); 1260 mouseListener.longTouch = setTimeout(function() { 1261 plan.startLongTouchAnimation(ev.canvasX, ev.canvasY, character, 1262 function() { 1263 if (controller.getMode() === PlanController.Mode.SELECTION) { 1264 // Simulate shift key press 1265 controller.setAlignmentActivated(true); 1266 } else if (controller.getMode() === PlanController.Mode.POLYLINE_CREATION) { 1267 // Enable curved or elevation dimension creation 1268 controller.setDuplicationActivated(true); 1269 } 1270 }); 1271 }, PlanComponent.LONG_TOUCH_DELAY); 1272 mouseListener.longTouchStartTime = Date.now(); 1273 } 1274 1275 controller.pressMouse(plan.convertXPixelToModel(ev.canvasX), plan.convertYPixelToModel(ev.canvasY), 1276 clickCount, mouseListener.isShiftDown(ev), mouseListener.isAlignmentActivated(ev), 1277 mouseListener.isDuplicationActivated(ev), mouseListener.isMagnetismToggled(ev), View.PointerType.TOUCH); 1278 } else { 1279 // Cancel autoscroll 1280 if (mouseListener.autoScroll != null) { 1281 clearInterval(mouseListener.autoScroll); 1282 mouseListener.autoScroll = null; 1283 } 1284 // Additional touch allows to escape current modification 1285 controller.escape(); 1286 1287 if (ev.targetTouches.length === 2) { 1288 mouseListener.actionStartedInPlanComponent = true; 1289 mouseListener.initialPointerLocation = null; 1290 mouseListener.distanceLastPinch = mouseListener.distance(ev.targetTouches[0].clientX, ev.targetTouches[0].clientY, 1291 ev.targetTouches[1].clientX, ev.targetTouches[1].clientY); 1292 } 1293 } 1294 } 1295 }, 1296 touchMoved: function(ev) { 1297 if (mouseListener.actionStartedInPlanComponent 1298 && plan.isEnabled()) { 1299 ev.preventDefault(); 1300 ev.stopPropagation(); 1301 if (mouseListener.updateCoordinates(ev, "touchMoved")) { 1302 plan.stopIndicatorAnimation(); 1303 1304 mouseListener.initialPointerLocation = null; 1305 1306 if (ev.targetTouches.length == 1) { 1307 // Handle autoscroll 1308 if (mouseListener.lastPointerLocation != null) { 1309 if (mouseListener.autoScroll != null 1310 && mouseListener.isInCanvas(ev)) { 1311 clearInterval(mouseListener.autoScroll); 1312 mouseListener.autoScroll = null; 1313 } 1314 if (mouseListener.autoScroll == null 1315 && !mouseListener.isInCanvas(ev) 1316 && controller.getMode() !== PlanController.Mode.PANNING 1317 && mouseListener.lastPointerLocation != null) { 1318 mouseListener.autoScroll = setInterval(function() { 1319 if (mouseListener.actionStartedInPlanComponent) { 1320 mouseListener.touchMoved(ev); 1321 } else { 1322 clearInterval(mouseListener.autoScroll); 1323 mouseListener.autoScroll = null; 1324 } 1325 }, 10); 1326 } 1327 } 1328 1329 if (mouseListener.longTouch != null) { 1330 // Cancel long touch animation only when pointer moved during the past 200 ms 1331 clearTimeout(mouseListener.longTouch); 1332 mouseListener.longTouch = null; 1333 plan.stopLongTouchAnimation(); 1334 } 1335 1336 mouseListener.lastPointerLocation = [ev.canvasX, ev.canvasY]; 1337 controller.moveMouse(plan.convertXPixelToModel(ev.canvasX), plan.convertYPixelToModel(ev.canvasY)); 1338 1339 if (!mouseListener.autoScroll 1340 && controller.getMode() !== PlanController.Mode.PANNING 1341 && controller.getMode() !== PlanController.Mode.SELECTION) { 1342 mouseListener.longTouch = setTimeout(function() { 1343 mouseListener.longTouchWhenDragged = true; 1344 plan.startLongTouchAnimation(ev.canvasX, ev.canvasY, '2'); 1345 }, PlanComponent.LONG_TOUCH_DELAY_WHEN_DRAGGING); 1346 mouseListener.longTouchStartTime = Date.now(); 1347 } 1348 } else if (ev.targetTouches.length == 2 1349 && mouseListener.distanceLastPinch != null) { 1350 var newDistance = mouseListener.distance(ev.targetTouches[0].clientX, ev.targetTouches[0].clientY, 1351 ev.targetTouches[1].clientX, ev.targetTouches[1].clientY); 1352 var scaleDifference = newDistance / mouseListener.distanceLastPinch; 1353 var rect = plan.canvas.getBoundingClientRect(); 1354 var x = plan.convertXPixelToModel((ev.targetTouches[0].clientX + ev.targetTouches[1].clientX) / 2 - rect.left); 1355 var y = plan.convertYPixelToModel((ev.targetTouches[0].clientY + ev.targetTouches[1].clientY) / 2 - rect.top); 1356 var oldScale = plan.getScale(); 1357 controller.zoom(scaleDifference); 1358 mouseListener.distanceLastPinch = newDistance; 1359 if (plan.isScrolled() 1360 && plan.getScale() !== oldScale) { 1361 // If scale changed, update viewport position to keep the same coordinates under mouse cursor 1362 plan.scrollPane.scrollLeft = 0; 1363 plan.scrollPane.scrollTop = 0; 1364 var mouseDeltaX = (ev.targetTouches[0].clientX + ev.targetTouches[1].clientX) / 2 - rect.left - plan.convertXModelToPixel(x); 1365 var mouseDeltaY = (ev.targetTouches[0].clientY + ev.targetTouches[1].clientY) / 2 - rect.top - plan.convertYModelToPixel(y); 1366 plan.moveView(-plan.convertPixelToLength(mouseDeltaX), -plan.convertPixelToLength(mouseDeltaY)); 1367 } 1368 } 1369 } 1370 } 1371 }, 1372 touchEnded: function(ev) { 1373 if (mouseListener.actionStartedInPlanComponent 1374 && plan.isEnabled()) { 1375 mouseListener.updateCoordinates(ev, "touchEnded"); 1376 1377 if (mouseListener.panningAfterPinch) { 1378 controller.setMode(PlanController.Mode.SELECTION); 1379 mouseListener.panningAfterPinch = false; 1380 } 1381 1382 if (ev.targetTouches.length == 0) { 1383 // Cancel autoscroll 1384 if (mouseListener.autoScroll != null) { 1385 clearInterval(mouseListener.autoScroll); 1386 mouseListener.autoScroll = null; 1387 } 1388 1389 if (mouseListener.longTouch != null) { 1390 clearTimeout(mouseListener.longTouch); 1391 mouseListener.longTouch = null; 1392 plan.stopLongTouchAnimation(); 1393 } 1394 1395 var xModel = plan.convertXPixelToModel(mouseListener.lastPointerLocation [0]); 1396 var yModel = plan.convertYPixelToModel(mouseListener.lastPointerLocation [1]); 1397 controller.releaseMouse(xModel, yModel); 1398 if (controller.getMode() !== PlanController.Mode.SELECTION) { 1399 if (mouseListener.isLongTouch(true) 1400 && mouseListener.longTouchWhenDragged) { 1401 // Emulate double click 1402 controller.pressMouse(xModel, yModel, 1, false, false, false, false, View.PointerType.TOUCH); 1403 controller.releaseMouse(xModel, yModel); 1404 controller.pressMouse(xModel, yModel, 2, false, false, false, false, View.PointerType.TOUCH); 1405 controller.releaseMouse(xModel, yModel); 1406 } else if (mouseListener.isLongTouch() 1407 && mouseListener.initialPointerLocation != null) { 1408 // Emulate double click 1409 controller.pressMouse(xModel, yModel, 2, false, false, false, false, View.PointerType.TOUCH); 1410 controller.releaseMouse(xModel, yModel); 1411 } 1412 } 1413 1414 if (mouseListener.isLongTouch()) { 1415 // Avoid firing contextmenu event 1416 ev.preventDefault(); 1417 } 1418 plan.stopIndicatorAnimation(); 1419 mouseListener.actionStartedInPlanComponent = false; 1420 } else if (ev.targetTouches.length == 1) { 1421 if (controller.getMode() === PlanController.Mode.SELECTION) { 1422 controller.setMode(PlanController.Mode.PANNING); 1423 controller.pressMouse(plan.convertXPixelToModel(ev.canvasX), 1424 plan.convertYPixelToModel(ev.canvasY), 1, false, false, false, false, View.PointerType.TOUCH); 1425 mouseListener.panningAfterPinch = true; 1426 } 1427 } else if (ev.targetTouches.length == 2 1428 && mouseListener.distanceLastPinch != null) { 1429 // If the user keeps 2 finger on screen after releasing other fingers 1430 mouseListener.distanceLastPinch = mouseListener.distance(ev.targetTouches[0].clientX, ev.targetTouches[0].clientY, 1431 ev.targetTouches[1].clientX, ev.targetTouches[1].clientY) 1432 } 1433 1434 plan.lastTouchEndX = plan.lastTouchX; 1435 plan.lastTouchEndY = plan.lastTouchY; 1436 plan.lastTouchX = undefined; 1437 plan.lastTouchY = undefined; 1438 } 1439 // Reset mouseListener.touchEventType in windowMouseReleased call 1440 }, 1441 copyPointerToTargetTouches : function(ev, touchEnded) { 1442 // Copy the IE and Edge pointer location to ev.targetTouches or ev.changedTouches 1443 if (touchEnded) { 1444 ev.changedTouches = [mouseListener.pointerTouches [ev.pointerId]]; 1445 delete mouseListener.pointerTouches [ev.pointerId]; 1446 } else { 1447 mouseListener.pointerTouches [ev.pointerId] = {clientX : ev.clientX, clientY : ev.clientY}; 1448 } 1449 ev.targetTouches = []; 1450 for (var attribute in mouseListener.pointerTouches) { 1451 if (mouseListener.pointerTouches.hasOwnProperty(attribute)) { 1452 ev.targetTouches.push(mouseListener.pointerTouches [attribute]); 1453 } 1454 } 1455 }, 1456 updateCoordinates : function(ev, type) { 1457 // Updates canvasX and canvasY properties and return true if they changed 1458 plan.lastTouchX = undefined; 1459 plan.lastTouchY = undefined; 1460 var rect = plan.canvas.getBoundingClientRect(); 1461 var updated = true; 1462 if (type.indexOf("touch") === 0) { 1463 plan.pointerType = View.PointerType.TOUCH; 1464 var minDistance = mouseListener.lastEventType == "touchStarted" 1465 ? 5 : 1.5; 1466 var touches; 1467 if (ev.targetTouches.length === 1 1468 && type == "touchMoved" 1469 && mouseListener.distance(mouseListener.lastTargetTouches [0].clientX, mouseListener.lastTargetTouches [0].clientY, 1470 ev.targetTouches[0].clientX, ev.targetTouches[0].clientY) < minDistance 1471 || ev.targetTouches.length === 0 1472 && type == "touchEnded" 1473 && mouseListener.distance(mouseListener.lastTargetTouches [0].clientX, mouseListener.lastTargetTouches [0].clientY, 1474 ev.changedTouches[0].clientX, ev.changedTouches[0].clientY) < minDistance) { 1475 touches = mouseListener.lastTargetTouches; 1476 updated = false; 1477 } else { 1478 if (ev.targetTouches.length == 0) { 1479 // touchend case 1480 touches = ev.changedTouches; 1481 } else { 1482 touches = ev.targetTouches; 1483 } 1484 mouseListener.lastEventType = type; 1485 } 1486 1487 if (touches.length == 1) { 1488 ev.canvasX = touches[0].clientX - rect.left; 1489 ev.canvasY = touches[0].clientY - rect.top; 1490 plan.lastTouchX = ev.canvasX; 1491 plan.lastTouchY = ev.canvasY; 1492 } 1493 ev.clickCount = 1; 1494 1495 if (updated) { 1496 // Make a copy of touches because old iOS reuse the same ev.targetTouches array between events 1497 mouseListener.lastTargetTouches = []; 1498 for (var i = 0; touches[i] !== undefined; i++) { 1499 mouseListener.lastTargetTouches.push({clientX: touches[i].clientX, clientY: touches[i].clientY}); 1500 } 1501 } 1502 } else { 1503 plan.pointerType = View.PointerType.MOUSE; 1504 ev.canvasX = ev.clientX - rect.left; 1505 ev.canvasY = ev.clientY - rect.top; 1506 } 1507 1508 if (ev.clickCount === undefined) { 1509 if (type == "mouseDoubleClicked") { 1510 ev.clickCount = 2; 1511 } else if (type == "mousePressed" || type == "mouseReleased") { 1512 ev.clickCount = 1; 1513 } else { 1514 ev.clickCount = 0; 1515 } 1516 } 1517 if (type == "mouseWheelMoved") { 1518 ev.wheelRotation = (ev.deltaY !== undefined 1519 ? ev.deltaX + ev.deltaY 1520 : -ev.wheelDelta) / 4; 1521 } 1522 1523 return updated; 1524 }, 1525 isLongTouch: function(dragging) { 1526 return Date.now() - mouseListener.longTouchStartTime 1527 > ((dragging 1528 ? PlanComponent.LONG_TOUCH_DELAY_WHEN_DRAGGING 1529 : PlanComponent.LONG_TOUCH_DELAY) + PlanComponent.LONG_TOUCH_DURATION_AFTER_DELAY); 1530 }, 1531 distance: function(x1, y1, x2, y2) { 1532 return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); 1533 }, 1534 isInCanvas: function(ev) { 1535 return ev.canvasX >= 0 && ev.canvasX < plan.canvas.clientWidth 1536 && ev.canvasY >= 0 && ev.canvasY < plan.canvas.clientHeight; 1537 }, 1538 mouseWheelMoved: function(ev) { 1539 ev.preventDefault(); 1540 mouseListener.updateCoordinates(ev, "mouseWheelMoved"); 1541 var shortcutKeyPressed = OperatingSystem.isMacOSX() ? ev.metaKey : ev.ctrlKey; 1542 if (shortcutKeyPressed) { 1543 var x = plan.convertXPixelToModel(ev.canvasX); 1544 var y = plan.convertYPixelToModel(ev.canvasY); 1545 var oldScale = plan.getScale(); 1546 controller.zoom(ev.wheelRotation < 0 1547 ? Math.pow(1.05, -ev.wheelRotation) 1548 : Math.pow(0.95, ev.wheelRotation)); 1549 if (plan.isScrolled() 1550 && plan.getScale() !== oldScale) { 1551 // If scale changed, update viewport position to keep the same coordinates under mouse cursor 1552 plan.scrollPane.scrollLeft = 0; 1553 plan.scrollPane.scrollTop = 0; 1554 var mouseDeltaX = ev.canvasX - plan.convertXModelToPixel(x); 1555 var mouseDeltaY = ev.canvasY - plan.convertYModelToPixel(y); 1556 plan.moveView(-plan.convertPixelToLength(mouseDeltaX), -plan.convertPixelToLength(mouseDeltaY)); 1557 } 1558 } else { 1559 plan.moveView(ev.shiftKey ? plan.convertPixelToLength(ev.wheelRotation) : 0, 1560 ev.shiftKey ? 0 : plan.convertPixelToLength(ev.wheelRotation)); 1561 } 1562 } 1563 }; 1564 if (OperatingSystem.isInternetExplorerOrLegacyEdge() 1565 && window.PointerEvent) { 1566 // Multi touch support for IE and Edge 1567 this.canvas.addEventListener("pointerdown", mouseListener.pointerPressed); 1568 this.canvas.addEventListener("mousedown", mouseListener.pointerMousePressed); 1569 this.canvas.addEventListener("dblclick", mouseListener.mouseDoubleClicked); 1570 // Add pointermove and pointerup event listeners to window to capture pointer events out of the canvas 1571 window.addEventListener("pointermove", mouseListener.windowPointerMoved); 1572 window.addEventListener("pointerup", mouseListener.windowPointerReleased); 1573 } else { 1574 this.canvas.addEventListener("touchstart", mouseListener.touchStarted); 1575 this.canvas.addEventListener("touchmove", mouseListener.touchMoved); 1576 this.canvas.addEventListener("touchend", mouseListener.touchEnded); 1577 this.canvas.addEventListener("mousedown", mouseListener.mousePressed); 1578 this.canvas.addEventListener("dblclick", mouseListener.mouseDoubleClicked); 1579 window.addEventListener("mousemove", mouseListener.windowMouseMoved); 1580 window.addEventListener("mouseup", mouseListener.windowMouseReleased); 1581 } 1582 this.canvas.addEventListener("contextmenu", mouseListener.contextMenuDisplayed); 1583 this.canvas.addEventListener("mousewheel", mouseListener.mouseWheelMoved); 1584 1585 this.mouseListener = mouseListener; 1586 } 1587 1588 /** 1589 * @private 1590 */ 1591 PlanComponent.prototype.startLongTouchAnimation = function(x, y, character, animationPostTask) { 1592 this.touchOverlay.style.visibility = "visible"; 1593 if (character == '⇪') { 1594 document.getElementById("plan-touch-overlay-timer-content").innerHTML = "<span style='font-weight: bold; font-family: sans-serif; font-size: 140%; line-height: 90%'>⇪</span>"; 1595 } else { 1596 document.getElementById("plan-touch-overlay-timer-content").innerHTML = "<span style='font-weight: bold; font-family: sans-serif'>" + character + "</span>"; 1597 } 1598 this.touchOverlay.style.left = (this.canvas.getBoundingClientRect().left + x - this.touchOverlay.clientWidth / 2) + "px"; 1599 this.touchOverlay.style.top = (this.canvas.getBoundingClientRect().top + y - this.touchOverlay.clientHeight - 40) + "px"; 1600 if (this.tooltip.style.visibility == "visible" 1601 && this.tooltip.getBoundingClientRect().top < this.canvas.getBoundingClientRect().top + y) { 1602 this.tooltip.style.marginTop = -(this.tooltip.clientHeight + 70) + "px"; 1603 } 1604 for (var i = 0; i < this.touchOverlay.children.length; i++) { 1605 this.touchOverlay.children.item(i).classList.remove("indicator"); 1606 this.touchOverlay.children.item(i).classList.add("animated"); 1607 } 1608 if (animationPostTask !== undefined) { 1609 this.longTouchAnimationPostTask = setTimeout(animationPostTask, PlanComponent.LONG_TOUCH_DURATION_AFTER_DELAY); 1610 } 1611 } 1612 1613 /** 1614 * @private 1615 */ 1616 PlanComponent.prototype.startIndicatorAnimation = function(x, y, indicator) { 1617 if (indicator == "default" || indicator == "selection") { 1618 this.touchOverlay.style.visibility = "hidden"; 1619 } else { 1620 this.touchOverlay.style.visibility = "visible"; 1621 document.getElementById("plan-touch-overlay-timer-content").innerHTML = '<img src="' + ZIPTools.getScriptFolder() + 'resources/cursors/' + indicator + '32x32.png"/>'; 1622 this.touchOverlay.style.left = (this.canvas.getBoundingClientRect().left + x - this.touchOverlay.clientWidth / 2) + "px"; 1623 this.touchOverlay.style.top = (this.canvas.getBoundingClientRect().top + y - this.touchOverlay.clientHeight - 40) + "px"; 1624 if (this.tooltip.style.visibility == "visible" 1625 && this.tooltip.getBoundingClientRect().top < this.canvas.getBoundingClientRect().top + y) { 1626 this.tooltip.style.marginTop = -(this.tooltip.clientHeight + 70) + "px"; 1627 } 1628 for (var i = 0; i < this.touchOverlay.children.length; i++) { 1629 this.touchOverlay.children.item(i).classList.remove("animated"); 1630 this.touchOverlay.children.item(i).classList.add("indicator"); 1631 } 1632 } 1633 } 1634 1635 /** 1636 * @private 1637 */ 1638 PlanComponent.prototype.stopLongTouchAnimation = function(x, y) { 1639 this.touchOverlay.style.visibility = "hidden"; 1640 for (var i = 0; i < this.touchOverlay.children.length; i++) { 1641 this.touchOverlay.children.item(i).classList.remove("animated"); 1642 this.touchOverlay.children.item(i).classList.remove("indicator"); 1643 } 1644 if (this.longTouchAnimationPostTask !== undefined) { 1645 clearTimeout(this.longTouchAnimationPostTask); 1646 delete this.longTouchAnimationPostTask; 1647 } 1648 } 1649 1650 /** 1651 * @private 1652 */ 1653 PlanComponent.prototype.stopIndicatorAnimation = function() { 1654 this.touchOverlay.style.visibility = "hidden"; 1655 for (var i = 0; i < this.touchOverlay.children.length; i++) { 1656 this.touchOverlay.children.item(i).classList.remove("animated"); 1657 this.touchOverlay.children.item(i).classList.remove("indicator"); 1658 } 1659 } 1660 1661 /** 1662 * Adds focus listener to this component that calls back <code>controller</code> 1663 * escape method on focus lost event. 1664 * @param {PlanController} controller 1665 * @private 1666 */ 1667 PlanComponent.prototype.addFocusListener = function(controller) { 1668 var plan = this; 1669 this.focusOutListener = function() { 1670 if (plan.pointerType === View.PointerType.TOUCH 1671 && plan.lastTouchEndX 1672 && plan.lastTouchEndY 1673 && controller.isModificationState() 1674 && (controller.getMode() === PlanController.Mode.WALL_CREATION 1675 || controller.getMode() === PlanController.Mode.ROOM_CREATION 1676 || controller.getMode() === PlanController.Mode.POLYLINE_CREATION 1677 || controller.getMode() === PlanController.Mode.DIMENSION_LINE_CREATION)) { 1678 // Emulate a mouse click at last touch location to validate last entered point 1679 controller.pressMouse(plan.convertXPixelToModel(plan.lastTouchEndX), 1680 plan.convertYPixelToModel(plan.lastTouchEndY), 1, false, false, false, false, View.PointerType.TOUCH); 1681 controller.releaseMouse(plan.convertXPixelToModel(plan.lastTouchEndX), 1682 plan.convertYPixelToModel(plan.lastTouchEndY)); 1683 } 1684 plan.mouseListener.lastPointerLocation = null; 1685 plan.mouseListener.actionStartedInPlanComponent = false; 1686 controller.escape(); 1687 }; 1688 this.container.addEventListener("focusout", this.focusOutListener); 1689 } 1690 1691 /** 1692 * Adds a listener to the controller to follow changes in base plan modification state. 1693 * @param {PlanController} controller 1694 * @private 1695 */ 1696 PlanComponent.prototype.addControllerListener = function(controller) { 1697 var plan = this; 1698 controller.addPropertyChangeListener("BASE_PLAN_MODIFICATION_STATE", 1699 function(ev) { 1700 var wallsDoorsOrWindowsModification = controller.isBasePlanModificationState(); 1701 if (wallsDoorsOrWindowsModification) { 1702 if (controller.getMode() !== PlanController.Mode.WALL_CREATION) { 1703 var items = plan.draggedItemsFeedback != null ? plan.draggedItemsFeedback : plan.home.getSelectedItems(); 1704 for (var i = 0; i < items.length; i++) { 1705 var item = items[i]; 1706 if (!(item instanceof Wall) 1707 && !((item instanceof HomePieceOfFurniture) && item.isDoorOrWindow())) { 1708 wallsDoorsOrWindowsModification = false; 1709 } 1710 } 1711 } 1712 } 1713 if (plan.wallsDoorsOrWindowsModification !== wallsDoorsOrWindowsModification) { 1714 plan.wallsDoorsOrWindowsModification = wallsDoorsOrWindowsModification; 1715 plan.repaint(); 1716 } 1717 }); 1718 } 1719 1720 /** 1721 * Installs default keys bound to actions. 1722 * @private 1723 */ 1724 PlanComponent.prototype.installDefaultKeyboardActions = function() { 1725 var plan = this; 1726 this.inputMap = { 1727 "pressed DELETE": "DELETE_SELECTION", 1728 "pressed BACK_SPACE": "DELETE_SELECTION", 1729 "pressed ESCAPE": "ESCAPE", 1730 "shift pressed ESCAPE": "ESCAPE", 1731 "pressed LEFT": "MOVE_SELECTION_LEFT", 1732 "shift pressed LEFT": "MOVE_SELECTION_FAST_LEFT", 1733 "pressed UP": "MOVE_SELECTION_UP", 1734 "shift pressed UP": "MOVE_SELECTION_FAST_UP", 1735 "pressed DOWN": "MOVE_SELECTION_DOWN", 1736 "shift pressed DOWN": "MOVE_SELECTION_FAST_DOWN", 1737 "pressed RIGHT": "MOVE_SELECTION_RIGHT", 1738 "shift pressed RIGHT": "MOVE_SELECTION_FAST_RIGHT", 1739 "pressed ENTER": "ACTIVATE_EDITIION", 1740 "shift pressed ENTER": "ACTIVATE_EDITIION" 1741 }; 1742 if (OperatingSystem.isMacOSX()) { 1743 CoreTools.merge(this.inputMap, { 1744 "alt pressed ALT": "ACTIVATE_DUPLICATION", 1745 "released ALT": "DEACTIVATE_DUPLICATION", 1746 "shift alt pressed ALT": "ACTIVATE_DUPLICATION", 1747 "shift released ALT": "DEACTIVATE_DUPLICATION", 1748 "meta alt pressed ALT": "ACTIVATE_DUPLICATION", 1749 "meta released ALT": "DEACTIVATE_DUPLICATION", 1750 "shift meta alt pressed ALT": "ACTIVATE_DUPLICATION", 1751 "shift meta released ALT": "DEACTIVATE_DUPLICATION", 1752 "alt pressed ESCAPE": "ESCAPE", 1753 "alt pressed ENTER": "ACTIVATE_EDITIION" 1754 }); 1755 } 1756 else { 1757 CoreTools.merge(this.inputMap, { 1758 "control pressed CONTROL": "ACTIVATE_DUPLICATION", 1759 "released CONTROL": "DEACTIVATE_DUPLICATION", 1760 "shift control pressed CONTROL": "ACTIVATE_DUPLICATION", 1761 "shift released CONTROL": "DEACTIVATE_DUPLICATION", 1762 "meta control pressed CONTROL": "ACTIVATE_DUPLICATION", 1763 "meta released CONTROL": "DEACTIVATE_DUPLICATION", 1764 "shift meta control pressed CONTROL": "ACTIVATE_DUPLICATION", 1765 "shift meta released CONTROL": "DEACTIVATE_DUPLICATION", 1766 "control pressed ESCAPE": "ESCAPE", 1767 "control pressed ENTER": "ACTIVATE_EDITIION" 1768 }); 1769 } 1770 if (OperatingSystem.isWindows()) { 1771 CoreTools.merge(this.inputMap, { 1772 "alt pressed ALT": "TOGGLE_MAGNETISM_ON", 1773 "released ALT": "TOGGLE_MAGNETISM_OFF", 1774 "shift alt pressed ALT": "TOGGLE_MAGNETISM_ON", 1775 "shift released ALT": "TOGGLE_MAGNETISM_OFF", 1776 "control alt pressed ALT": "TOGGLE_MAGNETISM_ON", 1777 "control released ALT": "TOGGLE_MAGNETISM_OFF", 1778 "shift control alt pressed ALT": "TOGGLE_MAGNETISM_ON", 1779 "shift control released ALT": "TOGGLE_MAGNETISM_OFF", 1780 "alt pressed ESCAPE": "ESCAPE", 1781 "alt pressed ENTER": "ACTIVATE_EDITIION" 1782 }); 1783 } 1784 else if (OperatingSystem.isMacOSX()) { 1785 CoreTools.merge(this.inputMap, { 1786 "meta pressed META": "TOGGLE_MAGNETISM_ON", 1787 "released META": "TOGGLE_MAGNETISM_OFF", 1788 "shift meta pressed META": "TOGGLE_MAGNETISM_ON", 1789 "shift released META": "TOGGLE_MAGNETISM_OFF", 1790 "alt meta pressed META": "TOGGLE_MAGNETISM_ON", 1791 "alt released META": "TOGGLE_MAGNETISM_OFF", 1792 "shift alt meta pressed META": "TOGGLE_MAGNETISM_ON", 1793 "shift alt released META": "TOGGLE_MAGNETISM_OFF", 1794 "meta pressed ESCAPE": "ESCAPE", 1795 "meta pressed ENTER": "ACTIVATE_EDITIION" 1796 }); 1797 } 1798 else { 1799 CoreTools.merge(this.inputMap, { 1800 "shift alt pressed ALT": "TOGGLE_MAGNETISM_ON", 1801 "alt shift pressed SHIFT": "TOGGLE_MAGNETISM_ON", 1802 "alt released SHIFT": "TOGGLE_MAGNETISM_OFF", 1803 "shift released ALT": "TOGGLE_MAGNETISM_OFF", 1804 "control shift alt pressed ALT": "TOGGLE_MAGNETISM_ON", 1805 "control alt shift pressed SHIFT": "TOGGLE_MAGNETISM_ON", 1806 "control alt released SHIFT": "TOGGLE_MAGNETISM_OFF", 1807 "control shift released ALT": "TOGGLE_MAGNETISM_OFF", 1808 "alt shift pressed ESCAPE": "ESCAPE", 1809 "alt shift pressed ENTER": "ACTIVATE_EDITIION", 1810 "control alt shift pressed ESCAPE": "ESCAPE", 1811 "control alt shift pressed ENTER": "ACTIVATE_EDITIION" 1812 }); 1813 } 1814 CoreTools.merge(this.inputMap, { 1815 "shift pressed SHIFT": "ACTIVATE_ALIGNMENT", 1816 "released SHIFT": "DEACTIVATE_ALIGNMENT" 1817 }); 1818 if (OperatingSystem.isWindows()) { 1819 CoreTools.merge(this.inputMap, { 1820 "control shift pressed SHIFT": "ACTIVATE_ALIGNMENT", 1821 "control released SHIFT": "DEACTIVATE_ALIGNMENT", 1822 "alt shift pressed SHIFT": "ACTIVATE_ALIGNMENT", 1823 "alt released SHIFT": "DEACTIVATE_ALIGNMENT" 1824 }); 1825 } 1826 else if (OperatingSystem.isMacOSX()) { 1827 CoreTools.merge(this.inputMap, { 1828 "alt shift pressed SHIFT": "ACTIVATE_ALIGNMENT", 1829 "alt released SHIFT": "DEACTIVATE_ALIGNMENT", 1830 "meta shift pressed SHIFT": "ACTIVATE_ALIGNMENT", 1831 "meta released SHIFT": "DEACTIVATE_ALIGNMENT" 1832 }); 1833 } 1834 else { 1835 CoreTools.merge(this.inputMap, { 1836 "control shift pressed SHIFT": "ACTIVATE_ALIGNMENT", 1837 "control released SHIFT": "DEACTIVATE_ALIGNMENT", 1838 "shift released ALT": "ACTIVATE_ALIGNMENT", 1839 "control shift released ALT": "ACTIVATE_ALIGNMENT" 1840 }); 1841 } 1842 this.keyDownListener = function(ev) { 1843 return plan.callAction(ev, "keydown"); 1844 }; 1845 this.container.addEventListener("keydown", this.keyDownListener, false); 1846 this.keyUpListener = function(ev) { 1847 return plan.callAction(ev, "keyup"); 1848 }; 1849 this.container.addEventListener("keyup", this.keyUpListener, false); 1850 } 1851 1852 /** 1853 * Runs the action bound to the key event in parameter. 1854 * @private 1855 */ 1856 PlanComponent.prototype.callAction = function(ev, keyType) { 1857 var keyStroke = KeyStroke.getKeyStrokeForEvent(ev, keyType); 1858 if (keyStroke !== undefined) { 1859 var actionKey = this.inputMap[keyStroke]; 1860 if (actionKey !== undefined) { 1861 var action = this.actionMap[actionKey]; 1862 if (action !== undefined) { 1863 action.actionPerformed(ev); 1864 } 1865 ev.stopPropagation(); 1866 } 1867 } 1868 } 1869 1870 /** 1871 * Installs keys bound to actions during edition. 1872 * @private 1873 */ 1874 PlanComponent.prototype.installEditionKeyboardActions = function() { 1875 this.inputMap = { 1876 "ESCAPE": "ESCAPE", 1877 "shift ESCAPE": "ESCAPE", 1878 "ENTER": "DEACTIVATE_EDITIION", 1879 "shift ENTER": "DEACTIVATE_EDITIION" 1880 }; 1881 if (OperatingSystem.isMacOSX()) { 1882 CoreTools.merge(this.inputMap, { 1883 "alt ESCAPE": "ESCAPE", 1884 "alt ENTER": "DEACTIVATE_EDITIION", 1885 "alt shift ENTER": "DEACTIVATE_EDITIION", 1886 "alt pressed ALT": "ACTIVATE_DUPLICATION", 1887 "released ALT": "DEACTIVATE_DUPLICATION", 1888 "shift alt pressed ALT": "ACTIVATE_DUPLICATION", 1889 "shift released ALT": "DEACTIVATE_DUPLICATION" 1890 }); 1891 } 1892 else { 1893 CoreTools.merge(this.inputMap, { 1894 "control ESCAPE": "ESCAPE", 1895 "control ENTER": "DEACTIVATE_EDITIION", 1896 "control shift ENTER": "DEACTIVATE_EDITIION", 1897 "control pressed CONTROL": "ACTIVATE_DUPLICATION", 1898 "released CONTROL": "DEACTIVATE_DUPLICATION", 1899 "shift control pressed CONTROL": "ACTIVATE_DUPLICATION", 1900 "shift released CONTROL": "DEACTIVATE_DUPLICATION" 1901 }); 1902 } 1903 } 1904 1905 /** 1906 * Creates actions that calls back <code>controller</code> methods. 1907 * @param {PlanController} controller 1908 * @private 1909 */ 1910 PlanComponent.prototype.createActions = function(controller) { 1911 var plan = this; 1912 1913 function MoveSelectionAction(dx, dy) { 1914 this.dx = dx; 1915 this.dy = dy; 1916 } 1917 MoveSelectionAction.prototype.actionPerformed = function(ev) { 1918 controller.moveSelection(this.dx / plan.getScale(), this.dy / plan.getScale()); 1919 }; 1920 1921 function ToggleMagnetismAction(toggle) { 1922 this.toggle = toggle; 1923 } 1924 ToggleMagnetismAction.prototype.actionPerformed = function(ev) { 1925 controller.toggleMagnetism(this.toggle); 1926 }; 1927 1928 function SetAlignmentActivatedAction(alignmentActivated) { 1929 this.alignmentActivated = alignmentActivated; 1930 } 1931 SetAlignmentActivatedAction.prototype.actionPerformed = function(ev) { 1932 controller.setAlignmentActivated(this.alignmentActivated); 1933 }; 1934 1935 function SetDuplicationActivatedAction(duplicationActivated) { 1936 this.duplicationActivated = duplicationActivated; 1937 } 1938 SetDuplicationActivatedAction.prototype.actionPerformed = function(ev) { 1939 controller.setDuplicationActivated(this.duplicationActivated); 1940 }; 1941 1942 function SetEditionActivatedAction(editionActivated) { 1943 this.editionActivated = editionActivated; 1944 } 1945 SetEditionActivatedAction.prototype.actionPerformed = function(ev) { 1946 controller.setEditionActivated(this.editionActivated); 1947 }; 1948 1949 this.actionMap = { 1950 "DELETE_SELECTION": { 1951 actionPerformed: function() { 1952 controller.deleteSelection(); 1953 } 1954 }, 1955 "ESCAPE": { 1956 actionPerformed: function() { 1957 controller.escape(); 1958 } 1959 }, 1960 "MOVE_SELECTION_LEFT": new MoveSelectionAction(-1, 0), 1961 "MOVE_SELECTION_FAST_LEFT": new MoveSelectionAction(-10, 0), 1962 "MOVE_SELECTION_UP": new MoveSelectionAction(0, -1), 1963 "MOVE_SELECTION_FAST_UP": new MoveSelectionAction(0, -10), 1964 "MOVE_SELECTION_DOWN": new MoveSelectionAction(0, 1), 1965 "MOVE_SELECTION_FAST_DOWN": new MoveSelectionAction(0, 10), 1966 "MOVE_SELECTION_RIGHT": new MoveSelectionAction(1, 0), 1967 "MOVE_SELECTION_FAST_RIGHT": new MoveSelectionAction(10, 0), 1968 "TOGGLE_MAGNETISM_ON": new ToggleMagnetismAction(true), 1969 "TOGGLE_MAGNETISM_OFF": new ToggleMagnetismAction(false), 1970 "ACTIVATE_ALIGNMENT": new SetAlignmentActivatedAction(true), 1971 "DEACTIVATE_ALIGNMENT": new SetAlignmentActivatedAction(false), 1972 "ACTIVATE_DUPLICATION": new SetDuplicationActivatedAction(true), 1973 "DEACTIVATE_DUPLICATION": new SetDuplicationActivatedAction(false), 1974 "ACTIVATE_EDITIION": new SetEditionActivatedAction(true), 1975 "DEACTIVATE_EDITIION": new SetEditionActivatedAction(false) 1976 }; 1977 } 1978 1979 PlanComponent.createCustomCursor = function(name, defaultCursor) { 1980 if (OperatingSystem.isInternetExplorer()) { 1981 return defaultCursor; 1982 } else { 1983 return 'url("' + ZIPTools.getScriptFolder() + '/resources/cursors/' 1984 + name + '16x16' + (OperatingSystem.isMacOSX() ? '-macosx' : '') + '.png") 8 8, ' + defaultCursor; 1985 } 1986 } 1987 1988 /** 1989 * Returns the preferred size of this component in actual screen pixels size. 1990 * @return {java.awt.Dimension} 1991 */ 1992 PlanComponent.prototype.getPreferredSize = function() { 1993 var insets = this.getInsets(); 1994 var planBounds = this.getPlanBounds(); 1995 return {width: this.convertLengthToPixel(planBounds.getWidth() + PlanComponent.MARGIN * 2) + insets.left + insets.right, 1996 height: this.convertLengthToPixel(planBounds.getHeight() + PlanComponent.MARGIN * 2) + insets.top + insets.bottom}; 1997 } 1998 1999 /** 2000 * @private 2001 */ 2002 PlanComponent.prototype.getInsets = function() { 2003 return { top: 0, bottom: 0, left: 0, right: 0 }; 2004 } 2005 2006 /** 2007 * @private 2008 */ 2009 PlanComponent.prototype.getWidth = function() { 2010 return this.view.clientWidth; 2011 } 2012 2013 /** 2014 * @private 2015 */ 2016 PlanComponent.prototype.getHeight = function() { 2017 return this.view.clientHeight; 2018 } 2019 2020 /** 2021 * Returns the bounds of the plan displayed by this component. 2022 * @return {java.awt.geom.Rectangle2D} 2023 * @private 2024 */ 2025 PlanComponent.prototype.getPlanBounds = function() { 2026 var plan = this; 2027 if (!this.planBoundsCacheValid) { 2028 if (this.planBoundsCache == null) { 2029 this.planBoundsCache = new java.awt.geom.Rectangle2D.Float(0, 0, 1000, 1000); 2030 } 2031 if (this.backgroundImageCache != null) { 2032 var backgroundImage = this.home.getBackgroundImage(); 2033 if (backgroundImage != null) { 2034 this.planBoundsCache.add(-backgroundImage.getXOrigin(), -backgroundImage.getYOrigin()); 2035 this.planBoundsCache.add(this.backgroundImageCache.width * backgroundImage.getScale() - backgroundImage.getXOrigin(), 2036 this.backgroundImageCache.height * backgroundImage.getScale() - backgroundImage.getYOrigin()); 2037 } 2038 this.home.getLevels().forEach(function(level) { 2039 var levelBackgroundImage = level.getBackgroundImage(); 2040 if (levelBackgroundImage != null) { 2041 plan.planBoundsCache.add(-levelBackgroundImage.getXOrigin(), -levelBackgroundImage.getYOrigin()); 2042 plan.planBoundsCache.add(plan.backgroundImageCache.width * levelBackgroundImage.getScale() - levelBackgroundImage.getXOrigin(), 2043 plan.backgroundImageCache.height * levelBackgroundImage.getScale() - levelBackgroundImage.getYOrigin()); 2044 } 2045 }); 2046 } 2047 var g = this.getGraphics(); 2048 if (g != null) { 2049 this.setRenderingHints(g); 2050 } 2051 var homeItemsBounds = this.getItemsBounds(g, this.getPaintedItems()); 2052 if (homeItemsBounds != null) { 2053 this.planBoundsCache.add(homeItemsBounds); 2054 } 2055 this.home.getObserverCamera().getPoints().forEach(function(point) { 2056 return plan.planBoundsCache.add(point[0], point[1]); 2057 }); 2058 this.planBoundsCacheValid = true; 2059 } 2060 return this.planBoundsCache; 2061 } 2062 2063 /** 2064 * Returns the collection of walls, furniture, rooms and dimension lines of the home 2065 * painted by this component wherever the level they belong to is selected or not. 2066 * @return {Object[]} 2067 */ 2068 PlanComponent.prototype.getPaintedItems = function() { 2069 return this.home.getSelectableViewableItems(); 2070 } 2071 2072 /** 2073 * Returns the bounds of the given collection of <code>items</code>. 2074 * @param {Graphics2D} g 2075 * @param {Bound[]} items 2076 * @return {java.awt.geom.Rectangle2D} 2077 * @private 2078 */ 2079 PlanComponent.prototype.getItemsBounds = function(g, items) { 2080 var itemsBounds = null; 2081 for (var i = 0; i < items.length; i++) { 2082 var item = items[i]; 2083 var itemBounds = this.getItemBounds(g, item); 2084 if (itemsBounds == null) { 2085 itemsBounds = itemBounds; 2086 } else { 2087 itemsBounds.add(itemBounds); 2088 } 2089 } 2090 return itemsBounds; 2091 } 2092 2093 /** 2094 * Returns the bounds of the given <code>item</code>. 2095 * @param {Graphics2D} g 2096 * @param {Object} item 2097 * @return {java.awt.geom.Rectangle2D} 2098 */ 2099 PlanComponent.prototype.getItemBounds = function(g, item) { 2100 var plan = this; 2101 var points = item.getPoints(); 2102 var itemBounds = new java.awt.geom.Rectangle2D.Float(points[0][0], points[0][1], 0, 0); 2103 for (var i = 1; i < points.length; i++) { 2104 itemBounds.add(points[i][0], points[i][1]); 2105 } 2106 var componentFont; 2107 if (g != null) { 2108 componentFont = g.getFont(); 2109 } else { 2110 componentFont = this.getFont(); 2111 } 2112 if (item instanceof Room) { 2113 var room = item; 2114 var xRoomCenter = room.getXCenter(); 2115 var yRoomCenter = room.getYCenter(); 2116 var roomName = room.getName(); 2117 if (roomName != null && roomName.length > 0) { 2118 this.addTextBounds(room.constructor, 2119 roomName, room.getNameStyle(), 2120 xRoomCenter + room.getNameXOffset(), 2121 yRoomCenter + room.getNameYOffset(), room.getNameAngle(), itemBounds); 2122 } 2123 if (room.isAreaVisible()) { 2124 var area = room.getArea(); 2125 if (area > 0.01) { 2126 var areaText = this.preferences.getLengthUnit().getAreaFormatWithUnit().format(area); 2127 this.addTextBounds(room.constructor, 2128 areaText, room.getAreaStyle(), 2129 xRoomCenter + room.getAreaXOffset(), 2130 yRoomCenter + room.getAreaYOffset(), room.getAreaAngle(), itemBounds); 2131 } 2132 } 2133 } else if (item instanceof Polyline) { 2134 var polyline = item; 2135 return ShapeTools.getPolylineShape(polyline.getPoints(), 2136 polyline.getJoinStyle() === Polyline.JoinStyle.CURVED, polyline.isClosedPath()).getBounds2D(); 2137 } else if (item instanceof HomePieceOfFurniture) { 2138 if (item != null && item instanceof HomeDoorOrWindow) { 2139 var doorOrWindow_1 = item; 2140 doorOrWindow_1.getSashes().forEach(function(sash) { 2141 itemBounds.add(plan.getDoorOrWindowSashShape(doorOrWindow_1, sash).getBounds2D()); 2142 }); 2143 } else if (item instanceof HomeFurnitureGroup) { 2144 itemBounds.add(this.getItemsBounds(g, item.getFurniture())); 2145 } 2146 var piece = item; 2147 var pieceName = piece.getName(); 2148 if (piece.isVisible() 2149 && piece.isNameVisible() 2150 && pieceName.length > 0) { 2151 this.addTextBounds(piece.constructor, 2152 pieceName, piece.getNameStyle(), 2153 piece.getX() + piece.getNameXOffset(), 2154 piece.getY() + piece.getNameYOffset(), piece.getNameAngle(), itemBounds); 2155 } 2156 } else if (item instanceof DimensionLine) { 2157 var dimensionLine = item; 2158 var dimensionLineLength = dimensionLine.getLength(); 2159 var lengthText = this.preferences.getLengthUnit().getFormat().format(dimensionLineLength); 2160 var lengthStyle = dimensionLine.getLengthStyle(); 2161 if (lengthStyle == null) { 2162 lengthStyle = this.preferences.getDefaultTextStyle(dimensionLine.constructor); 2163 } 2164 var transform = java.awt.geom.AffineTransform.getTranslateInstance( 2165 dimensionLine.getXStart(), dimensionLine.getYStart()); 2166 var angle = dimensionLine.isElevationDimensionLine() 2167 ? (dimensionLine.getPitch() + 2 * Math.PI) % (2 * Math.PI) 2168 : Math.atan2(dimensionLine.getYEnd() - dimensionLine.getYStart(), dimensionLine.getXEnd() - dimensionLine.getXStart()); 2169 if (dimensionLine.getElevationStart() == dimensionLine.getElevationEnd()) { 2170 var lengthFontMetrics = this.getFontMetrics(componentFont, lengthStyle); 2171 var lengthTextBounds = lengthFontMetrics.getStringBounds(lengthText); 2172 transform.rotate(angle); 2173 transform.translate(0, dimensionLine.getOffset()); 2174 transform.translate((dimensionLineLength - lengthTextBounds.getWidth()) / 2, 2175 dimensionLine.getOffset() <= 0 2176 ? -lengthFontMetrics.getDescent() - 1 2177 : lengthFontMetrics.getAscent() + 1); 2178 var lengthTextBoundsPath = new java.awt.geom.GeneralPath(lengthTextBounds); 2179 for (var it = lengthTextBoundsPath.getPathIterator(transform); !it.isDone(); it.next()) { 2180 var pathPoint = [0, 0]; 2181 if (it.currentSegment(pathPoint) !== java.awt.geom.PathIterator.SEG_CLOSE) { 2182 itemBounds.add(pathPoint[0], pathPoint[1]); 2183 } 2184 } 2185 } 2186 transform.setToTranslation(dimensionLine.getXStart(), dimensionLine.getYStart()); 2187 transform.rotate(angle); 2188 transform.translate(0, dimensionLine.getOffset()); 2189 for (var it = PlanComponent.DIMENSION_LINE_MARK_END.getPathIterator(transform); !it.isDone(); it.next()) { 2190 var pathPoint = [0, 0]; 2191 if (it.currentSegment(pathPoint) !== java.awt.geom.PathIterator.SEG_CLOSE) { 2192 itemBounds.add(pathPoint[0], pathPoint[1]); 2193 } 2194 } 2195 transform.translate(dimensionLineLength, 0); 2196 for (var it = PlanComponent.DIMENSION_LINE_MARK_END.getPathIterator(transform); !it.isDone(); it.next()) { 2197 var pathPoint = [0, 0]; 2198 if (it.currentSegment(pathPoint) !== java.awt.geom.PathIterator.SEG_CLOSE) { 2199 itemBounds.add(pathPoint[0], pathPoint[1]); 2200 } 2201 } 2202 } else if (item instanceof Label) { 2203 var label = item; 2204 this.addTextBounds(label.constructor, 2205 label.getText(), label.getStyle(), label.getX(), label.getY(), label.getAngle(), itemBounds); 2206 } else if (item instanceof Compass) { 2207 var compass = item; 2208 var transform = java.awt.geom.AffineTransform.getTranslateInstance(compass.getX(), compass.getY()); 2209 transform.scale(compass.getDiameter(), compass.getDiameter()); 2210 transform.rotate(compass.getNorthDirection()); 2211 return PlanComponent.COMPASS.createTransformedShape(transform).getBounds2D(); 2212 } 2213 return itemBounds; 2214 } 2215 2216 /** 2217 * Add <code>text</code> bounds to the given rectangle <code>bounds</code>. 2218 * @param {Object} selectableClass 2219 * @param {string} text 2220 * @param {TextStyle} style 2221 * @param {number} x 2222 * @param {number} y 2223 * @param {number} angle 2224 * @param {java.awt.geom.Rectangle2D} bounds 2225 * @private 2226 */ 2227 PlanComponent.prototype.addTextBounds = function(selectableClass, text, style, x, y, angle, bounds) { 2228 if (style == null) { 2229 style = this.preferences.getDefaultTextStyle(selectableClass); 2230 } 2231 this.getTextBounds(text, style, x, y, angle).forEach(function(points) { 2232 return bounds.add(points[0], points[1]); 2233 }); 2234 } 2235 2236 /** 2237 * Returns the coordinates of the bounding rectangle of the <code>text</code> centered at 2238 * the point (<code>x</code>,<code>y</code>). 2239 * @param {string} text 2240 * @param {TextStyle} style 2241 * @param {number} x 2242 * @param {number} y 2243 * @param {number} angle 2244 * @return {Array} 2245 */ 2246 PlanComponent.prototype.getTextBounds = function(text, style, x, y, angle) { 2247 var fontMetrics = this.getFontMetrics(this.getFont(), style); 2248 var textBounds = null; 2249 var lines = text.replace(/\n*$/, "").split("\n"); 2250 var g = this.getGraphics(); 2251 if (g != null) { 2252 this.setRenderingHints(g); 2253 } 2254 for (var i = 0; i < lines.length; i++) { 2255 var lineBounds = fontMetrics.getStringBounds(lines[i]); 2256 if (textBounds == null || textBounds.getWidth() < lineBounds.getWidth()) { 2257 textBounds = lineBounds; 2258 } 2259 } 2260 var textWidth = textBounds.getWidth(); 2261 var shiftX; 2262 if (style.getAlignment() === TextStyle.Alignment.LEFT) { 2263 shiftX = 0; 2264 } 2265 else if (style.getAlignment() === TextStyle.Alignment.RIGHT) { 2266 shiftX = -textWidth; 2267 } 2268 else { 2269 shiftX = -textWidth / 2; 2270 } 2271 if (angle === 0) { 2272 var minY = (y + textBounds.getY()); 2273 var maxY = (minY + textBounds.getHeight()); 2274 minY -= (textBounds.getHeight() * (lines.length - 1)); 2275 return [ 2276 [x + shiftX, minY], 2277 [x + shiftX + textWidth, minY], 2278 [x + shiftX + textWidth, maxY], 2279 [x + shiftX, maxY]]; 2280 } else { 2281 textBounds.add(textBounds.getX(), textBounds.getY() - textBounds.getHeight() * (lines.length - 1)); 2282 var transform = new java.awt.geom.AffineTransform(); 2283 transform.translate(x, y); 2284 transform.rotate(angle); 2285 transform.translate(shiftX, 0); 2286 var textBoundsPath = new java.awt.geom.GeneralPath(textBounds); 2287 var textPoints = []; 2288 for (var it = textBoundsPath.getPathIterator(transform); !it.isDone(); it.next()) { 2289 var pathPoint = [0, 0]; 2290 if (it.currentSegment(pathPoint) !== java.awt.geom.PathIterator.SEG_CLOSE) { 2291 textPoints.push(pathPoint); 2292 } 2293 } 2294 return textPoints.slice(0); 2295 } 2296 } 2297 2298 /** 2299 * Returns the HTML font matching a given text style. 2300 * @param {string} [defaultFont] 2301 * @param {TextStyle} [textStyle] 2302 * @return {string} 2303 */ 2304 PlanComponent.prototype.getFont = function(defaultFont, textStyle) { 2305 if (defaultFont == null && textStyle == null) { 2306 return this.font; 2307 } 2308 if (this.fonts == null) { 2309 this.fonts = {}; 2310 } 2311 var font = CoreTools.getFromMap(this.fonts, textStyle); 2312 if (font == null) { 2313 var fontStyle = 'normal'; 2314 var fontWeight = 'normal'; 2315 if (textStyle.isBold()) { 2316 fontWeight = 'bold'; 2317 } 2318 if (textStyle.isItalic()) { 2319 fontStyle = 'italic'; 2320 } 2321 if (defaultFont == null 2322 || this.preferences.getDefaultFontName() != null 2323 || textStyle.getFontName() != null) { 2324 var fontName = textStyle.getFontName(); 2325 if (fontName == null) { 2326 fontName = this.preferences.getDefaultFontName(); 2327 } 2328 if (fontName == null) { 2329 fontName = new Font(this.font).family; 2330 } 2331 defaultFont = new Font({ 2332 style: fontStyle, 2333 weight: fontWeight, 2334 size: "10px", 2335 family: fontName }).toString(); 2336 } 2337 font = new Font({ 2338 style: fontStyle, 2339 weight: fontWeight, 2340 size: textStyle.getFontSize() + "px", 2341 family: new Font(defaultFont).family }).toString(); 2342 CoreTools.putToMap(this.fonts, textStyle, font); 2343 } 2344 return font; 2345 } 2346 2347 /** 2348 * Sets the default font used to paint text in this component. 2349 * @param {string} font a HTML font 2350 * @private 2351 */ 2352 PlanComponent.prototype.setFont = function(font) { 2353 this.font = font; 2354 } 2355 2356 /** 2357 * Returns the font metrics matching a given text style. 2358 * @param {string} defaultFont 2359 * @param {TextStyle} textStyle 2360 * @return {FontMetrics} 2361 */ 2362 PlanComponent.prototype.getFontMetrics = function(defaultFont, textStyle) { 2363 if (textStyle == null) { 2364 return new FontMetrics(defaultFont); 2365 } 2366 if (this.fontsMetrics == null) { 2367 this.fontsMetrics = {}; 2368 } 2369 var fontMetrics = CoreTools.getFromMap(this.fontsMetrics, textStyle); 2370 if (fontMetrics == null) { 2371 fontMetrics = this.getFontMetrics(this.getFont(defaultFont, textStyle)); 2372 CoreTools.putToMap(this.fontsMetrics, textStyle, fontMetrics); 2373 } 2374 return fontMetrics; 2375 } 2376 2377 /** 2378 * Sets whether plan's background should be painted or not. 2379 * Background may include grid and an image. 2380 * @param {boolean} backgroundPainted 2381 */ 2382 PlanComponent.prototype.setBackgroundPainted = function(backgroundPainted) { 2383 if (this.backgroundPainted !== backgroundPainted) { 2384 this.backgroundPainted = backgroundPainted; 2385 this.repaint(); 2386 } 2387 } 2388 2389 /** 2390 * Returns <code>true</code> if plan's background should be painted. 2391 * @return {boolean} 2392 */ 2393 PlanComponent.prototype.isBackgroundPainted = function() { 2394 return this.backgroundPainted; 2395 } 2396 2397 /** 2398 * Sets whether the outline of home selected items should be painted or not. 2399 * @param {boolean} selectedItemsOutlinePainted 2400 */ 2401 PlanComponent.prototype.setSelectedItemsOutlinePainted = function(selectedItemsOutlinePainted) { 2402 if (this.selectedItemsOutlinePainted !== selectedItemsOutlinePainted) { 2403 this.selectedItemsOutlinePainted = selectedItemsOutlinePainted; 2404 this.repaint(); 2405 } 2406 } 2407 2408 /** 2409 * Returns <code>true</code> if the outline of home selected items should be painted. 2410 * @return {boolean} 2411 */ 2412 PlanComponent.prototype.isSelectedItemsOutlinePainted = function() { 2413 return this.selectedItemsOutlinePainted; 2414 } 2415 2416 /** 2417 * Repaints this component elements when possible. 2418 */ 2419 PlanComponent.prototype.repaint = function() { 2420 var plan = this; 2421 if (!this.canvasNeededRepaint) { 2422 this.canvasNeededRepaint = true; 2423 requestAnimationFrame(function() { 2424 if (plan.canvasNeededRepaint) { 2425 plan.canvasNeededRepaint = false; 2426 plan.paintComponent(plan.getGraphics()); 2427 } 2428 }); 2429 } 2430 } 2431 2432 /** 2433 * Returns a <code>Graphics2D</code> object. 2434 * @return {Graphics2D} 2435 */ 2436 PlanComponent.prototype.getGraphics = function() { 2437 if (!this.graphics) { 2438 this.graphics = new Graphics2D(this.canvas); 2439 } 2440 return this.graphics; 2441 } 2442 2443 /** 2444 * Paints this component. 2445 * @param {Graphics2D} g 2446 */ 2447 PlanComponent.prototype.paintComponent = function(g2D) { 2448 g2D.setTransform(new java.awt.geom.AffineTransform()); 2449 g2D.clear(); 2450 if (this.backgroundPainted) { 2451 this.paintBackground(g2D, this.getBackgroundColor(PlanComponent.PaintMode.PAINT)); 2452 } 2453 var insets = this.getInsets(); 2454 // g2D.clipRect(0, 0, this.getWidth(), this.getHeight()); 2455 var planBounds = this.getPlanBounds(); 2456 var planScale = this.getScale() * this.resolutionScale; 2457 if (this.isScrolled()) { 2458 g2D.translate(-this.scrollPane.scrollLeft * this.resolutionScale, -this.scrollPane.scrollTop * this.resolutionScale); 2459 } 2460 g2D.translate(insets.left * this.resolutionScale + (PlanComponent.MARGIN - planBounds.getMinX()) * planScale, 2461 insets.top * this.resolutionScale + (PlanComponent.MARGIN - planBounds.getMinY()) * planScale); 2462 g2D.scale(planScale, planScale); 2463 this.setRenderingHints(g2D); 2464 // For debugging only 2465 if (this.drawPlanBounds) { 2466 g2D.setColor("#FF0000"); 2467 g2D.draw(planBounds); 2468 } 2469 this.paintContent(g2D, this.home.getSelectedLevel(), this.getScale(), PlanComponent.PaintMode.PAINT); 2470 g2D.dispose(); 2471 } 2472 2473 /** 2474 * Returns the print preferred scale of the plan drawn in this component 2475 * to make it fill <code>pageFormat</code> imageable size. 2476 * @param {Graphics2D} g 2477 * @param {java.awt.print.PageFormat} pageFormat 2478 * @return {number} 2479 */ 2480 PlanComponent.prototype.getPrintPreferredScale = function(g, pageFormat) { 2481 return 1; 2482 } 2483 2484 /** 2485 * Returns the stroke width used to paint an item of the given class. 2486 * @param {Object} itemClass 2487 * @param {PlanComponent.PaintMode} paintMode 2488 * @return {number} 2489 * @private 2490 */ 2491 PlanComponent.prototype.getStrokeWidth = function(itemClass, paintMode) { 2492 var strokeWidth; 2493 if (Wall === itemClass || Room === itemClass) { 2494 strokeWidth = PlanComponent.WALL_STROKE_WIDTH; 2495 } else { 2496 strokeWidth = PlanComponent.BORDER_STROKE_WIDTH; 2497 } 2498 if (paintMode === PlanComponent.PaintMode.PRINT) { 2499 strokeWidth *= 0.5; 2500 } 2501 return strokeWidth; 2502 } 2503 2504 /** 2505 * Returns an image of selected items in plan for transfer purpose. 2506 * @param {TransferableView.DataType} dataType 2507 * @return {Object} 2508 */ 2509 PlanComponent.prototype.createTransferData = function(dataType) { 2510 if (dataType === TransferableView.DataType.PLAN_IMAGE) { 2511 return this.getClipboardImage(); 2512 } else { 2513 return null; 2514 } 2515 } 2516 2517 /** 2518 * Returns an image of the selected items displayed by this component 2519 * (camera excepted) with no outline at scale 1/1 (1 pixel = 1cm). 2520 * @return {HTMLImageElement} 2521 */ 2522 PlanComponent.prototype.getClipboardImage = function() { 2523 throw new UnsupportedOperationException("Not implemented"); 2524 } 2525 2526 /** 2527 * Returns <code>true</code> if the given format is SVG. 2528 * @param {ExportableView.FormatType} formatType 2529 * @return {boolean} 2530 */ 2531 PlanComponent.prototype.isFormatTypeSupported = function(formatType) { 2532 return false; 2533 } 2534 2535 /** 2536 * Writes this plan in the given output stream at SVG (Scalable Vector Graphics) format if this is the requested format. 2537 * @param {java.io.OutputStream} out 2538 * @param {ExportableView.FormatType} formatType 2539 * @param {Object} settings 2540 */ 2541 PlanComponent.prototype.exportData = function(out, formatType, settings) { 2542 throw new UnsupportedOperationException("Unsupported format " + formatType); 2543 } 2544 2545 /** 2546 * Sets rendering hints used to paint plan. 2547 * @param {Graphics2D} g2D 2548 * @private 2549 */ 2550 PlanComponent.prototype.setRenderingHints = function(g2D) { 2551 // TODO 2552 } 2553 2554 /** 2555 * Fills the background. 2556 * @param {Graphics2D} g2D 2557 * @param {string} backgroundColor 2558 * @private 2559 */ 2560 PlanComponent.prototype.paintBackground = function(g2D, backgroundColor) { 2561 if (this.isOpaque()) { 2562 g2D.setColor(backgroundColor); 2563 g2D.fillRect(0, 0, this.canvas.width, this.canvas.height); 2564 } 2565 } 2566 2567 /** 2568 * @private 2569 */ 2570 PlanComponent.prototype.isOpaque = function() { 2571 return this.opaque === true; 2572 } 2573 2574 /** 2575 * @private 2576 */ 2577 PlanComponent.prototype.setOpaque = function(opaque) { 2578 this.opaque = opaque; 2579 } 2580 2581 /** 2582 * Paints background image and returns <code>true</code> if an image is painted. 2583 * @param {Graphics2D} g2D 2584 * @param {Level} level 2585 * @param {PlanComponent.PaintMode} paintMode 2586 * @return {boolean} 2587 * @private 2588 */ 2589 PlanComponent.prototype.paintBackgroundImage = function(g2D, level, paintMode) { 2590 var selectedLevel = this.home.getSelectedLevel(); 2591 var backgroundImageLevel = null; 2592 if (level != null) { 2593 var levels = this.home.getLevels(); 2594 for (var i = levels.length - 1; i >= 0; i--) { 2595 var homeLevel = levels[i]; 2596 if (homeLevel.getElevation() === level.getElevation() 2597 && homeLevel.getElevationIndex() <= level.getElevationIndex() 2598 && homeLevel.isViewable() 2599 && homeLevel.getBackgroundImage() != null 2600 && homeLevel.getBackgroundImage().isVisible()) { 2601 backgroundImageLevel = homeLevel; 2602 break; 2603 } 2604 } 2605 } 2606 var backgroundImage = backgroundImageLevel == null 2607 ? this.home.getBackgroundImage() 2608 : backgroundImageLevel.getBackgroundImage(); 2609 if (backgroundImage != null && backgroundImage.isVisible()) { 2610 var previousTransform = g2D.getTransform(); 2611 g2D.translate(-backgroundImage.getXOrigin(), -backgroundImage.getYOrigin()); 2612 var backgroundImageScale = backgroundImage.getScale(); 2613 g2D.scale(backgroundImageScale, backgroundImageScale); 2614 var oldAlpha = this.setTransparency(g2D, 0.7); 2615 g2D.drawImage(this.backgroundImageCache != null 2616 ? this.backgroundImageCache 2617 : this.readBackgroundImage(backgroundImage.getImage()), 0, 0); 2618 g2D.setAlpha(oldAlpha); 2619 g2D.setTransform(previousTransform); 2620 return true; 2621 } 2622 return false; 2623 } 2624 2625 /** 2626 * Returns the foreground color used to draw content. 2627 * @param {PlanComponent.PaintMode} mode 2628 * @return {string} 2629 */ 2630 PlanComponent.prototype.getForegroundColor = function(mode) { 2631 if (mode === PlanComponent.PaintMode.PAINT) { 2632 return this.getForeground(); 2633 } 2634 else { 2635 return "#000000"; 2636 } 2637 } 2638 2639 /** 2640 * @private 2641 */ 2642 PlanComponent.prototype.getForeground = function() { 2643 if (this.foreground == null) { 2644 this.foreground = ColorTools.styleToHexadecimalString(this.canvas.style.color); 2645 } 2646 return this.foreground; 2647 } 2648 2649 /** 2650 * Returns the background color used to draw content. 2651 * @param {PlanComponent.PaintMode} mode 2652 * @return {string} 2653 */ 2654 PlanComponent.prototype.getBackgroundColor = function(mode) { 2655 if (mode === PlanComponent.PaintMode.PAINT) { 2656 return this.getBackground(); 2657 } 2658 else { 2659 return "#FFFFFF"; 2660 } 2661 } 2662 2663 /** 2664 * @private 2665 */ 2666 PlanComponent.prototype.getBackground = function() { 2667 if (this.background == null) { 2668 this.background = ColorTools.styleToHexadecimalString(this.canvas.style.backgroundColor); 2669 } 2670 return this.background; 2671 } 2672 2673 /** 2674 * Returns the image contained in <code>imageContent</code> or an empty image if reading failed. 2675 * @param {Content} imageContent 2676 * @return {HTMLImageElement} 2677 * @private 2678 */ 2679 PlanComponent.prototype.readBackgroundImage = function(imageContent) { 2680 var plan = this; 2681 if (this.backgroundImageCache != PlanComponent.WAIT_TEXTURE_IMAGE 2682 && this.backgroundImageCache != PlanComponent.ERROR_TEXTURE_IMAGE) { 2683 this.backgroundImageCache = PlanComponent.WAIT_TEXTURE_IMAGE; 2684 TextureManager.getInstance().loadTexture(imageContent, { 2685 textureUpdated: function(texture) { 2686 plan.backgroundImageCache = texture; 2687 plan.repaint(); 2688 }, 2689 textureError: function() { 2690 plan.backgroundImageCache = PlanComponent.ERROR_TEXTURE_IMAGE; 2691 plan.repaint(); 2692 } 2693 }); 2694 } 2695 return this.backgroundImageCache; 2696 } 2697 2698 /** 2699 * Paints walls and rooms of lower levels or upper levels to help the user draw in the given level. 2700 * @param {Graphics2D} g2D 2701 * @param {Level} level 2702 * @param {number} planScale 2703 * @param {string} backgroundColor 2704 * @param {string} foregroundColor 2705 * @private 2706 */ 2707 PlanComponent.prototype.paintOtherLevels = function(g2D, level, planScale, backgroundColor, foregroundColor) { 2708 var plan = this; 2709 var levels = this.home.getLevels(); 2710 if (levels.length 2711 && level != null) { 2712 var level0 = levels[0].getElevation() === level.getElevation(); 2713 var otherLevels = null; 2714 if (this.otherLevelsRoomsCache == null 2715 || this.otherLevelsWallsCache == null) { 2716 var selectedLevelIndex = levels.indexOf(level); 2717 otherLevels = []; 2718 if (level0) { 2719 var nextElevationLevelIndex = selectedLevelIndex; 2720 while (++nextElevationLevelIndex < levels.length 2721 && levels[nextElevationLevelIndex].getElevation() === level.getElevation()) { 2722 } 2723 if (nextElevationLevelIndex < levels.length) { 2724 var nextLevel = levels[nextElevationLevelIndex]; 2725 var nextElevation = nextLevel.getElevation(); 2726 do { 2727 if (nextLevel.isViewable()) { 2728 otherLevels.push(nextLevel); 2729 } 2730 } while (++nextElevationLevelIndex < levels.length 2731 && (nextLevel = levels[nextElevationLevelIndex]).getElevation() === nextElevation); 2732 } 2733 } else { 2734 var previousElevationLevelIndex = selectedLevelIndex; 2735 while (--previousElevationLevelIndex >= 0 2736 && levels[previousElevationLevelIndex].getElevation() === level.getElevation()) { 2737 } 2738 if (previousElevationLevelIndex >= 0) { 2739 var previousLevel = levels[previousElevationLevelIndex]; 2740 var previousElevation = previousLevel.getElevation(); 2741 do { 2742 if (previousLevel.isViewable()) { 2743 otherLevels.push(previousLevel); 2744 } 2745 } while (--previousElevationLevelIndex >= 0 2746 && (previousLevel = levels[previousElevationLevelIndex]).getElevation() === previousElevation); 2747 } 2748 } 2749 if (this.otherLevelsRoomsCache == null) { 2750 if (otherLevels.length !== 0) { 2751 var otherLevelsRooms = []; 2752 this.home.getRooms().forEach(function(room) { 2753 otherLevels.forEach(function(otherLevel) { 2754 if (room.getLevel() === otherLevel 2755 && (level0 && room.isFloorVisible() 2756 || !level0 && room.isCeilingVisible())) { 2757 otherLevelsRooms.push(room); 2758 } 2759 }); 2760 }); 2761 if (otherLevelsRooms.length > 0) { 2762 this.otherLevelsRoomAreaCache = this.getItemsArea(otherLevelsRooms); 2763 this.otherLevelsRoomsCache = otherLevelsRooms; 2764 } 2765 } 2766 if (this.otherLevelsRoomsCache == null) { 2767 this.otherLevelsRoomsCache = []; 2768 } 2769 } 2770 if (this.otherLevelsWallsCache == null) { 2771 if (otherLevels.length !== 0) { 2772 var otherLevelswalls = []; 2773 this.home.getWalls().forEach(function(wall) { 2774 if (!plan.isViewableAtLevel(wall, level)) { 2775 otherLevels.forEach(function(otherLevel) { 2776 if (wall.getLevel() === otherLevel) { 2777 otherLevelswalls.push(wall); 2778 } 2779 }); 2780 } 2781 }); 2782 if (otherLevelswalls.length > 0) { 2783 this.otherLevelsWallAreaCache = this.getItemsArea(otherLevelswalls); 2784 this.otherLevelsWallsCache = otherLevelswalls; 2785 } 2786 } 2787 } 2788 if (this.otherLevelsWallsCache == null) { 2789 this.otherLevelsWallsCache = []; 2790 } 2791 } 2792 if (this.otherLevelsRoomsCache.length !== 0) { 2793 var oldComposite = this.setTransparency(g2D, 2794 this.preferences.isGridVisible() ? 0.2 : 0.1); 2795 g2D.setPaint("#808080"); 2796 g2D.fill(this.otherLevelsRoomAreaCache); 2797 g2D.setAlpha(oldComposite); 2798 } 2799 if (this.otherLevelsWallsCache.length !== 0) { 2800 var oldComposite = this.setTransparency(g2D, 2801 this.preferences.isGridVisible() ? 0.2 : 0.1); 2802 this.fillAndDrawWallsArea(g2D, this.otherLevelsWallAreaCache, planScale, 2803 this.getWallPaint(g2D, planScale, backgroundColor, foregroundColor, this.preferences.getNewWallPattern()), 2804 foregroundColor, PlanComponent.PaintMode.PAINT); 2805 g2D.setAlpha(oldComposite); 2806 } 2807 } 2808 } 2809 2810 /** 2811 * Sets the transparency composite to the given percentage and returns the old composite. 2812 * @param {Graphics2D} g2D 2813 * @param {number} alpha 2814 * @return {Object} 2815 * @private 2816 */ 2817 PlanComponent.prototype.setTransparency = function(g2D, alpha) { 2818 var oldAlpha = g2D.getAlpha(); 2819 g2D.setAlpha(alpha); 2820 return oldAlpha; 2821 } 2822 2823 /** 2824 * Paints background grid lines. 2825 * @param {Graphics2D} g2D 2826 * @param {number} gridScale 2827 * @private 2828 */ 2829 PlanComponent.prototype.paintGrid = function(g2D, gridScale) { 2830 var gridSize = this.getGridSize(gridScale); 2831 var mainGridSize = this.getMainGridSize(gridScale); 2832 var planBounds = this.getPlanBounds(); 2833 var xMin = planBounds.getMinX() - PlanComponent.MARGIN; 2834 var yMin = planBounds.getMinY() - PlanComponent.MARGIN; 2835 var xMax = this.convertXPixelToModel(Math.max(this.getWidth(), this.canvas.clientWidth)); 2836 var yMax = this.convertYPixelToModel(Math.max(this.getHeight(), this.canvas.clientHeight)); 2837 this.paintGridLines(g2D, gridScale, xMin, xMax, yMin, yMax, gridSize, mainGridSize); 2838 } 2839 2840 /** 2841 * Paints background grid lines from <code>xMin</code> to <code>xMax</code> 2842 * and <code>yMin</code> to <code>yMax</code>. 2843 * @param {Graphics2D} g2D 2844 * @param {number} gridScale 2845 * @param {number} xMin 2846 * @param {number} xMax 2847 * @param {number} yMin 2848 * @param {number} yMax 2849 * @param {number} gridSize 2850 * @param {number} mainGridSize 2851 * @private 2852 */ 2853 PlanComponent.prototype.paintGridLines = function(g2D, gridScale, xMin, xMax, yMin, yMax, gridSize, mainGridSize) { 2854 g2D.setColor(this.getGridColor()); 2855 g2D.setStroke(new java.awt.BasicStroke(0.5 / gridScale)); 2856 for (var x = ((xMin / gridSize) | 0) * gridSize; x < xMax; x += gridSize) { 2857 g2D.draw(new java.awt.geom.Line2D.Double(x, yMin, x, yMax)); 2858 } 2859 for (var y = ((yMin / gridSize) | 0) * gridSize; y < yMax; y += gridSize) { 2860 g2D.draw(new java.awt.geom.Line2D.Double(xMin, y, xMax, y)); 2861 } 2862 if (mainGridSize !== gridSize) { 2863 g2D.setStroke(new java.awt.BasicStroke(1.5 / gridScale, 2864 java.awt.BasicStroke.CAP_BUTT, java.awt.BasicStroke.JOIN_BEVEL)); 2865 for (var x = ((xMin / mainGridSize) | 0) * mainGridSize; x < xMax; x += mainGridSize) { 2866 g2D.draw(new java.awt.geom.Line2D.Double(x, yMin, x, yMax)); 2867 } 2868 for (var y = ((yMin / mainGridSize) | 0) * mainGridSize; y < yMax; y += mainGridSize) { 2869 g2D.draw(new java.awt.geom.Line2D.Double(xMin, y, xMax, y)); 2870 } 2871 } 2872 } 2873 2874 /** 2875 * Returns the color used to paint the grid. 2876 * @private 2877 */ 2878 PlanComponent.prototype.getGridColor = function() { 2879 if (this.gridColor == null) { 2880 // compute the gray color in between background and foreground colors 2881 var background = ColorTools.styleToInteger(this.canvas.style.backgroundColor); 2882 var foreground = ColorTools.styleToInteger(this.canvas.style.color); 2883 var gridColorComponent = ((((background & 0xFF) + (foreground & 0xFF) + (background & 0xFF00) + (foreground & 0xFF00) + (background & 0xFF0000) + (foreground & 0xFF0000)) / 6) | 0) & 0xFF; 2884 this.gridColor = ColorTools.integerToHexadecimalString(gridColorComponent + (gridColorComponent << 8) + (gridColorComponent << 16)); 2885 } 2886 return this.gridColor; 2887 } 2888 2889 /** 2890 * Returns the space between main lines grid. 2891 * @param {number} gridScale 2892 * @return {number} 2893 * @private 2894 */ 2895 PlanComponent.prototype.getMainGridSize = function(gridScale) { 2896 var mainGridSizes; 2897 var lengthUnit = this.preferences.getLengthUnit(); 2898 if (lengthUnit.isMetric()) { 2899 mainGridSizes = [100, 200, 500, 1000, 2000, 5000, 10000]; 2900 } else { 2901 var oneFoot = 2.54 * 12; 2902 mainGridSizes = [oneFoot, 3 * oneFoot, 6 * oneFoot, 2903 12 * oneFoot, 24 * oneFoot, 48 * oneFoot, 96 * oneFoot, 192 * oneFoot, 384 * oneFoot]; 2904 } 2905 var mainGridSize = mainGridSizes[0]; 2906 for (var i = 1; i < mainGridSizes.length && mainGridSize * gridScale < 50; i++) { 2907 mainGridSize = mainGridSizes[i]; 2908 } 2909 return mainGridSize; 2910 } 2911 2912 /** 2913 * Returns the space between lines grid. 2914 * @param {number} gridScale 2915 * @return {number} 2916 * @private 2917 */ 2918 PlanComponent.prototype.getGridSize = function(gridScale) { 2919 var gridSizes; 2920 var lengthUnit = this.preferences.getLengthUnit(); 2921 if (lengthUnit.isMetric()) { 2922 gridSizes = [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000]; 2923 } else { 2924 var oneFoot = 2.54 * 12; 2925 gridSizes = [2.54, 5.08, 7.62, 15.24, oneFoot, 3 * oneFoot, 6 * oneFoot, 2926 12 * oneFoot, 24 * oneFoot, 48 * oneFoot, 96 * oneFoot, 192 * oneFoot, 384 * oneFoot]; 2927 } 2928 var gridSize = gridSizes[0]; 2929 for (var i = 1; i < gridSizes.length && gridSize * gridScale < 10; i++) { 2930 gridSize = gridSizes[i]; 2931 } 2932 return gridSize; 2933 } 2934 2935 /** 2936 * Paints plan items at the given <code>level</code>. 2937 * @throws InterruptedIOException if painting was interrupted (may happen only 2938 * if <code>paintMode</code> is equal to <code>PaintMode.EXPORT</code>). 2939 * @param {Graphics2D} g2D 2940 * @param {Level} level 2941 * @param {number} gridScale 2942 * @param {PlanComponent.PaintMode} paintMode 2943 * @private 2944 */ 2945 PlanComponent.prototype.paintContent = function(g2D, level, planScale, paintMode) { 2946 var backgroundColor = this.getBackgroundColor(paintMode); 2947 var foregroundColor = this.getForegroundColor(paintMode); 2948 if (this.backgroundPainted) { 2949 this.paintBackgroundImage(g2D, level, paintMode); 2950 if (paintMode === PlanComponent.PaintMode.PAINT) { 2951 this.paintOtherLevels(g2D, level, planScale, backgroundColor, foregroundColor); 2952 if (this.preferences.isGridVisible()) { 2953 this.paintGrid(g2D, planScale); 2954 } 2955 } 2956 } 2957 2958 if (level == this.home.getSelectedLevel()) { 2959 // Call deprecated implementation in case a subclass did override paintHomeItems 2960 this.paintHomeItems(g2D, planScale, backgroundColor, foregroundColor, paintMode); 2961 } else { 2962 this.paintHomeItems(g2D, level, planScale, backgroundColor, foregroundColor, paintMode); 2963 } 2964 2965 if (paintMode === PlanComponent.PaintMode.PAINT) { 2966 var selectedItems = this.home.getSelectedItems(); 2967 var selectionColor = this.getSelectionColor(); 2968 var furnitureOutlineColor = this.getFurnitureOutlineColor(); 2969 var selectionOutlinePaint = ColorTools.toRGBAStyle(selectionColor, 0.5); 2970 var selectionOutlineStroke = new java.awt.BasicStroke(6 / planScale, 2971 java.awt.BasicStroke.CAP_ROUND, java.awt.BasicStroke.JOIN_ROUND); 2972 var dimensionLinesSelectionOutlineStroke = new java.awt.BasicStroke(4 / planScale, 2973 java.awt.BasicStroke.CAP_ROUND, java.awt.BasicStroke.JOIN_ROUND); 2974 var locationFeedbackStroke = new java.awt.BasicStroke( 2975 1 / planScale, java.awt.BasicStroke.CAP_SQUARE, java.awt.BasicStroke.JOIN_BEVEL, 0, 2976 [20 / planScale, 5 / planScale, 5 / planScale, 5 / planScale], 4 / planScale); 2977 2978 this.paintCamera(g2D, selectedItems, selectionOutlinePaint, selectionOutlineStroke, selectionColor, 2979 planScale, backgroundColor, foregroundColor); 2980 2981 if (this.alignedObjectClass != null) { 2982 if (Wall === this.alignedObjectClass) { 2983 this.paintWallAlignmentFeedback(g2D, this.alignedObjectFeedback, level, this.locationFeeback, this.showPointFeedback, 2984 selectionColor, locationFeedbackStroke, planScale, 2985 selectionOutlinePaint, selectionOutlineStroke); 2986 } else if (Room === this.alignedObjectClass) { 2987 this.paintRoomAlignmentFeedback(g2D, this.alignedObjectFeedback, level, this.locationFeeback, this.showPointFeedback, 2988 selectionColor, locationFeedbackStroke, planScale, 2989 selectionOutlinePaint, selectionOutlineStroke); 2990 } else if (Polyline === this.alignedObjectClass) { 2991 if (this.showPointFeedback) { 2992 this.paintPointFeedback(g2D, this.locationFeeback, selectionColor, planScale, selectionOutlinePaint, selectionOutlineStroke); 2993 } 2994 } else if (DimensionLine === this.alignedObjectClass) { 2995 this.paintDimensionLineAlignmentFeedback(g2D, this.alignedObjectFeedback, level, this.locationFeeback, this.showPointFeedback, 2996 selectionColor, locationFeedbackStroke, planScale, 2997 selectionOutlinePaint, selectionOutlineStroke); 2998 } 2999 } 3000 if (this.centerAngleFeedback != null) { 3001 this.paintAngleFeedback(g2D, this.centerAngleFeedback, this.point1AngleFeedback, this.point2AngleFeedback, 3002 planScale, selectionColor); 3003 } 3004 if (this.dimensionLinesFeedback != null) { 3005 var emptySelection = []; 3006 this.paintDimensionLines(g2D, this.dimensionLinesFeedback, emptySelection, level, 3007 null, null, null, locationFeedbackStroke, planScale, 3008 backgroundColor, selectionColor, paintMode, true); 3009 } 3010 3011 if (this.draggedItemsFeedback != null) { 3012 this.paintDimensionLines(g2D, Home.getDimensionLinesSubList(this.draggedItemsFeedback), this.draggedItemsFeedback, level, 3013 selectionOutlinePaint, dimensionLinesSelectionOutlineStroke, null, 3014 locationFeedbackStroke, planScale, backgroundColor, foregroundColor, paintMode, false); 3015 this.paintLabels(g2D, Home.getLabelsSubList(this.draggedItemsFeedback), this.draggedItemsFeedback, level, 3016 selectionOutlinePaint, dimensionLinesSelectionOutlineStroke, null, 3017 planScale, foregroundColor, paintMode); 3018 this.paintRoomsOutline(g2D, this.draggedItemsFeedback, level, selectionOutlinePaint, selectionOutlineStroke, null, 3019 planScale, foregroundColor); 3020 this.paintWallsOutline(g2D, this.draggedItemsFeedback, level, selectionOutlinePaint, selectionOutlineStroke, null, 3021 planScale, foregroundColor); 3022 this.paintFurniture(g2D, Home.getFurnitureSubList(this.draggedItemsFeedback), selectedItems, level, planScale, null, 3023 foregroundColor, furnitureOutlineColor, paintMode, false); 3024 this.paintFurnitureOutline(g2D, this.draggedItemsFeedback, level, selectionOutlinePaint, selectionOutlineStroke, null, 3025 planScale, foregroundColor); 3026 } 3027 3028 this.paintRectangleFeedback(g2D, selectionColor, planScale); 3029 } 3030 } 3031 3032 /** 3033 * Paints home items at the given scale, and with background and foreground colors. 3034 * Outline around selected items will be painted only under <code>PAINT</code> mode. 3035 * @param {Graphics2D} g 3036 * @param {Level} level 3037 * @param {number} planScale 3038 * @param {string} backgroundColor 3039 * @param {string} foregroundColor 3040 * @param {PlanComponent.PaintMode} paintMode 3041 */ 3042 PlanComponent.prototype.paintHomeItems = function(g2D, level, planScale, backgroundColor, foregroundColor, paintMode) { 3043 if (paintMode === undefined) { 3044 // 5 parameters 3045 paintMode = foregroundColor; 3046 foregroundColor = backgroundColor; 3047 backgroundColor = planScale; 3048 planScale = level; 3049 level = this.home.getSelectedLevel(); 3050 } 3051 var plan = this; 3052 var selectedItems = this.home.getSelectedItems(); 3053 if (this.sortedLevelFurniture == null) { 3054 this.sortedLevelFurniture = []; 3055 this.home.getFurniture().forEach(function(piece) { 3056 if (plan.isViewableAtLevel(piece, level)) { 3057 plan.sortedLevelFurniture.push(piece); 3058 } 3059 }); 3060 CoreTools.sortArray(this.sortedLevelFurniture, { 3061 compare: function(piece1, piece2) { 3062 return (piece1.getGroundElevation() - piece2.getGroundElevation()); 3063 } 3064 }); 3065 } 3066 var selectionColor = this.getSelectionColor(); 3067 var selectionOutlinePaint = ColorTools.toRGBAStyle(selectionColor, 0.5); 3068 var selectionOutlineStroke = new java.awt.BasicStroke(6 / planScale, 3069 java.awt.BasicStroke.CAP_ROUND, java.awt.BasicStroke.JOIN_ROUND); 3070 var dimensionLinesSelectionOutlineStroke = new java.awt.BasicStroke(4 / planScale, 3071 java.awt.BasicStroke.CAP_ROUND, java.awt.BasicStroke.JOIN_ROUND); 3072 var locationFeedbackStroke = new java.awt.BasicStroke( 3073 1 / planScale, java.awt.BasicStroke.CAP_SQUARE, java.awt.BasicStroke.JOIN_BEVEL, 0, 3074 [20 / planScale, 5 / planScale, 5 / planScale, 5 / planScale], 4 / planScale); 3075 3076 this.paintCompass(g2D, selectedItems, planScale, foregroundColor, paintMode); 3077 3078 this.paintRooms(g2D, selectedItems, level, planScale, foregroundColor, paintMode); 3079 3080 this.paintWalls(g2D, selectedItems, level, planScale, backgroundColor, foregroundColor, paintMode); 3081 3082 this.paintFurniture(g2D, this.sortedLevelFurniture, selectedItems, level, 3083 planScale, backgroundColor, foregroundColor, this.getFurnitureOutlineColor(), paintMode, true); 3084 3085 this.paintPolylines(g2D, this.home.getPolylines(), selectedItems, level, 3086 selectionOutlinePaint, selectionColor, planScale, foregroundColor, paintMode); 3087 3088 this.paintDimensionLines(g2D, this.home.getDimensionLines(), selectedItems, level, 3089 selectionOutlinePaint, dimensionLinesSelectionOutlineStroke, selectionColor, 3090 locationFeedbackStroke, planScale, backgroundColor, foregroundColor, paintMode, false); 3091 3092 this.paintRoomsNameAndArea(g2D, selectedItems, planScale, foregroundColor, paintMode); 3093 3094 this.paintFurnitureName(g2D, this.sortedLevelFurniture, selectedItems, planScale, foregroundColor, paintMode); 3095 3096 this.paintLabels(g2D, this.home.getLabels(), selectedItems, level, 3097 selectionOutlinePaint, dimensionLinesSelectionOutlineStroke, 3098 selectionColor, planScale, foregroundColor, paintMode); 3099 3100 if (paintMode === PlanComponent.PaintMode.PAINT 3101 && this.selectedItemsOutlinePainted) { 3102 this.paintCompassOutline(g2D, selectedItems, selectionOutlinePaint, selectionOutlineStroke, selectionColor, 3103 planScale, foregroundColor); 3104 this.paintRoomsOutline(g2D, selectedItems, level, selectionOutlinePaint, selectionOutlineStroke, selectionColor, 3105 planScale, foregroundColor); 3106 this.paintWallsOutline(g2D, selectedItems, level, selectionOutlinePaint, selectionOutlineStroke, selectionColor, 3107 planScale, foregroundColor); 3108 this.paintFurnitureOutline(g2D, selectedItems, level, selectionOutlinePaint, selectionOutlineStroke, selectionColor, 3109 planScale, foregroundColor); 3110 } 3111 } 3112 3113 /** 3114 * Returns the color used to draw selection outlines. 3115 * @return {string} 3116 */ 3117 PlanComponent.prototype.getSelectionColor = function() { 3118 return PlanComponent.getDefaultSelectionColor(this); 3119 } 3120 3121 /** 3122 * Returns the default color used to draw selection outlines. 3123 * Note that the default selection color may be forced using CSS by setting backgroundColor with the ::selection selector. 3124 * In case the browser does not support the ::selection selector, one can force the selectio color in JavaScript by setting PlanComponent.DEFAULT_SELECTION_COLOR. 3125 * @param {PlanComponent} planComponent 3126 * @return {string} 3127 */ 3128 PlanComponent.getDefaultSelectionColor = function(planComponent) { 3129 if (PlanComponent.DEFAULT_SELECTION_COLOR == null) { 3130 var color = window.getComputedStyle(planComponent.container, "::selection").backgroundColor; 3131 if (color.indexOf("rgb") === -1 3132 || ColorTools.isTransparent(color) 3133 || color == window.getComputedStyle(planComponent.container).backgroundColor) { 3134 planComponent.container.style.color = "Highlight"; 3135 color = window.getComputedStyle(planComponent.container).color; 3136 } 3137 if (color.indexOf("rgb") === -1 3138 || ColorTools.isTransparent(color) 3139 || color == window.getComputedStyle(planComponent.container).backgroundColor) { 3140 PlanComponent.DEFAULT_SELECTION_COLOR = "#0042E0"; 3141 } else { 3142 PlanComponent.DEFAULT_SELECTION_COLOR = ColorTools.styleToHexadecimalString(color); 3143 } 3144 } 3145 return PlanComponent.DEFAULT_SELECTION_COLOR; 3146 } 3147 3148 /** 3149 * Returns the color used to draw furniture outline of 3150 * the shape where a user can click to select a piece of furniture. 3151 * @return {string} 3152 */ 3153 PlanComponent.prototype.getFurnitureOutlineColor = function() { 3154 return ColorTools.toRGBAStyle(this.getForeground(), 0.33); 3155 } 3156 3157 /** 3158 * Paints rooms. 3159 * @param {Graphics2D} g2D 3160 * @param {Object[]} selectedItems 3161 * @param {Level} level 3162 * @param {number} planScale 3163 * @param {string} foregroundColor 3164 * @param {PlanComponent.PaintMode} paintMode 3165 * @private 3166 */ 3167 PlanComponent.prototype.paintRooms = function(g2D, selectedItems, level, planScale, foregroundColor, paintMode) { 3168 var plan = this; 3169 if (this.sortedLevelRooms == null) { 3170 this.sortedLevelRooms = []; 3171 this.home.getRooms().forEach(function(room) { 3172 if (plan.isViewableAtLevel(room, level)) { 3173 plan.sortedLevelRooms.push(room); 3174 } 3175 }); 3176 CoreTools.sortArray(this.sortedLevelRooms, { 3177 compare: function(room1, room2) { 3178 if (room1.isFloorVisible() === room2.isFloorVisible() 3179 && room1.isCeilingVisible() === room2.isCeilingVisible()) { 3180 return 0; 3181 } else if (!room1.isFloorVisible() && !room1.isCeilingVisible() 3182 || room1.isFloorVisible() && room2.isCeilingVisible()) { 3183 return -1; 3184 } else { 3185 return 1; 3186 } 3187 } 3188 }); 3189 } 3190 var defaultFillPaint = paintMode === PlanComponent.PaintMode.PRINT 3191 ? "#000000" 3192 : "#808080"; 3193 g2D.setStroke(new java.awt.BasicStroke(this.getStrokeWidth(Room, paintMode) / planScale)); 3194 for (var i = 0; i < this.sortedLevelRooms.length; i++) { 3195 var room = plan.sortedLevelRooms[i]; 3196 var selectedRoom = selectedItems.indexOf(room) >= 0; 3197 if (paintMode !== PlanComponent.PaintMode.CLIPBOARD 3198 || selectedRoom) { 3199 g2D.setPaint(defaultFillPaint); 3200 var textureAngle = 0; 3201 var textureScaleX = 1; 3202 var textureScaleY = 1; 3203 var textureOffsetX = 0; 3204 var textureOffsetY = 0; 3205 var floorTexture = null; 3206 if (plan.preferences.isRoomFloorColoredOrTextured() && room.isFloorVisible()) { 3207 if (room.getFloorColor() != null) { 3208 g2D.setPaint(ColorTools.integerToHexadecimalString(room.getFloorColor())); 3209 } else { 3210 floorTexture = room.getFloorTexture(); 3211 if (floorTexture != null) { 3212 if (plan.floorTextureImagesCache == null) { 3213 plan.floorTextureImagesCache = {}; 3214 } 3215 var textureImage = plan.floorTextureImagesCache[floorTexture.getImage().getURL()]; 3216 if (textureImage == null) { 3217 textureImage = PlanComponent.WAIT_TEXTURE_IMAGE; 3218 plan.floorTextureImagesCache[floorTexture.getImage().getURL()] = textureImage; 3219 var waitForTexture = paintMode !== PlanComponent.PaintMode.PAINT; 3220 TextureManager.getInstance().loadTexture(floorTexture.getImage(), waitForTexture, { 3221 floorTexture: floorTexture, 3222 textureUpdated: function(texture) { 3223 plan.floorTextureImagesCache[this.floorTexture.getImage().getURL()] = texture; 3224 if (!waitForTexture) { 3225 plan.repaint(); 3226 } 3227 }, 3228 textureError: function() { 3229 plan.floorTextureImagesCache[this.floorTexture.getImage().getURL()] = PlanComponent.ERROR_TEXTURE_IMAGE; 3230 }, 3231 progression: function() { } 3232 }); 3233 } 3234 if (room.getFloorTexture().isFittingArea()) { 3235 var min = room.getBoundsMinimumCoordinates(); 3236 var max = room.getBoundsMaximumCoordinates(); 3237 textureScaleX = (max[0] - min[0]) / textureImage.naturalWidth; 3238 textureScaleY = (max[1] - min[1]) / textureImage.naturalHeight; 3239 textureOffsetX = min[0] / textureScaleX; 3240 textureOffsetY = min[1] / textureScaleY; 3241 } else { 3242 var textureWidth = floorTexture.getWidth(); 3243 var textureHeight = floorTexture.getHeight(); 3244 if (textureWidth === -1 || textureHeight === -1) { 3245 textureWidth = 100; 3246 textureHeight = 100; 3247 } 3248 var textureScale = floorTexture.getScale(); 3249 textureScaleX = (textureWidth * textureScale) / textureImage.naturalWidth; 3250 textureScaleY = (textureHeight * textureScale) / textureImage.naturalHeight; 3251 textureAngle = floorTexture.getAngle(); 3252 var cosAngle = Math.cos(textureAngle); 3253 var sinAngle = Math.sin(textureAngle); 3254 textureOffsetX = (floorTexture.getXOffset() * textureImage.naturalWidth * cosAngle - floorTexture.getYOffset() * textureImage.height * sinAngle); 3255 textureOffsetY = (-floorTexture.getXOffset() * textureImage.naturalWidth * sinAngle - floorTexture.getYOffset() * textureImage.height * cosAngle); 3256 } 3257 g2D.setPaint(g2D.createPattern(textureImage)); 3258 } 3259 } 3260 } 3261 3262 var oldComposite = plan.setTransparency(g2D, room.isFloorVisible() ? 0.75 : 0.5); 3263 var transform = null; 3264 if (floorTexture != null) { 3265 g2D.scale(textureScaleX, textureScaleY); 3266 g2D.rotate(textureAngle, 0, 0); 3267 g2D.translate(textureOffsetX, textureOffsetY); 3268 transform = java.awt.geom.AffineTransform.getTranslateInstance(-textureOffsetX, -textureOffsetY); 3269 transform.rotate(-textureAngle, 0, 0); 3270 transform.scale(1 / textureScaleX, 1 / textureScaleY); 3271 } 3272 var roomShape = ShapeTools.getShape(room.getPoints(), true, transform); 3273 plan.fillShape(g2D, roomShape, paintMode); 3274 if (floorTexture != null) { 3275 g2D.translate(-textureOffsetX, -textureOffsetY); 3276 g2D.rotate(-textureAngle, 0, 0); 3277 g2D.scale(1 / textureScaleX, 1 / textureScaleY); 3278 roomShape = ShapeTools.getShape(room.getPoints(), true); 3279 } 3280 g2D.setAlpha(oldComposite); 3281 g2D.setPaint(foregroundColor); 3282 g2D.draw(roomShape); 3283 } 3284 } 3285 } 3286 3287 /** 3288 * Fills the given <code>shape</code>. 3289 * @param {Graphics2D} g2D 3290 * @param {Object} shape 3291 * @param {PlanComponent.PaintMode} paintMode 3292 * @private 3293 */ 3294 PlanComponent.prototype.fillShape = function(g2D, shape, paintMode) { 3295 g2D.fill(shape); 3296 } 3297 3298 /** 3299 * Returns <code>true</code> if <code>TextureManager</code> can be used to manage textures. 3300 * @return {boolean} 3301 * @private 3302 */ 3303 PlanComponent.isTextureManagerAvailable = function() { 3304 return true; 3305 } 3306 3307 /** 3308 * Paints rooms name and area. 3309 * @param {Graphics2D} g2D 3310 * @param {Object[]} selectedItems 3311 * @param {number} planScale 3312 * @param {string} foregroundColor 3313 * @param {PlanComponent.PaintMode} paintMode 3314 * @private 3315 */ 3316 PlanComponent.prototype.paintRoomsNameAndArea = function(g2D, selectedItems, planScale, foregroundColor, paintMode) { 3317 var plan = this; 3318 g2D.setPaint(foregroundColor); 3319 var previousFont = g2D.getFont(); 3320 this.sortedLevelRooms.forEach(function(room) { 3321 var selectedRoom = (selectedItems.indexOf(room) >= 0); 3322 if (paintMode !== PlanComponent.PaintMode.CLIPBOARD 3323 || selectedRoom) { 3324 var xRoomCenter = room.getXCenter(); 3325 var yRoomCenter = room.getYCenter(); 3326 var name = room.getName(); 3327 if (name != null) { 3328 name = name.trim(); 3329 if (name.length > 0) { 3330 plan.paintText(g2D, room.constructor, name, room.getNameStyle(), null, 3331 xRoomCenter + room.getNameXOffset(), 3332 yRoomCenter + room.getNameYOffset(), 3333 room.getNameAngle(), previousFont); 3334 } 3335 } 3336 if (room.isAreaVisible()) { 3337 var area = room.getArea(); 3338 if (area > 0.01) { 3339 var areaText = plan.preferences.getLengthUnit().getAreaFormatWithUnit().format(area); 3340 plan.paintText(g2D, room.constructor, areaText, room.getAreaStyle(), null, 3341 xRoomCenter + room.getAreaXOffset(), 3342 yRoomCenter + room.getAreaYOffset(), 3343 room.getAreaAngle(), previousFont); 3344 } 3345 } 3346 } 3347 }); 3348 g2D.setFont(previousFont); 3349 } 3350 3351 /** 3352 * Paints the given <code>text</code> centered at the point (<code>x</code>,<code>y</code>). 3353 * @param {Graphics2D} g2D 3354 * @param {Object} selectableClass 3355 * @param {string} text 3356 * @param {TextStyle} style 3357 * @param {number} outlineColor 3358 * @param {number} x 3359 * @param {number} y 3360 * @param {number} angle 3361 * @param {string} defaultFont 3362 * @private 3363 */ 3364 PlanComponent.prototype.paintText = function(g2D, selectableClass, text, style, outlineColor, x, y, angle, defaultFont) { 3365 var previousTransform = g2D.getTransform(); 3366 g2D.translate(x, y); 3367 g2D.rotate(angle); 3368 if (style == null) { 3369 style = this.preferences.getDefaultTextStyle(selectableClass); 3370 } 3371 var fontMetrics = this.getFontMetrics(defaultFont, style); 3372 var lines = text.replace(/\n*$/, "").split("\n"); 3373 var lineWidths = new Array(lines.length); 3374 var textWidth = -3.4028235E38; 3375 for (var i = 0; i < lines.length; i++) { 3376 lineWidths[i] = fontMetrics.getStringBounds(lines[i]).getWidth(); 3377 textWidth = Math.max(lineWidths[i], textWidth); 3378 } 3379 var stroke = null; 3380 var font; 3381 if (outlineColor != null) { 3382 stroke = new java.awt.BasicStroke(style.getFontSize() * 0.05); 3383 // Call directly the overloaded deriveStyle method that takes a float parameter 3384 // to avoid confusion with the one that takes a TextStyle.Alignment parameter 3385 var outlineStyle = style.deriveStyle$float(style.getFontSize() - stroke.getLineWidth()); 3386 font = this.getFont(defaultFont, outlineStyle); 3387 g2D.setStroke(stroke); 3388 } else { 3389 font = this.getFont(defaultFont, style); 3390 } 3391 g2D.setFont(font); 3392 3393 for (var i = lines.length - 1; i >= 0; i--) { 3394 var line = lines[i]; 3395 var translationX = void 0; 3396 if (style.getAlignment() === TextStyle.Alignment.LEFT) { 3397 translationX = 0; 3398 } else if (style.getAlignment() === TextStyle.Alignment.RIGHT) { 3399 translationX = -lineWidths[i]; 3400 } else { 3401 translationX = -lineWidths[i] / 2; 3402 } 3403 if (outlineColor != null) { 3404 translationX += stroke.getLineWidth() / 2; 3405 } 3406 g2D.translate(translationX, 0); 3407 if (outlineColor != null) { 3408 var defaultColor = g2D.getColor(); 3409 g2D.setColor(ColorTools.integerToHexadecimalString(outlineColor)); 3410 g2D.drawStringOutline(line, 0, 0); 3411 g2D.setColor(defaultColor); 3412 } 3413 g2D.drawString(line, 0, 0); 3414 g2D.translate(-translationX, -fontMetrics.getHeight()); 3415 } 3416 g2D.setTransform(previousTransform); 3417 } 3418 3419 /** 3420 * Paints the outline of rooms among <code>items</code> and indicators if 3421 * <code>items</code> contains only one room and indicator paint isn't <code>null</code>. 3422 * @param {Graphics2D} g2D 3423 * @param {Object[]} items 3424 * @param {Level} level 3425 * @param {string|CanvasPattern} selectionOutlinePaint 3426 * @param {java.awt.BasicStroke} selectionOutlineStroke 3427 * @param {string|CanvasPattern} indicatorPaint 3428 * @param {number} planScale 3429 * @param {string} foregroundColor 3430 * @private 3431 */ 3432 PlanComponent.prototype.paintRoomsOutline = function(g2D, items, level, selectionOutlinePaint, selectionOutlineStroke, indicatorPaint, planScale, foregroundColor) { 3433 var rooms = Home.getRoomsSubList(items); 3434 var previousTransform = g2D.getTransform(); 3435 var scaleInverse = 1 / planScale; 3436 for (var i = 0; i < rooms.length; i++) { 3437 var room = rooms[i]; 3438 if (this.isViewableAtLevel(room, level)) { 3439 g2D.setPaint(selectionOutlinePaint); 3440 g2D.setStroke(selectionOutlineStroke); 3441 g2D.draw(ShapeTools.getShape(room.getPoints(), true, null)); 3442 3443 if (indicatorPaint != null) { 3444 g2D.setPaint(indicatorPaint); 3445 room.getPoints().forEach(function(point) { 3446 g2D.translate(point[0], point[1]); 3447 g2D.scale(scaleInverse, scaleInverse); 3448 g2D.setStroke(PlanComponent.POINT_STROKE); 3449 g2D.fill(PlanComponent.WALL_POINT); 3450 g2D.setTransform(previousTransform); 3451 }); 3452 } 3453 } 3454 } 3455 3456 g2D.setPaint(foregroundColor); 3457 g2D.setStroke(new java.awt.BasicStroke(this.getStrokeWidth(Room, PlanComponent.PaintMode.PAINT) / planScale)); 3458 for (var i = 0; i < rooms.length; i++) { 3459 var room = rooms[i]; 3460 if (this.isViewableAtLevel(room, level)) { 3461 g2D.draw(ShapeTools.getShape(room.getPoints(), true, null)); 3462 } 3463 } 3464 3465 if (items.length === 1 3466 && rooms.length === 1 3467 && indicatorPaint != null) { 3468 var selectedRoom = rooms[0]; 3469 if (this.isViewableAtLevel(room, level)) { 3470 g2D.setPaint(indicatorPaint); 3471 this.paintPointsResizeIndicators(g2D, selectedRoom, indicatorPaint, planScale, true, 0, 0, true); 3472 this.paintRoomNameOffsetIndicator(g2D, selectedRoom, indicatorPaint, planScale); 3473 this.paintRoomAreaOffsetIndicator(g2D, selectedRoom, indicatorPaint, planScale); 3474 } 3475 } 3476 } 3477 3478 /** 3479 * Paints resize indicators on selectable <code>item</code>. 3480 * @param {Graphics2D} g2D 3481 * @param {Object} item 3482 * @param {string|CanvasPattern} indicatorPaint 3483 * @param {number} planScale 3484 * @param {boolean} closedPath 3485 * @param {number} angleAtStart 3486 * @param {number} angleAtEnd 3487 * @param {boolean} orientateIndicatorOutsideShape 3488 * @private 3489 */ 3490 PlanComponent.prototype.paintPointsResizeIndicators = function(g2D, item, indicatorPaint, planScale, closedPath, angleAtStart, angleAtEnd, orientateIndicatorOutsideShape) { 3491 if (this.resizeIndicatorVisible) { 3492 g2D.setPaint(indicatorPaint); 3493 g2D.setStroke(PlanComponent.INDICATOR_STROKE); 3494 var previousTransform = g2D.getTransform(); 3495 var scaleInverse = 1 / planScale; 3496 var points = item.getPoints(); 3497 var resizeIndicator = this.getIndicator(item, PlanComponent.IndicatorType.RESIZE); 3498 for (var i = 0; i < points.length; i++) { 3499 var point = points[i]; 3500 g2D.translate(point[0], point[1]); 3501 g2D.scale(scaleInverse, scaleInverse); 3502 var previousPoint = i === 0 3503 ? points[points.length - 1] 3504 : points[i - 1]; 3505 var nextPoint = i === points.length - 1 3506 ? points[0] 3507 : points[i + 1]; 3508 var angle = void 0; 3509 if (closedPath || (i > 0 && i < points.length - 1)) { 3510 var distance1 = java.awt.geom.Point2D.distance( 3511 previousPoint[0], previousPoint[1], point[0], point[1]); 3512 var xNormal1 = (point[1] - previousPoint[1]) / distance1; 3513 var yNormal1 = (previousPoint[0] - point[0]) / distance1; 3514 var distance2 = java.awt.geom.Point2D.distance( 3515 nextPoint[0], nextPoint[1], point[0], point[1]); 3516 var xNormal2 = (nextPoint[1] - point[1]) / distance2; 3517 var yNormal2 = (point[0] - nextPoint[0]) / distance2; 3518 angle = Math.atan2(yNormal1 + yNormal2, xNormal1 + xNormal2); 3519 if (orientateIndicatorOutsideShape 3520 && item.containsPoint(point[0] + Math.cos(angle), 3521 point[1] + Math.sin(angle), 0.001) 3522 || !orientateIndicatorOutsideShape 3523 && (xNormal1 * yNormal2 - yNormal1 * xNormal2) < 0) { 3524 angle += Math.PI; 3525 } 3526 } else if (i === 0) { 3527 angle = angleAtStart; 3528 } else { 3529 angle = angleAtEnd; 3530 } 3531 g2D.rotate(angle); 3532 g2D.draw(resizeIndicator); 3533 g2D.setTransform(previousTransform); 3534 } 3535 } 3536 } 3537 3538 /** 3539 * Returns the shape of the given indicator type. 3540 * @param {Object} item 3541 * @param {PlanComponent.IndicatorType} indicatorType 3542 * @return {Object} 3543 */ 3544 PlanComponent.prototype.getIndicator = function(item, indicatorType) { 3545 if (PlanComponent.IndicatorType.RESIZE === indicatorType) { 3546 if (item instanceof HomePieceOfFurniture) { 3547 return PlanComponent.FURNITURE_RESIZE_INDICATOR; 3548 } else if (item instanceof Compass) { 3549 return PlanComponent.COMPASS_RESIZE_INDICATOR; 3550 } else { 3551 return PlanComponent.WALL_AND_LINE_RESIZE_INDICATOR; 3552 } 3553 } else if (PlanComponent.IndicatorType.ROTATE === indicatorType) { 3554 if (item instanceof HomePieceOfFurniture) { 3555 return PlanComponent.FURNITURE_ROTATION_INDICATOR; 3556 } else if (item instanceof Compass) { 3557 return PlanComponent.COMPASS_ROTATION_INDICATOR; 3558 } else if (item instanceof Camera) { 3559 return PlanComponent.CAMERA_YAW_ROTATION_INDICATOR; 3560 } else if (item instanceof DimensionLine) { 3561 return PlanComponent.DIMENSION_LINE_HEIGHT_ROTATION_INDICATOR; 3562 } 3563 } else if (PlanComponent.IndicatorType.ELEVATE === indicatorType) { 3564 if (item instanceof Camera) { 3565 return PlanComponent.CAMERA_ELEVATION_INDICATOR; 3566 } else { 3567 return PlanComponent.ELEVATION_INDICATOR; 3568 } 3569 } else if (PlanComponent.IndicatorType.RESIZE_HEIGHT === indicatorType) { 3570 if (item instanceof HomePieceOfFurniture) { 3571 return PlanComponent.FURNITURE_HEIGHT_INDICATOR; 3572 } else if (item instanceof DimensionLine) { 3573 return PlanComponent.DIMENSION_LINE_HEIGHT_INDICATOR; 3574 } 3575 } else if (PlanComponent.IndicatorType.CHANGE_POWER === indicatorType) { 3576 if (item instanceof HomeLight) { 3577 return PlanComponent.LIGHT_POWER_INDICATOR; 3578 } 3579 } else if (PlanComponent.IndicatorType.MOVE_TEXT === indicatorType) { 3580 return PlanComponent.TEXT_LOCATION_INDICATOR; 3581 } else if (PlanComponent.IndicatorType.ROTATE_TEXT === indicatorType) { 3582 return PlanComponent.TEXT_ANGLE_INDICATOR; 3583 } else if (PlanComponent.IndicatorType.ROTATE_PITCH === indicatorType) { 3584 if (item instanceof HomePieceOfFurniture) { 3585 return PlanComponent.FURNITURE_PITCH_ROTATION_INDICATOR; 3586 } else if (item instanceof Camera) { 3587 return PlanComponent.CAMERA_PITCH_ROTATION_INDICATOR; 3588 } 3589 } else if (PlanComponent.IndicatorType.ROTATE_ROLL === indicatorType) { 3590 if (item instanceof HomePieceOfFurniture) { 3591 return PlanComponent.FURNITURE_ROLL_ROTATION_INDICATOR; 3592 } 3593 } else if (PlanComponent.IndicatorType.ARC_EXTENT === indicatorType) { 3594 if (item instanceof Wall) { 3595 return PlanComponent.WALL_ARC_EXTENT_INDICATOR; 3596 } 3597 } 3598 return null; 3599 } 3600 3601 /** 3602 * Paints name indicator on <code>room</code>. 3603 * @param {Graphics2D} g2D 3604 * @param {Room} room 3605 * @param {string|CanvasPattern} indicatorPaint 3606 * @param {number} planScale 3607 * @private 3608 */ 3609 PlanComponent.prototype.paintRoomNameOffsetIndicator = function(g2D, room, indicatorPaint, planScale) { 3610 if (this.resizeIndicatorVisible 3611 && room.getName() != null 3612 && room.getName().trim().length > 0) { 3613 var xName = room.getXCenter() + room.getNameXOffset(); 3614 var yName = room.getYCenter() + room.getNameYOffset(); 3615 this.paintTextIndicators(g2D, room, this.getLineCount(room.getName()), 3616 room.getNameStyle(), xName, yName, room.getNameAngle(), indicatorPaint, planScale); 3617 } 3618 } 3619 3620 /** 3621 * Paints resize indicator on <code>room</code>. 3622 * @param {Graphics2D} g2D 3623 * @param {Room} room 3624 * @param {string|CanvasPattern} indicatorPaint 3625 * @param {number} planScale 3626 * @private 3627 */ 3628 PlanComponent.prototype.paintRoomAreaOffsetIndicator = function(g2D, room, indicatorPaint, planScale) { 3629 if (this.resizeIndicatorVisible 3630 && room.isAreaVisible() 3631 && room.getArea() > 0.01) { 3632 var xArea = room.getXCenter() + room.getAreaXOffset(); 3633 var yArea = room.getYCenter() + room.getAreaYOffset(); 3634 this.paintTextIndicators(g2D, room, 1, room.getAreaStyle(), xArea, yArea, room.getAreaAngle(), 3635 indicatorPaint, planScale); 3636 } 3637 } 3638 3639 /** 3640 * Paints text location and angle indicators at the given coordinates. 3641 * @param {Graphics2D} g2D 3642 * @param {Object} selectableObject 3643 * @param {number} lineCount 3644 * @param {TextStyle} style 3645 * @param {number} x 3646 * @param {number} y 3647 * @param {number} angle 3648 * @param {string|CanvasPattern} indicatorPaint 3649 * @param {number} planScale 3650 * @private 3651 */ 3652 PlanComponent.prototype.paintTextIndicators = function(g2D, selectableObject, lineCount, style, x, y, angle, indicatorPaint, planScale) { 3653 if (this.resizeIndicatorVisible) { 3654 g2D.setPaint(indicatorPaint); 3655 g2D.setStroke(PlanComponent.INDICATOR_STROKE); 3656 var previousTransform = g2D.getTransform(); 3657 var scaleInverse = 1 / planScale; 3658 g2D.translate(x, y); 3659 g2D.rotate(angle); 3660 g2D.scale(scaleInverse, scaleInverse); 3661 if (selectableObject instanceof Label) { 3662 g2D.draw(PlanComponent.LABEL_CENTER_INDICATOR); 3663 } else { 3664 g2D.draw(this.getIndicator(null, PlanComponent.IndicatorType.MOVE_TEXT)); 3665 } 3666 if (style == null) { 3667 style = this.preferences.getDefaultTextStyle(selectableObject); 3668 } 3669 var fontMetrics = this.getFontMetrics(g2D.getFont(), style); 3670 g2D.setTransform(previousTransform); 3671 g2D.translate(x, y); 3672 g2D.rotate(angle); 3673 g2D.translate(0, -fontMetrics.getHeight() * (lineCount - 1) 3674 - fontMetrics.getAscent() * (selectableObject instanceof Label ? 1 : 0.85)); 3675 g2D.scale(scaleInverse, scaleInverse); 3676 g2D.draw(this.getIndicator(null, PlanComponent.IndicatorType.ROTATE_TEXT)); 3677 g2D.setTransform(previousTransform); 3678 } 3679 } 3680 3681 /** 3682 * Returns the number of lines in the given <code>text</code> ignoring trailing line returns. 3683 * @param {string} text 3684 * @return {number} 3685 * @private 3686 */ 3687 PlanComponent.prototype.getLineCount = function(text) { 3688 var lineCount = 1; 3689 var i = text.length - 1; 3690 while (i >= 0 && text.charAt(i) == '\n') { 3691 i--; 3692 } 3693 for ( ; i >= 0; i--) { 3694 if (text.charAt(i) == '\n') { 3695 lineCount++; 3696 } 3697 } 3698 return lineCount; 3699 } 3700 3701 /** 3702 * Paints walls. 3703 * @param {Graphics2D} g2D 3704 * @param {Object[]} selectedItems 3705 * @param {Level} level 3706 * @param {number} planScale 3707 * @param {string} backgroundColor 3708 * @param {string} foregroundColor 3709 * @param {PlanComponent.PaintMode} paintMode 3710 * @private 3711 */ 3712 PlanComponent.prototype.paintWalls = function(g2D, selectedItems, level, planScale, backgroundColor, foregroundColor, paintMode) { 3713 var paintedWalls; 3714 var wallAreas; 3715 if (paintMode !== PlanComponent.PaintMode.CLIPBOARD) { 3716 wallAreas = this.getWallAreasAtLevel(level); 3717 } else { 3718 paintedWalls = Home.getWallsSubList(selectedItems); 3719 wallAreas = this.getWallAreas(this.getDrawableWallsAtLevel(paintedWalls, level)); 3720 } 3721 var wallPaintScale = paintMode === PlanComponent.PaintMode.PRINT 3722 ? planScale / 72 * 150 3723 : planScale; 3724 var oldComposite = null; 3725 if (paintMode === PlanComponent.PaintMode.PAINT 3726 && this.backgroundPainted 3727 && this.backgroundImageCache != null 3728 && this.wallsDoorsOrWindowsModification) { 3729 oldComposite = this.setTransparency(g2D, 0.5); 3730 } 3731 3732 var areaEntries = wallAreas.entries == null ? [] : wallAreas.entries; // Parse entrySet 3733 for (var i = 0; i < areaEntries.length; i++) { 3734 var areaEntry = areaEntries[i]; 3735 var wallPattern = areaEntry.getKey() [0].getPattern(); 3736 this.fillAndDrawWallsArea(g2D, areaEntry.getValue(), planScale, 3737 this.getWallPaint(g2D, wallPaintScale, backgroundColor, foregroundColor, 3738 wallPattern != null ? wallPattern : this.preferences.getWallPattern()), foregroundColor, paintMode); 3739 } 3740 if (oldComposite != null) { 3741 g2D.setAlpha(oldComposite); 3742 } 3743 } 3744 3745 /** 3746 * Fills and paints the given area. 3747 * @param {Graphics2D} g2D 3748 * @param {java.awt.geom.Area} area 3749 * @param {number} planScale 3750 * @param {string|CanvasPattern} fillPaint 3751 * @param {string|CanvasPattern} drawPaint 3752 * @param {PlanComponent.PaintMode} paintMode 3753 * @private 3754 */ 3755 PlanComponent.prototype.fillAndDrawWallsArea = function(g2D, area, planScale, fillPaint, drawPaint, paintMode) { 3756 g2D.setPaint(fillPaint); 3757 var patternScale = 1 / planScale; 3758 g2D.scale(patternScale, patternScale); 3759 var filledArea = area.clone(); 3760 filledArea.transform(java.awt.geom.AffineTransform.getScaleInstance(1 / patternScale, 1 / patternScale)); 3761 this.fillShape(g2D, filledArea, paintMode); 3762 g2D.scale(1 / patternScale, 1 / patternScale); 3763 g2D.setPaint(drawPaint); 3764 g2D.setStroke(new java.awt.BasicStroke(this.getStrokeWidth(Wall, paintMode) / planScale)); 3765 g2D.draw(area); 3766 } 3767 3768 /** 3769 * Paints the outline of walls among <code>items</code> and a resize indicator if 3770 * <code>items</code> contains only one wall and indicator paint isn't <code>null</code>. 3771 * @param {Graphics2D} g2D 3772 * @param {Object[]} items 3773 * @param {Level} level 3774 * @param {string|CanvasPattern} selectionOutlinePaint 3775 * @param {java.awt.BasicStroke} selectionOutlineStroke 3776 * @param {string|CanvasPattern} indicatorPaint 3777 * @param {number} planScale 3778 * @param {string} foregroundColor 3779 * @private 3780 */ 3781 PlanComponent.prototype.paintWallsOutline = function(g2D, items, level, selectionOutlinePaint, selectionOutlineStroke, indicatorPaint, planScale, foregroundColor) { 3782 var scaleInverse = 1 / planScale; 3783 var walls = Home.getWallsSubList(items); 3784 var previousTransform = g2D.getTransform(); 3785 for (var i = 0; i < walls.length; i++) { 3786 var wall = walls[i]; 3787 if (this.isViewableAtLevel(wall, level)) { 3788 g2D.setPaint(selectionOutlinePaint); 3789 g2D.setStroke(selectionOutlineStroke); 3790 g2D.draw(ShapeTools.getShape(wall.getPoints(), true, null)); 3791 3792 if (indicatorPaint != null) { 3793 g2D.translate(wall.getXStart(), wall.getYStart()); 3794 g2D.scale(scaleInverse, scaleInverse); 3795 g2D.setPaint(indicatorPaint); 3796 g2D.setStroke(PlanComponent.POINT_STROKE); 3797 g2D.fill(PlanComponent.WALL_POINT); 3798 3799 var arcExtent = wall.getArcExtent(); 3800 var indicatorAngle = void 0; 3801 var distanceAtScale = void 0; 3802 var xArcCircleCenter = 0; 3803 var yArcCircleCenter = 0; 3804 var arcCircleRadius = 0; 3805 var startPointToEndPointDistance = wall.getStartPointToEndPointDistance(); 3806 var wallAngle = Math.atan2(wall.getYEnd() - wall.getYStart(), 3807 wall.getXEnd() - wall.getXStart()); 3808 if (arcExtent != null && arcExtent !== 0) { 3809 xArcCircleCenter = wall.getXArcCircleCenter(); 3810 yArcCircleCenter = wall.getYArcCircleCenter(); 3811 arcCircleRadius = java.awt.geom.Point2D.distance(wall.getXStart(), wall.getYStart(), 3812 xArcCircleCenter, yArcCircleCenter); 3813 distanceAtScale = arcCircleRadius * Math.abs(arcExtent) * planScale; 3814 indicatorAngle = Math.atan2(yArcCircleCenter - wall.getYStart(), 3815 xArcCircleCenter - wall.getXStart()) 3816 + (arcExtent > 0 ? -Math.PI / 2 : Math.PI / 2); 3817 } else { 3818 distanceAtScale = startPointToEndPointDistance * planScale; 3819 indicatorAngle = wallAngle; 3820 } 3821 if (distanceAtScale < 30) { 3822 g2D.rotate(wallAngle); 3823 if (arcExtent != null) { 3824 var wallToStartPointArcCircleCenterAngle = Math.abs(arcExtent) > Math.PI 3825 ? -(Math.PI + arcExtent) / 2 3826 : (Math.PI - arcExtent) / 2; 3827 var arcCircleCenterToWallDistance = (Math.tan(wallToStartPointArcCircleCenterAngle) 3828 * startPointToEndPointDistance / 2); 3829 g2D.translate(startPointToEndPointDistance * planScale / 2, 3830 (arcCircleCenterToWallDistance - arcCircleRadius * (Math.abs(wallAngle) > Math.PI / 2 ? -1 : 1)) * planScale); 3831 } else { 3832 g2D.translate(distanceAtScale / 2, 0); 3833 } 3834 } else { 3835 g2D.rotate(indicatorAngle); 3836 g2D.translate(8, 0); 3837 } 3838 g2D.draw(PlanComponent.WALL_ORIENTATION_INDICATOR); 3839 g2D.setTransform(previousTransform); 3840 g2D.translate(wall.getXEnd(), wall.getYEnd()); 3841 g2D.scale(scaleInverse, scaleInverse); 3842 g2D.fill(PlanComponent.WALL_POINT); 3843 if (distanceAtScale >= 30) { 3844 if (arcExtent != null) { 3845 indicatorAngle += arcExtent; 3846 } 3847 g2D.rotate(indicatorAngle); 3848 g2D.translate(-10, 0); 3849 g2D.draw(PlanComponent.WALL_ORIENTATION_INDICATOR); 3850 } 3851 g2D.setTransform(previousTransform); 3852 } 3853 } 3854 } 3855 g2D.setPaint(foregroundColor); 3856 g2D.setStroke(new java.awt.BasicStroke(this.getStrokeWidth(Wall, PlanComponent.PaintMode.PAINT) / planScale)); 3857 var areas = CoreTools.valuesFromMap(this.getWallAreas(this.getDrawableWallsAtLevel(walls, level))); 3858 for (var i = 0; i < areas.length; i++) { 3859 g2D.draw(areas[i]); 3860 } 3861 if (items.length === 1 3862 && walls.length === 1 3863 && indicatorPaint != null) { 3864 var wall = walls[0]; 3865 if (this.isViewableAtLevel(wall, level)) { 3866 this.paintWallResizeIndicators(g2D, wall, indicatorPaint, planScale); 3867 } 3868 } 3869 } 3870 3871 /** 3872 * Returns <code>true</code> if the given item can be viewed in the plan at a level. 3873 * @param {Object} item 3874 * @param {Level} level 3875 * @return {boolean} 3876 */ 3877 PlanComponent.prototype.isViewableAtLevel = function(item, level) { 3878 var itemLevel = item.getLevel(); 3879 return itemLevel == null 3880 || (itemLevel.isViewable() 3881 && item.isAtLevel(level)); 3882 } 3883 3884 /** 3885 * Returns <code>true</code> if the given item can be viewed in the plan at the selected level. 3886 * @deprecated Override {@link #isViewableAtLevel} if you want to print different levels 3887 * @param {Object} item 3888 * @return {boolean} 3889 */ 3890 PlanComponent.prototype.isViewableAtSelectedLevel = function(item) { 3891 return this.isViewableAtLevel(item, this.home.getSelectedLevel()); 3892 } 3893 3894 /** 3895 * Paints resize indicators on <code>wall</code>. 3896 * @param {Graphics2D} g2D 3897 * @param {Wall} wall 3898 * @param {string|CanvasPattern} indicatorPaint 3899 * @param {number} planScale 3900 * @private 3901 */ 3902 PlanComponent.prototype.paintWallResizeIndicators = function(g2D, wall, indicatorPaint, planScale) { 3903 if (this.resizeIndicatorVisible) { 3904 g2D.setPaint(indicatorPaint); 3905 g2D.setStroke(PlanComponent.INDICATOR_STROKE); 3906 var previousTransform = g2D.getTransform(); 3907 var scaleInverse = 1 / planScale; 3908 var wallPoints = wall.getPoints(); 3909 var leftSideMiddlePointIndex = (wallPoints.length / 4 | 0); 3910 var wallAngle = Math.atan2(wall.getYEnd() - wall.getYStart(), 3911 wall.getXEnd() - wall.getXStart()); 3912 3913 if (wallPoints.length % 4 === 0) { 3914 g2D.translate((wallPoints[leftSideMiddlePointIndex - 1][0] + wallPoints[leftSideMiddlePointIndex][0]) / 2, 3915 (wallPoints[leftSideMiddlePointIndex - 1][1] + wallPoints[leftSideMiddlePointIndex][1]) / 2); 3916 } else { 3917 g2D.translate(wallPoints[leftSideMiddlePointIndex][0], wallPoints[leftSideMiddlePointIndex][1]); 3918 } 3919 g2D.scale(scaleInverse, scaleInverse); 3920 g2D.rotate(wallAngle + Math.PI); 3921 g2D.draw(this.getIndicator(wall, PlanComponent.IndicatorType.ARC_EXTENT)); 3922 g2D.setTransform(previousTransform); 3923 3924 var arcExtent = wall.getArcExtent(); 3925 var indicatorAngle = void 0; 3926 if (arcExtent != null && arcExtent !== 0) { 3927 indicatorAngle = Math.atan2(wall.getYArcCircleCenter() - wall.getYEnd(), 3928 wall.getXArcCircleCenter() - wall.getXEnd()) 3929 + (arcExtent > 0 ? -Math.PI / 2 : Math.PI / 2); 3930 } else { 3931 indicatorAngle = wallAngle; 3932 } 3933 3934 g2D.translate(wall.getXEnd(), wall.getYEnd()); 3935 g2D.scale(scaleInverse, scaleInverse); 3936 g2D.rotate(indicatorAngle); 3937 g2D.draw(this.getIndicator(wall, PlanComponent.IndicatorType.RESIZE)); 3938 g2D.setTransform(previousTransform); 3939 3940 if (arcExtent != null) { 3941 indicatorAngle += Math.PI - arcExtent; 3942 } else { 3943 indicatorAngle += Math.PI; 3944 } 3945 3946 g2D.translate(wall.getXStart(), wall.getYStart()); 3947 g2D.scale(scaleInverse, scaleInverse); 3948 g2D.rotate(indicatorAngle); 3949 g2D.draw(this.getIndicator(wall, PlanComponent.IndicatorType.RESIZE)); 3950 g2D.setTransform(previousTransform); 3951 } 3952 } 3953 3954 /** 3955 * Returns areas matching the union of home wall shapes sorted by pattern. 3956 * @param {Level} level 3957 * @return {Object} 3958 * @private 3959 */ 3960 PlanComponent.prototype.getWallAreasAtLevel = function(level) { 3961 if (this.wallAreasCache == null) { 3962 this.wallAreasCache = this.getWallAreas(this.getDrawableWallsAtLevel(this.home.getWalls(), level)); 3963 } 3964 return this.wallAreasCache; 3965 } 3966 3967 /** 3968 * Returns the walls that belong to the given <code>level</code> in home. 3969 * @param {Wall[]} walls 3970 * @param {Level} level 3971 * @return {Wall[]} 3972 * @private 3973 */ 3974 PlanComponent.prototype.getDrawableWallsAtLevel = function(walls, level) { 3975 var wallsAtLevel = []; 3976 for (var i = 0; i < walls.length; i++) { 3977 var wall = walls[i]; 3978 if (this.isViewableAtLevel(wall, level)) { 3979 wallsAtLevel.push(wall); 3980 } 3981 } 3982 return wallsAtLevel; 3983 } 3984 3985 /** 3986 * Returns areas matching the union of <code>walls</code> shapes sorted by pattern. 3987 * @param {Wall[]} walls 3988 * @return {Object} Map<Collection<Wall>, Area> 3989 * @private 3990 */ 3991 PlanComponent.prototype.getWallAreas = function(walls) { 3992 var plan = this; 3993 if (walls.length === 0) { 3994 return {}; 3995 } 3996 var pattern = walls[0].getPattern(); 3997 var samePattern = true; 3998 for (var i = 0; i < walls.length; i++) { 3999 if (pattern !== walls[i].getPattern()) { 4000 samePattern = false; 4001 break; 4002 } 4003 } 4004 var wallAreas = {}; 4005 if (samePattern) { 4006 CoreTools.putToMap(wallAreas, walls, this.getItemsArea(walls)); 4007 } else { 4008 var sortedWalls = {}; // LinkedHashMap 4009 walls.forEach(function(wall) { 4010 var wallPattern = wall.getPattern(); 4011 if (wallPattern == null) { 4012 wallPattern = plan.preferences.getWallPattern(); 4013 } 4014 var patternWalls = CoreTools.getFromMap(sortedWalls, wallPattern); 4015 if (patternWalls == null) { 4016 patternWalls = []; 4017 CoreTools.putToMap(sortedWalls, wallPattern, patternWalls); 4018 } 4019 patternWalls.push(wall); 4020 }); 4021 4022 var walls = CoreTools.valuesFromMap(sortedWalls); 4023 for (var i = 0; i < walls.length; i++) { 4024 var patternWalls = walls[i]; 4025 CoreTools.putToMap(wallAreas, patternWalls, this.getItemsArea(patternWalls)); 4026 } 4027 } 4028 return wallAreas; 4029 } 4030 4031 /** 4032 * Returns an area matching the union of all <code>items</code> shapes. 4033 * @param {Bound[]} items 4034 * @return {java.awt.geom.Area} 4035 * @private 4036 */ 4037 PlanComponent.prototype.getItemsArea = function(items) { 4038 var itemsArea = new java.awt.geom.Area(); 4039 items.forEach(function(item) { 4040 itemsArea.add(new java.awt.geom.Area(ShapeTools.getShape(item.getPoints(), true, null))); 4041 }); 4042 return itemsArea; 4043 } 4044 4045 /** 4046 * Modifies the pattern image to substitute the transparent color with backgroundColor and the black color with the foregroundColor. 4047 * @param {HTMLImageElement} image the orginal pattern image (black over transparent background) 4048 * @param {string} foregroundColor the foreground color 4049 * @param {string} backgroundColor the background color 4050 * @return {HTMLImageElement} the final pattern image with the substituted colors 4051 * @private 4052 */ 4053 PlanComponent.prototype.makePatternImage = function(image, foregroundColor, backgroundColor) { 4054 var canvas = document.createElement("canvas"); 4055 canvas.width = image.naturalWidth; 4056 canvas.height = image.naturalHeight; 4057 var context = canvas.getContext("2d"); 4058 context.fillStyle = "#FFFFFF"; 4059 context.fillRect(0, 0, canvas.width, canvas.height); 4060 context.drawImage(image, 0, 0); 4061 var imageData = context.getImageData(0, 0, image.naturalWidth, image.height).data; 4062 var bgColor = ColorTools.hexadecimalStringToInteger(backgroundColor); 4063 var fgColor = ColorTools.hexadecimalStringToInteger(foregroundColor); 4064 var updatedImageData = context.createImageData(image.naturalWidth, image.height); 4065 for (var i = 0; i < imageData.length; i += 4) { 4066 updatedImageData.data[i + 3] = 0xFF; 4067 if (imageData[i] === 0xFF && imageData[i + 1] === 0xFF && imageData[i + 2] === 0xFF) { 4068 // Change white pixels to background color 4069 updatedImageData.data[i] = (bgColor & 0xFF0000) >> 16; 4070 updatedImageData.data[i + 1] = (bgColor & 0xFF00) >> 8; 4071 updatedImageData.data[i + 2] = bgColor & 0xFF; 4072 } else if (imageData[i] === 0 && imageData[i + 1] === 0 && imageData[i + 2] === 0) { 4073 // Change black pixels to foreground color 4074 updatedImageData.data[i] = (fgColor & 0xFF0000) >> 16; 4075 updatedImageData.data[i + 1] = (fgColor & 0xFF00) >> 8; 4076 updatedImageData.data[i + 2] = fgColor & 0xFF; 4077 } else { 4078 // Change color mixing foreground and background color 4079 var percent = 1 - (imageData[i] + imageData[i + 1] + imageData[i + 2]) / 3. / 0xFF; 4080 updatedImageData.data[i] = Math.min(0xFF, ((fgColor & 0xFF0000) >> 16) * percent + ((bgColor & 0xFF0000) >> 16) * (1 - percent)); 4081 updatedImageData.data[i + 1] = Math.min(0xFF, ((fgColor & 0xFF00) >> 8) * percent + ((bgColor & 0xFF00) >> 8) * (1 - percent)); 4082 updatedImageData.data[i + 2] = Math.min(0xFF, (fgColor & 0xFF) * percent + (bgColor & 0xFF) * (1 - percent)); 4083 } 4084 } 4085 context.putImageData(updatedImageData, 0, 0); 4086 image.src = canvas.toDataURL(); 4087 return image; 4088 } 4089 4090 /** 4091 * Returns the <code>Paint</code> object used to fill walls. 4092 * @param {Graphics2D} g2D 4093 * @param {number} planScale 4094 * @param {string} backgroundColor 4095 * @param {string} foregroundColor 4096 * @param {TextureImage} wallPattern 4097 * @return {Object} 4098 * @private 4099 */ 4100 PlanComponent.prototype.getWallPaint = function(g2D, planScale, backgroundColor, foregroundColor, wallPattern) { 4101 var plan = this; 4102 var patternImage = this.patternImagesCache[wallPattern.getImage().getURL()]; 4103 if (patternImage == null 4104 || backgroundColor != this.wallsPatternBackgroundCache 4105 || foregroundColor != this.wallsPatternForegroundCache) { 4106 patternImage = TextureManager.getInstance().getWaitImage(); 4107 this.patternImagesCache[wallPattern.getImage().getURL()] = patternImage; 4108 TextureManager.getInstance().loadTexture(wallPattern.getImage(), false, { 4109 textureUpdated: function(image) { 4110 plan.patternImagesCache[wallPattern.getImage().getURL()] = plan.makePatternImage(image, plan.getForeground(), plan.getBackground()); 4111 plan.repaint(); 4112 }, 4113 textureError: function() { 4114 plan.patternImagesCache[wallPattern.getImage().getURL()] = PlanComponent.ERROR_TEXTURE_IMAGE; 4115 }, 4116 progression: function() { } 4117 }); 4118 this.wallsPatternBackgroundCache = backgroundColor; 4119 this.wallsPatternForegroundCache = foregroundColor; 4120 } 4121 return g2D.createPattern(patternImage); 4122 } 4123 4124 /** 4125 * Paints home furniture. 4126 * @param {Graphics2D} g2D 4127 * @param {HomePieceOfFurniture[]} furniture 4128 * @param {Bound[]} selectedItems 4129 * @param {Level} level 4130 * @param {number} planScale 4131 * @param {string} backgroundColor 4132 * @param {string} foregroundColor 4133 * @param {string} furnitureOutlineColor 4134 * @param {PlanComponent.PaintMode} paintMode 4135 * @param {boolean} paintIcon 4136 * @private 4137 */ 4138 PlanComponent.prototype.paintFurniture = function(g2D, furniture, selectedItems, level, planScale, backgroundColor, foregroundColor, furnitureOutlineColor, paintMode, paintIcon) { 4139 if (!(furniture.length == 0)) { 4140 var pieceBorderStroke = new java.awt.BasicStroke(this.getStrokeWidth(HomePieceOfFurniture, paintMode) / planScale); 4141 var allFurnitureViewedFromTop = null; 4142 for (var i = 0; i < furniture.length; i++) { 4143 var piece = furniture[i]; 4144 if (piece.isVisible()) { 4145 var selectedPiece = (selectedItems.indexOf((piece)) >= 0); 4146 if (piece instanceof HomeFurnitureGroup) { 4147 var groupFurniture = (piece).getFurniture(); 4148 var emptyList = []; 4149 this.paintFurniture(g2D, groupFurniture, 4150 selectedPiece 4151 ? groupFurniture 4152 : emptyList, level, 4153 planScale, backgroundColor, foregroundColor, 4154 furnitureOutlineColor, paintMode, paintIcon); 4155 } else if (paintMode !== PlanComponent.PaintMode.CLIPBOARD || selectedPiece) { 4156 var pieceShape = ShapeTools.getShape(piece.getPoints(), true, null); 4157 var pieceShape2D = void 0; 4158 if (piece instanceof HomeDoorOrWindow) { 4159 var doorOrWindow = piece; 4160 pieceShape2D = this.getDoorOrWindowWallPartShape(doorOrWindow); 4161 if (this.draggedItemsFeedback == null 4162 || !(this.draggedItemsFeedback.indexOf((piece)) >= 0)) { 4163 this.paintDoorOrWindowWallThicknessArea(g2D, doorOrWindow, planScale, backgroundColor, foregroundColor, paintMode); 4164 } 4165 this.paintDoorOrWindowSashes(g2D, doorOrWindow, planScale, foregroundColor, paintMode); 4166 } else { 4167 pieceShape2D = pieceShape; 4168 } 4169 4170 var viewedFromTop = void 0; 4171 if (this.preferences.isFurnitureViewedFromTop()) { 4172 if (piece.getPlanIcon() != null 4173 || (piece instanceof HomeDoorOrWindow)) { 4174 viewedFromTop = true; 4175 } else { 4176 allFurnitureViewedFromTop = PlanComponent.WEBGL_AVAILABLE; 4177 viewedFromTop = allFurnitureViewedFromTop; 4178 } 4179 } else { 4180 viewedFromTop = false; 4181 } 4182 if (paintIcon && viewedFromTop) { 4183 if (piece instanceof HomeDoorOrWindow) { 4184 g2D.setPaint(backgroundColor); 4185 g2D.fill(pieceShape2D); 4186 g2D.setPaint(foregroundColor); 4187 g2D.setStroke(pieceBorderStroke); 4188 g2D.draw(pieceShape2D); 4189 } else { 4190 this.paintPieceOfFurnitureTop(g2D, piece, pieceShape2D, pieceBorderStroke, planScale, 4191 backgroundColor, foregroundColor, paintMode); 4192 } 4193 if (paintMode === PlanComponent.PaintMode.PAINT) { 4194 g2D.setStroke(pieceBorderStroke); 4195 g2D.setPaint(furnitureOutlineColor); 4196 g2D.draw(pieceShape); 4197 } 4198 } else { 4199 if (paintIcon) { 4200 this.paintPieceOfFurnitureIcon(g2D, piece, null, pieceShape2D, planScale, 4201 backgroundColor, paintMode); 4202 } 4203 g2D.setPaint(foregroundColor); 4204 g2D.setStroke(pieceBorderStroke); 4205 g2D.draw(pieceShape2D); 4206 if ((piece instanceof HomeDoorOrWindow) 4207 && paintMode === PlanComponent.PaintMode.PAINT) { 4208 g2D.setPaint(furnitureOutlineColor); 4209 g2D.draw(pieceShape); 4210 } 4211 } 4212 } 4213 } 4214 } 4215 } 4216 } 4217 4218 /** 4219 * Returns the shape of the wall part of a door or a window. 4220 * @param {HomeDoorOrWindow} doorOrWindow 4221 * @return {Object} 4222 * @private 4223 */ 4224 PlanComponent.prototype.getDoorOrWindowWallPartShape = function(doorOrWindow) { 4225 var doorOrWindowWallPartRectangle = this.getDoorOrWindowRectangle(doorOrWindow, true); 4226 var rotation = java.awt.geom.AffineTransform.getRotateInstance( 4227 doorOrWindow.getAngle(), doorOrWindow.getX(), doorOrWindow.getY()); 4228 var it = doorOrWindowWallPartRectangle.getPathIterator(rotation); 4229 var doorOrWindowWallPartShape = new java.awt.geom.GeneralPath(); 4230 doorOrWindowWallPartShape.append(it, false); 4231 return doorOrWindowWallPartShape; 4232 } 4233 4234 /** 4235 * Returns the rectangle of a door or a window. 4236 * @param {HomeDoorOrWindow} doorOrWindow 4237 * @param {boolean} onlyWallPart 4238 * @return {java.awt.geom.Rectangle2D} 4239 * @private 4240 */ 4241 PlanComponent.prototype.getDoorOrWindowRectangle = function(doorOrWindow, onlyWallPart) { 4242 var wallThickness = doorOrWindow.getDepth() * (onlyWallPart ? doorOrWindow.getWallThickness() : 1); 4243 var wallDistance = doorOrWindow.getDepth() * (onlyWallPart ? doorOrWindow.getWallDistance() : 0); 4244 var cutOutShape = doorOrWindow.getCutOutShape(); 4245 var width = doorOrWindow.getWidth(); 4246 var wallWidth = doorOrWindow.getWallWidth() * width; 4247 var x = doorOrWindow.getX() - width / 2; 4248 x += doorOrWindow.isModelMirrored() 4249 ? (1 - doorOrWindow.getWallLeft() - doorOrWindow.getWallWidth()) * width 4250 : doorOrWindow.getWallLeft() * width; 4251 if (cutOutShape != null 4252 && PieceOfFurniture.DEFAULT_CUT_OUT_SHAPE != cutOutShape) { 4253 var shape = ShapeTools.getShape(cutOutShape); 4254 var bounds = shape.getBounds2D(); 4255 if (doorOrWindow.isModelMirrored()) { 4256 x += (1 - bounds.getX() - bounds.getWidth()) * wallWidth; 4257 } else { 4258 x += bounds.getX() * wallWidth; 4259 } 4260 wallWidth *= bounds.getWidth(); 4261 } 4262 var doorOrWindowWallPartRectangle = new java.awt.geom.Rectangle2D.Float( 4263 x, doorOrWindow.getY() - doorOrWindow.getDepth() / 2 + wallDistance, 4264 wallWidth, wallThickness); 4265 return doorOrWindowWallPartRectangle; 4266 } 4267 4268 /** 4269 * Paints the shape of a door or a window in the thickness of the wall it intersects. 4270 * @param {Graphics2D} g2D 4271 * @param {HomeDoorOrWindow} doorOrWindow 4272 * @param {number} planScale 4273 * @param {string} backgroundColor 4274 * @param {string} foregroundColor 4275 * @param {PlanComponent.PaintMode} paintMode 4276 * @private 4277 */ 4278 PlanComponent.prototype.paintDoorOrWindowWallThicknessArea = function(g2D, doorOrWindow, planScale, backgroundColor, foregroundColor, paintMode) { 4279 if (doorOrWindow.isWallCutOutOnBothSides()) { 4280 var doorOrWindowWallArea = null; 4281 if (this.doorOrWindowWallThicknessAreasCache != null) { 4282 doorOrWindowWallArea = CoreTools.getFromMap(this.doorOrWindowWallThicknessAreasCache, doorOrWindow); 4283 } 4284 4285 if (doorOrWindowWallArea == null) { 4286 var doorOrWindowRectangle = this.getDoorOrWindowRectangle(doorOrWindow, false); 4287 var rotation = java.awt.geom.AffineTransform.getRotateInstance( 4288 doorOrWindow.getAngle(), doorOrWindow.getX(), doorOrWindow.getY()); 4289 var it = doorOrWindowRectangle.getPathIterator(rotation); 4290 var doorOrWindowWallPartShape = new java.awt.geom.GeneralPath(); 4291 doorOrWindowWallPartShape.append(it, false); 4292 var doorOrWindowWallPartArea = new java.awt.geom.Area(doorOrWindowWallPartShape); 4293 4294 doorOrWindowWallArea = new java.awt.geom.Area(); 4295 var walls = this.home.getWalls(); 4296 for (var i = 0; i < walls.length; i++) { 4297 var wall = walls[i]; 4298 if (wall.isAtLevel(doorOrWindow.getLevel()) 4299 && doorOrWindow.isParallelToWall(wall)) { 4300 var wallShape = ShapeTools.getShape(wall.getPoints(), true, null); 4301 var wallArea = new java.awt.geom.Area(wallShape); 4302 wallArea.intersect(doorOrWindowWallPartArea); 4303 if (!wallArea.isEmpty()) { 4304 var doorOrWindowExtendedRectangle = new java.awt.geom.Rectangle2D.Float( 4305 doorOrWindowRectangle.getX(), 4306 doorOrWindowRectangle.getY() - 2 * wall.getThickness(), 4307 doorOrWindowRectangle.getWidth(), 4308 doorOrWindowRectangle.getWidth() + 4 * wall.getThickness()); 4309 it = doorOrWindowExtendedRectangle.getPathIterator(rotation); 4310 var path = new java.awt.geom.GeneralPath(); 4311 path.append(it, false); 4312 wallArea = new java.awt.geom.Area(wallShape); 4313 wallArea.intersect(new java.awt.geom.Area(path)); 4314 doorOrWindowWallArea.add(wallArea); 4315 } 4316 } 4317 } 4318 } 4319 4320 if (this.doorOrWindowWallThicknessAreasCache == null) { 4321 this.doorOrWindowWallThicknessAreasCache = {}; 4322 } 4323 CoreTools.putToMap(this.doorOrWindowWallThicknessAreasCache, doorOrWindow, doorOrWindowWallArea); 4324 4325 g2D.setPaint(backgroundColor); 4326 g2D.fill(doorOrWindowWallArea); 4327 g2D.setPaint(foregroundColor); 4328 g2D.setStroke(new java.awt.BasicStroke(this.getStrokeWidth(HomePieceOfFurniture, paintMode) / planScale)); 4329 g2D.draw(doorOrWindowWallArea); 4330 } 4331 } 4332 4333 /** 4334 * Paints the sashes of a door or a window. 4335 * @param {Graphics2D} g2D 4336 * @param {HomeDoorOrWindow} doorOrWindow 4337 * @param {number} planScale 4338 * @param {string} foregroundColor 4339 * @param {PlanComponent.PaintMode} paintMode 4340 * @private 4341 */ 4342 PlanComponent.prototype.paintDoorOrWindowSashes = function(g2D, doorOrWindow, planScale, foregroundColor, paintMode) { 4343 var sashBorderStroke = new java.awt.BasicStroke(this.getStrokeWidth(HomePieceOfFurniture, paintMode) / planScale); 4344 g2D.setPaint(foregroundColor); 4345 g2D.setStroke(sashBorderStroke); 4346 var sashes = doorOrWindow.getSashes(); 4347 for (var i = 0; i < sashes.length; i++) { 4348 g2D.draw(this.getDoorOrWindowSashShape(doorOrWindow, sashes[i])); 4349 } 4350 } 4351 4352 /** 4353 * Returns the shape of a sash of a door or a window. 4354 * @param {HomeDoorOrWindow} doorOrWindow 4355 * @param {Sash} sash 4356 * @return {java.awt.geom.GeneralPath} 4357 * @private 4358 */ 4359 PlanComponent.prototype.getDoorOrWindowSashShape = function(doorOrWindow, sash) { 4360 var modelMirroredSign = doorOrWindow.isModelMirrored() ? -1 : 1; 4361 var xAxis = modelMirroredSign * sash.getXAxis() * doorOrWindow.getWidth(); 4362 var yAxis = sash.getYAxis() * doorOrWindow.getDepth(); 4363 var sashWidth = sash.getWidth() * doorOrWindow.getWidth(); 4364 var startAngle = sash.getStartAngle() * 180 / Math.PI; 4365 if (doorOrWindow.isModelMirrored()) { 4366 startAngle = 180 - startAngle; 4367 } 4368 var extentAngle = modelMirroredSign * ((sash.getEndAngle() - sash.getStartAngle()) * 180 / Math.PI); 4369 4370 var arc = new java.awt.geom.Arc2D.Float(xAxis - sashWidth, yAxis - sashWidth, 4371 2 * sashWidth, 2 * sashWidth, 4372 startAngle, extentAngle, java.awt.geom.Arc2D.PIE); 4373 var transformation = java.awt.geom.AffineTransform.getTranslateInstance(doorOrWindow.getX(), doorOrWindow.getY()); 4374 transformation.rotate(doorOrWindow.getAngle()); 4375 transformation.translate(modelMirroredSign * -doorOrWindow.getWidth() / 2, -doorOrWindow.getDepth() / 2); 4376 var it = arc.getPathIterator(transformation); 4377 var sashShape = new java.awt.geom.GeneralPath(); 4378 sashShape.append(it, false); 4379 return sashShape; 4380 } 4381 4382 /** 4383 * Paints home furniture visible name. 4384 * @param {Graphics2D} g2D 4385 * @param {HomePieceOfFurniture[]} furniture 4386 * @param {Bound[]} selectedItems 4387 * @param {number} planScale 4388 * @param {string} foregroundColor 4389 * @param {PlanComponent.PaintMode} paintMode 4390 * @private 4391 */ 4392 PlanComponent.prototype.paintFurnitureName = function(g2D, furniture, selectedItems, planScale, foregroundColor, paintMode) { 4393 var previousFont = g2D.getFont(); 4394 g2D.setPaint(foregroundColor); 4395 for (var i = 0; i < furniture.length; i++) { 4396 var piece = furniture[i]; 4397 if (piece.isVisible()) { 4398 var selectedPiece = (selectedItems.indexOf((piece)) >= 0); 4399 if (piece instanceof HomeFurnitureGroup) { 4400 var groupFurniture = (piece).getFurniture(); 4401 var emptyList = []; 4402 this.paintFurnitureName(g2D, groupFurniture, 4403 selectedPiece 4404 ? groupFurniture 4405 : emptyList, 4406 planScale, foregroundColor, paintMode); 4407 } 4408 if (piece.isNameVisible() 4409 && (paintMode !== PlanComponent.PaintMode.CLIPBOARD 4410 || selectedPiece)) { 4411 var name = piece.getName().trim(); 4412 if (name.length > 0) { 4413 this.paintText(g2D, piece.constructor, name, piece.getNameStyle(), null, 4414 piece.getX() + piece.getNameXOffset(), 4415 piece.getY() + piece.getNameYOffset(), 4416 piece.getNameAngle(), previousFont); 4417 } 4418 } 4419 } 4420 } 4421 g2D.setFont(previousFont); 4422 } 4423 4424 /** 4425 * Paints the outline of furniture among <code>items</code> and indicators if 4426 * <code>items</code> contains only one piece and indicator paint isn't <code>null</code>. 4427 * @param {Graphics2D} g2D 4428 * @param {Object[]} items 4429 * @param {Level} level 4430 * @param {string|CanvasPattern} selectionOutlinePaint 4431 * @param {java.awt.BasicStroke} selectionOutlineStroke 4432 * @param {string|CanvasPattern} indicatorPaint 4433 * @param {number} planScale 4434 * @param {string} foregroundColor 4435 * @private 4436 */ 4437 PlanComponent.prototype.paintFurnitureOutline = function(g2D, items, level, selectionOutlinePaint, selectionOutlineStroke, indicatorPaint, planScale, foregroundColor) { 4438 var plan = this; 4439 var pieceBorderStroke = new java.awt.BasicStroke(this.getStrokeWidth(HomePieceOfFurniture, PlanComponent.PaintMode.PAINT) / planScale); 4440 var pieceFrontBorderStroke = new java.awt.BasicStroke(4 * this.getStrokeWidth(HomePieceOfFurniture, PlanComponent.PaintMode.PAINT) / planScale, 4441 java.awt.BasicStroke.CAP_BUTT, java.awt.BasicStroke.JOIN_MITER); 4442 4443 var furniture = Home.getFurnitureSubList(items); 4444 var paintedFurniture = []; 4445 var furnitureGroupsArea = null; 4446 var furnitureGroupsStroke = new java.awt.BasicStroke(15 / planScale, java.awt.BasicStroke.CAP_SQUARE, java.awt.BasicStroke.JOIN_ROUND); 4447 var lastGroup = null; 4448 var furnitureInGroupsArea = null; 4449 var homeFurniture = this.home.getFurniture(); 4450 for (var i = 0; i < furniture.length; i++) { 4451 var piece = furniture [i]; 4452 if (piece.isVisible() 4453 && this.isViewableAtLevel(piece, level)) { 4454 var homePieceOfFurniture = this.getPieceOfFurnitureInHomeFurniture(piece, homeFurniture); 4455 if (homePieceOfFurniture !== piece) { 4456 var groupArea = null; 4457 if (lastGroup !== homePieceOfFurniture) { 4458 var groupShape = ShapeTools.getShape(homePieceOfFurniture.getPoints(), true, null); 4459 groupArea = new java.awt.geom.Area(groupShape); 4460 groupArea.add(new java.awt.geom.Area(furnitureGroupsStroke.createStrokedShape(groupShape))); 4461 } 4462 var pieceArea = new java.awt.geom.Area(ShapeTools.getShape(piece.getPoints(), true, null)); 4463 if (furnitureGroupsArea == null) { 4464 furnitureGroupsArea = groupArea; 4465 furnitureInGroupsArea = pieceArea; 4466 } else { 4467 if (lastGroup !== homePieceOfFurniture) { 4468 furnitureGroupsArea.add(groupArea); 4469 } 4470 furnitureInGroupsArea.add(pieceArea); 4471 } 4472 lastGroup = homePieceOfFurniture; 4473 } 4474 paintedFurniture.push(piece); 4475 } 4476 } 4477 if (furnitureGroupsArea != null) { 4478 furnitureGroupsArea.subtract(furnitureInGroupsArea); 4479 var oldComposite = this.setTransparency(g2D, 0.6); 4480 g2D.setPaint(selectionOutlinePaint); 4481 g2D.fill(furnitureGroupsArea); 4482 g2D.setAlpha(oldComposite); 4483 } 4484 4485 paintedFurniture.forEach(function(piece) { 4486 var points = piece.getPoints(); 4487 var pieceShape = ShapeTools.getShape(points, true, null); 4488 4489 g2D.setPaint(selectionOutlinePaint); 4490 g2D.setStroke(selectionOutlineStroke); 4491 g2D.draw(pieceShape); 4492 4493 g2D.setPaint(foregroundColor); 4494 g2D.setStroke(pieceBorderStroke); 4495 g2D.draw(pieceShape); 4496 4497 g2D.setStroke(pieceFrontBorderStroke); 4498 g2D.draw(new java.awt.geom.Line2D.Float(points[2][0], points[2][1], points[3][0], points[3][1])); 4499 4500 if (items.length === 1 && indicatorPaint != null) { 4501 plan.paintPieceOfFurnitureIndicators(g2D, piece, indicatorPaint, planScale); 4502 } 4503 }); 4504 } 4505 4506 /** 4507 * Returns <code>piece</code> if it belongs to home furniture or the group to which <code>piece</code> belongs. 4508 * @param {HomePieceOfFurniture} piece 4509 * @param {HomePieceOfFurniture[]} homeFurniture 4510 * @return {HomePieceOfFurniture} 4511 * @private 4512 */ 4513 PlanComponent.prototype.getPieceOfFurnitureInHomeFurniture = function(piece, homeFurniture) { 4514 if (!(homeFurniture.indexOf((piece)) >= 0)) { 4515 for (var i = 0; i < homeFurniture.length; i++) { 4516 var homePiece = homeFurniture[i]; 4517 if ((homePiece instanceof HomeFurnitureGroup) 4518 && ((homePiece).getAllFurniture().indexOf((piece)) >= 0)) { 4519 return homePiece; 4520 } 4521 } 4522 } 4523 return piece; 4524 } 4525 4526 /** 4527 * Paints <code>icon</code> with <code>g2D</code>. 4528 * @param {Graphics2D} g2D 4529 * @param {HomePieceOfFurniture} piece 4530 * @param {PlanComponent.PieceOfFurnitureTopViewIcon} icon 4531 * @param {Object} pieceShape2D 4532 * @param {number} planScale 4533 * @param {string} backgroundColor 4534 * @private 4535 */ 4536 PlanComponent.prototype.paintPieceOfFurnitureIcon = function(g2D, piece, icon, pieceShape2D, planScale, backgroundColor) { 4537 var plan = this; 4538 if (icon == null) { 4539 if (this.furnitureIconsCache == null) { 4540 this.furnitureIconsCache = {}; 4541 } 4542 var image = this.furnitureIconsCache[piece.icon.getURL()]; 4543 if (image == null) { 4544 image = TextureManager.getInstance().getWaitImage(); 4545 TextureManager.getInstance().loadTexture(piece.icon, { 4546 textureUpdated: function(texture) { 4547 plan.furnitureIconsCache[piece.icon.getURL()] = texture; 4548 plan.repaint(); 4549 }, 4550 textureError: function() { 4551 plan.furnitureIconsCache[piece.icon.getURL()] = TextureManager.getInstance().getErrorImage(); 4552 plan.repaint(); 4553 } 4554 }); 4555 } 4556 icon = new PlanComponent.PieceOfFurnitureTopViewIcon(image); 4557 } 4558 4559 // Fill piece area 4560 g2D.setPaint(backgroundColor); 4561 g2D.fill(pieceShape2D); 4562 var previousClip = g2D.getClip(); 4563 // Clip icon drawing into piece shape 4564 g2D.clip(pieceShape2D); 4565 var previousTransform = g2D.getTransform(); 4566 // Translate to piece center 4567 var bounds = pieceShape2D.getBounds2D(); 4568 g2D.translate(bounds.getCenterX(), bounds.getCenterY()); 4569 var pieceDepth = piece.getDepthInPlan(); 4570 if (piece instanceof HomeDoorOrWindow) { 4571 pieceDepth *= piece.getWallThickness(); 4572 } 4573 // Scale icon to fit in its area 4574 var minDimension = Math.min(piece.getWidthInPlan(), pieceDepth); 4575 var iconScale = Math.min(1 / planScale, minDimension / icon.getIconHeight()); 4576 // If piece model is mirrored, inverse x scale 4577 if (piece.isModelMirrored()) { 4578 g2D.scale(-iconScale, iconScale); 4579 } else { 4580 g2D.scale(iconScale, iconScale); 4581 } 4582 // Paint piece icon 4583 icon.paintIcon(g2D, -icon.getIconWidth() / 2 | 0, -icon.getIconHeight() / 2 | 0); 4584 // Revert g2D transformation to previous value 4585 g2D.setTransform(previousTransform); 4586 g2D.setClip(previousClip); 4587 } 4588 4589 /** 4590 * Paints <code>piece</code> top icon with <code>g2D</code>. 4591 * @param {Graphics2D} g2D 4592 * @param {HomePieceOfFurniture} piece 4593 * @param {Object} pieceShape2D 4594 * @param {java.awt.BasicStroke} pieceBorderStroke 4595 * @param {number} planScale 4596 * @param {string} backgroundColor 4597 * @param {string} foregroundColor 4598 * @param {PlanComponent.PaintMode} paintMode 4599 * @private 4600 */ 4601 PlanComponent.prototype.paintPieceOfFurnitureTop = function(g2D, piece, pieceShape2D, pieceBorderStroke, planScale, backgroundColor, foregroundColor, paintMode) { 4602 if (this.furnitureTopViewIconKeys == null) { 4603 this.furnitureTopViewIconKeys = {}; 4604 this.furnitureTopViewIconsCache = {}; 4605 } 4606 var topViewIconKey = CoreTools.getFromMap(this.furnitureTopViewIconKeys, piece); 4607 var icon; 4608 if (topViewIconKey == null) { 4609 topViewIconKey = new PlanComponent.HomePieceOfFurnitureTopViewIconKey(piece.clone()); 4610 icon = CoreTools.getFromMap(this.furnitureTopViewIconsCache, topViewIconKey); 4611 if (icon == null 4612 || icon.isWaitIcon() 4613 && paintMode !== PlanComponent.PaintMode.PAINT) { 4614 var waitingComponent = paintMode === PlanComponent.PaintMode.PAINT ? this : null; 4615 if (piece.getPlanIcon() != null) { 4616 icon = new PlanComponent.PieceOfFurniturePlanIcon(piece, waitingComponent); 4617 } else { 4618 icon = new PlanComponent.PieceOfFurnitureModelIcon(piece, this.object3dFactory, waitingComponent, this.preferences.getFurnitureModelIconSize()); 4619 } 4620 CoreTools.putToMap(this.furnitureTopViewIconsCache, topViewIconKey, icon); 4621 } else { 4622 for (var i = 0; i < this.furnitureTopViewIconsCache.entries.length; i++) { // Parse keySet 4623 var key = this.furnitureTopViewIconsCache.entries[i].key; 4624 if (key.equals(topViewIconKey)) { 4625 topViewIconKey = key; 4626 break; 4627 } 4628 } 4629 } 4630 CoreTools.putToMap(this.furnitureTopViewIconKeys, piece, topViewIconKey); 4631 } else { 4632 icon = CoreTools.getFromMap(this.furnitureTopViewIconsCache, topViewIconKey); 4633 } 4634 if (icon.isWaitIcon() || icon.isErrorIcon()) { 4635 this.paintPieceOfFurnitureIcon(g2D, piece, icon, pieceShape2D, planScale, backgroundColor); 4636 g2D.setPaint(foregroundColor); 4637 g2D.setStroke(pieceBorderStroke); 4638 g2D.draw(pieceShape2D); 4639 } else { 4640 var previousTransform = g2D.getTransform(); 4641 var bounds = pieceShape2D.getBounds2D(); 4642 g2D.translate(bounds.getCenterX(), bounds.getCenterY()); 4643 g2D.rotate(piece.getAngle()); 4644 var pieceDepth = piece.getDepthInPlan(); 4645 if (piece.isModelMirrored() 4646 && piece.getRoll() == 0) { 4647 g2D.scale(-piece.getWidthInPlan() / icon.getIconWidth(), pieceDepth / icon.getIconHeight()); 4648 } else { 4649 g2D.scale(piece.getWidthInPlan() / icon.getIconWidth(), pieceDepth / icon.getIconHeight()); 4650 } 4651 icon.paintIcon(g2D, (-icon.getIconWidth() / 2 | 0), (-icon.getIconHeight() / 2 | 0)); 4652 g2D.setTransform(previousTransform); 4653 } 4654 } 4655 4656 /** 4657 * Paints rotation, elevation, height and resize indicators on <code>piece</code>. 4658 * @param {Graphics2D} g2D 4659 * @param {HomePieceOfFurniture} piece 4660 * @param {string|CanvasPattern} indicatorPaint 4661 * @param {number} planScale 4662 * @private 4663 */ 4664 PlanComponent.prototype.paintPieceOfFurnitureIndicators = function(g2D, piece, indicatorPaint, planScale) { 4665 if (this.resizeIndicatorVisible) { 4666 g2D.setPaint(indicatorPaint); 4667 g2D.setStroke(PlanComponent.INDICATOR_STROKE); 4668 4669 var previousTransform = g2D.getTransform(); 4670 var piecePoints = piece.getPoints(); 4671 var scaleInverse = 1 / planScale; 4672 var pieceAngle = piece.getAngle(); 4673 var rotationIndicator = this.getIndicator(piece, PlanComponent.IndicatorType.ROTATE); 4674 if (rotationIndicator != null) { 4675 g2D.translate(piecePoints[0][0], piecePoints[0][1]); 4676 g2D.scale(scaleInverse, scaleInverse); 4677 g2D.rotate(pieceAngle); 4678 g2D.draw(rotationIndicator); 4679 g2D.setTransform(previousTransform); 4680 } 4681 4682 var elevationIndicator = this.getIndicator(piece, PlanComponent.IndicatorType.ELEVATE); 4683 if (elevationIndicator != null) { 4684 g2D.translate(piecePoints[1][0], piecePoints[1][1]); 4685 g2D.scale(scaleInverse, scaleInverse); 4686 g2D.rotate(pieceAngle); 4687 g2D.draw(PlanComponent.ELEVATION_POINT_INDICATOR); 4688 g2D.translate(6.5, -6.5); 4689 g2D.rotate(-pieceAngle); 4690 g2D.draw(elevationIndicator); 4691 g2D.setTransform(previousTransform); 4692 } 4693 4694 g2D.translate(piecePoints[3][0], piecePoints[3][1]); 4695 g2D.scale(scaleInverse, scaleInverse); 4696 g2D.rotate(pieceAngle); 4697 if (piece.getPitch() !== 0 && this.isFurnitureSizeInPlanSupported()) { 4698 var pitchIndicator = this.getIndicator(piece, PlanComponent.IndicatorType.ROTATE_PITCH); 4699 if (pitchIndicator != null) { 4700 g2D.draw(pitchIndicator); 4701 } 4702 } else if (piece.getRoll() !== 0 && this.isFurnitureSizeInPlanSupported()) { 4703 var rollIndicator = this.getIndicator(piece, PlanComponent.IndicatorType.ROTATE_ROLL); 4704 if (rollIndicator != null) { 4705 g2D.draw(rollIndicator); 4706 } 4707 } else if (piece instanceof HomeLight) { 4708 var powerIndicator = this.getIndicator(piece, PlanComponent.IndicatorType.CHANGE_POWER); 4709 if (powerIndicator != null) { 4710 g2D.draw(PlanComponent.LIGHT_POWER_POINT_INDICATOR); 4711 g2D.translate(-7.5, 7.5); 4712 g2D.rotate(-pieceAngle); 4713 g2D.draw(powerIndicator); 4714 } 4715 } else if (piece.isResizable() && !piece.isHorizontallyRotated()) { 4716 var heightIndicator = this.getIndicator(piece, PlanComponent.IndicatorType.RESIZE_HEIGHT); 4717 if (heightIndicator != null) { 4718 g2D.draw(PlanComponent.HEIGHT_POINT_INDICATOR); 4719 g2D.translate(-7.5, 7.5); 4720 g2D.rotate(-pieceAngle); 4721 g2D.draw(heightIndicator); 4722 } 4723 } 4724 g2D.setTransform(previousTransform); 4725 if (piece.isResizable()) { 4726 var resizeIndicator = this.getIndicator(piece, PlanComponent.IndicatorType.RESIZE); 4727 if (resizeIndicator != null) { 4728 g2D.translate(piecePoints[2][0], piecePoints[2][1]); 4729 g2D.scale(scaleInverse, scaleInverse); 4730 g2D.rotate(pieceAngle); 4731 g2D.draw(resizeIndicator); 4732 g2D.setTransform(previousTransform); 4733 } 4734 } 4735 4736 if (piece.isNameVisible() 4737 && piece.getName().trim().length > 0) { 4738 var xName = piece.getX() + piece.getNameXOffset(); 4739 var yName = piece.getY() + piece.getNameYOffset(); 4740 this.paintTextIndicators(g2D, piece, this.getLineCount(piece.getName()), piece.getNameStyle(), xName, yName, piece.getNameAngle(), indicatorPaint, planScale); 4741 } 4742 } 4743 } 4744 4745 /** 4746 * Paints polylines. 4747 * @param {Graphics2D} g2D 4748 * @param {Polyline[]} polylines 4749 * @param {Object[]} selectedItems 4750 * @param {Level} level 4751 * @param {string|CanvasPattern} selectionOutlinePaint 4752 * @param {string|CanvasPattern} indicatorPaint 4753 * @param {number} planScale 4754 * @param {string} foregroundColor 4755 * @param {PlanComponent.PaintMode} paintMode 4756 * @private 4757 */ 4758 PlanComponent.prototype.paintPolylines = function(g2D, polylines, selectedItems, level, selectionOutlinePaint, indicatorPaint, planScale, foregroundColor, paintMode) { 4759 for (var i = 0; i < polylines.length; i++) { 4760 var polyline = polylines[i]; 4761 if (this.isViewableAtLevel(polyline, level)) { 4762 var selected = (selectedItems.indexOf((polyline)) >= 0); 4763 if (paintMode !== PlanComponent.PaintMode.CLIPBOARD || selected) { 4764 g2D.setPaint(ColorTools.integerToHexadecimalString(polyline.getColor())); 4765 var thickness = polyline.getThickness(); 4766 g2D.setStroke(ShapeTools.getStroke(thickness, polyline.getCapStyle(), polyline.getJoinStyle(), 4767 polyline.getDashStyle() !== Polyline.DashStyle.SOLID ? polyline.getDashPattern() : null, // null renders better closed shapes with a solid style 4768 polyline.getDashOffset())); 4769 var polylineShape = ShapeTools.getPolylineShape(polyline.getPoints(), 4770 polyline.getJoinStyle() === Polyline.JoinStyle.CURVED, polyline.isClosedPath()); 4771 g2D.draw(polylineShape); 4772 4773 var firstPoint = null; 4774 var secondPoint = null; 4775 var beforeLastPoint = null; 4776 var lastPoint = null; 4777 for (var it = polylineShape.getPathIterator(null, 0.5); !it.isDone(); it.next()) { 4778 var pathPoint = [0, 0]; 4779 if (it.currentSegment(pathPoint) !== java.awt.geom.PathIterator.SEG_CLOSE) { 4780 if (firstPoint == null) { 4781 firstPoint = pathPoint; 4782 } else if (secondPoint == null) { 4783 secondPoint = pathPoint; 4784 } 4785 beforeLastPoint = lastPoint; 4786 lastPoint = pathPoint; 4787 } 4788 } 4789 var angleAtStart = Math.atan2(firstPoint[1] - secondPoint[1], 4790 firstPoint[0] - secondPoint[0]); 4791 var angleAtEnd = Math.atan2(lastPoint[1] - beforeLastPoint[1], 4792 lastPoint[0] - beforeLastPoint[0]); 4793 var arrowDelta = polyline.getCapStyle() !== Polyline.CapStyle.BUTT 4794 ? thickness / 2 4795 : 0; 4796 this.paintArrow(g2D, firstPoint, angleAtStart, polyline.getStartArrowStyle(), thickness, arrowDelta); 4797 this.paintArrow(g2D, lastPoint, angleAtEnd, polyline.getEndArrowStyle(), thickness, arrowDelta); 4798 4799 if (selected && paintMode === PlanComponent.PaintMode.PAINT) { 4800 g2D.setPaint(selectionOutlinePaint); 4801 g2D.setStroke(ShapeTools.getStroke(thickness + 4 / planScale, 4802 polyline.getCapStyle(), polyline.getJoinStyle(), null)); 4803 g2D.draw(polylineShape); 4804 4805 if (selectedItems.length === 1 4806 && indicatorPaint != null) { 4807 var selectedPolyline = selectedItems[0]; 4808 if (this.isViewableAtLevel(polyline, level)) { 4809 g2D.setPaint(indicatorPaint); 4810 this.paintPointsResizeIndicators(g2D, selectedPolyline, indicatorPaint, planScale, 4811 selectedPolyline.isClosedPath(), angleAtStart, angleAtEnd, false); 4812 } 4813 } 4814 } 4815 } 4816 } 4817 } 4818 } 4819 4820 /** 4821 * Paints polyline arrow at the given point and orientation. 4822 * @param {Graphics2D} g2D 4823 * @param {Array} point 4824 * @param {number} angle 4825 * @param {Polyline.ArrowStyle} arrowStyle 4826 * @param {number} thickness 4827 * @param {number} arrowDelta 4828 * @private 4829 */ 4830 PlanComponent.prototype.paintArrow = function(g2D, point, angle, arrowStyle, thickness, arrowDelta) { 4831 if (arrowStyle != null 4832 && arrowStyle !== Polyline.ArrowStyle.NONE) { 4833 var oldTransform = g2D.getTransform(); 4834 g2D.translate(point[0], point[1]); 4835 g2D.rotate(angle); 4836 g2D.translate(arrowDelta, 0); 4837 var scale = Math.pow(thickness, 0.66) * 2; 4838 g2D.scale(scale, scale); 4839 switch ((arrowStyle)) { 4840 case Polyline.ArrowStyle.DISC: 4841 g2D.fill(new java.awt.geom.Ellipse2D.Float(-3.5, -2, 4, 4)); 4842 break; 4843 case Polyline.ArrowStyle.OPEN: 4844 g2D.scale(0.9, 0.9); 4845 g2D.setStroke(new java.awt.BasicStroke((thickness / scale / 0.9), java.awt.BasicStroke.CAP_BUTT, java.awt.BasicStroke.JOIN_MITER)); 4846 g2D.draw(PlanComponent.ARROW); 4847 break; 4848 case Polyline.ArrowStyle.DELTA: 4849 g2D.translate(1.65, 0); 4850 g2D.fill(PlanComponent.ARROW); 4851 break; 4852 default: 4853 break; 4854 } 4855 g2D.setTransform(oldTransform); 4856 } 4857 } 4858 4859 /** 4860 * Paints dimension lines. 4861 * @param {Graphics2D} g2D 4862 * @param {DimensionLine[]} dimensionLines 4863 * @param {Object[]} selectedItems 4864 * @param {Level} level 4865 * @param {string|CanvasPattern} selectionOutlinePaint 4866 * @param {java.awt.BasicStroke} selectionOutlineStroke 4867 * @param {string|CanvasPattern} indicatorPaint 4868 * @param {java.awt.BasicStroke} extensionLineStroke 4869 * @param {number} planScale 4870 * @param {string} backgroundColor 4871 * @param {string} foregroundColor 4872 * @param {PlanComponent.PaintMode} paintMode 4873 * @param {boolean} feedback 4874 * @private 4875 */ 4876 PlanComponent.prototype.paintDimensionLines = function(g2D, dimensionLines, selectedItems, level, selectionOutlinePaint, selectionOutlineStroke, indicatorPaint, extensionLineStroke, planScale, backgroundColor, foregroundColor, paintMode, feedback) { 4877 var plan = this; 4878 if (paintMode === PlanComponent.PaintMode.CLIPBOARD) { 4879 dimensionLines = Home.getDimensionLinesSubList(selectedItems); 4880 } 4881 var markEndWidth = PlanComponent.DIMENSION_LINE_MARK_END.getBounds2D().getWidth(); 4882 var selectedDimensionLineWithIndicators = selectedItems.length == 1 4883 && selectedItems[0] instanceof DimensionLine 4884 && paintMode === PlanComponent.PaintMode.PAINT 4885 && indicatorPaint != null 4886 ? selectedItems[0] 4887 : null; 4888 4889 var previousFont = g2D.getFont(); 4890 for (var i = 0; i < dimensionLines.length; i++) { 4891 var dimensionLine = dimensionLines[i]; 4892 if (plan.isViewableAtLevel(dimensionLine, level)) { 4893 var dimensionLineColor = dimensionLine.getColor(); 4894 var markEndScale = dimensionLine.getEndMarkSize() / markEndWidth; 4895 var dimensionLineStroke = new java.awt.BasicStroke(plan.getStrokeWidth(DimensionLine, paintMode) / markEndScale / planScale); 4896 g2D.setPaint(dimensionLineColor != null ? ColorTools.integerToHexadecimalString(dimensionLineColor) : foregroundColor); 4897 var previousTransform = g2D.getTransform(); 4898 var elevationDimensionLine = dimensionLine.isElevationDimensionLine(); 4899 var angle = elevationDimensionLine 4900 ? (dimensionLine.getPitch() + 2 * Math.PI) % (2 * Math.PI) 4901 : Math.atan2(dimensionLine.getYEnd() - dimensionLine.getYStart(), dimensionLine.getXEnd() - dimensionLine.getXStart()); 4902 var dimensionLineOffset = dimensionLine.getOffset(); 4903 var dimensionLineLength = dimensionLine.getLength(); 4904 g2D.translate(dimensionLine.getXStart(), dimensionLine.getYStart()); 4905 g2D.rotate(angle); 4906 g2D.translate(0, dimensionLineOffset); 4907 4908 var horizontalDimensionLine = dimensionLine.getElevationStart() == dimensionLine.getElevationEnd(); 4909 if (paintMode === PlanComponent.PaintMode.PAINT 4910 && plan.selectedItemsOutlinePainted 4911 && (selectedItems.indexOf((dimensionLine)) >= 0)) { 4912 g2D.setPaint(selectionOutlinePaint); 4913 g2D.setStroke(selectionOutlineStroke); 4914 if (horizontalDimensionLine) { 4915 g2D.draw(new java.awt.geom.Line2D.Float(0, 0, dimensionLineLength, 0)); 4916 g2D.scale(markEndScale, markEndScale); 4917 g2D.draw(PlanComponent.DIMENSION_LINE_MARK_END); 4918 g2D.translate(dimensionLineLength / markEndScale, 0); 4919 g2D.draw(PlanComponent.DIMENSION_LINE_MARK_END); 4920 g2D.scale(1 / markEndScale, 1 / markEndScale); 4921 g2D.translate(-dimensionLineLength, 0); 4922 g2D.draw(new java.awt.geom.Line2D.Float(0, -dimensionLineOffset, 0, 0)); 4923 g2D.draw(new java.awt.geom.Line2D.Float(dimensionLineLength, -dimensionLineOffset, dimensionLineLength, 0)); 4924 } else { 4925 g2D.scale(markEndScale, markEndScale); 4926 g2D.draw(PlanComponent.VERTICAL_DIMENSION_LINE); 4927 g2D.scale(1 / markEndScale, 1 / markEndScale); 4928 if (Math.abs(dimensionLineOffset) > dimensionLine.getEndMarkSize() / 2) { 4929 g2D.draw(new java.awt.geom.Line2D.Float(0, -dimensionLineOffset, 4930 0, -dimensionLine.getEndMarkSize() / 2 * (dimensionLineOffset >= 0 ? (dimensionLineOffset == 0 ? 0 : 1) : -1))); 4931 } 4932 } 4933 g2D.setPaint(dimensionLineColor != null ? ColorTools.integerToHexadecimalString(dimensionLineColor) : foregroundColor); 4934 } 4935 4936 g2D.setStroke(dimensionLineStroke); 4937 if (horizontalDimensionLine) { 4938 g2D.draw(new java.awt.geom.Line2D.Float(0, 0, dimensionLineLength, 0)); 4939 g2D.scale(markEndScale, markEndScale); 4940 g2D.draw(PlanComponent.DIMENSION_LINE_MARK_END); 4941 g2D.translate(dimensionLineLength / markEndScale, 0); 4942 g2D.draw(PlanComponent.DIMENSION_LINE_MARK_END); 4943 g2D.scale(1 / markEndScale, 1 / markEndScale); 4944 g2D.translate(-dimensionLineLength, 0); 4945 g2D.setStroke(extensionLineStroke); 4946 g2D.draw(new java.awt.geom.Line2D.Float(0, -dimensionLineOffset, 0, 0)); 4947 g2D.draw(new java.awt.geom.Line2D.Float(dimensionLineLength, -dimensionLineOffset, dimensionLineLength, 0)); 4948 } else { 4949 g2D.scale(markEndScale, markEndScale); 4950 g2D.fill(PlanComponent.VERTICAL_DIMENSION_LINE_DISC); 4951 g2D.draw(PlanComponent.VERTICAL_DIMENSION_LINE); 4952 g2D.scale(1 / markEndScale, 1 / markEndScale); 4953 g2D.setStroke(extensionLineStroke); 4954 if (Math.abs(dimensionLineOffset) > dimensionLine.getEndMarkSize() / 2) { 4955 g2D.draw(new java.awt.geom.Line2D.Float(0, -dimensionLineOffset, 4956 0, -dimensionLine.getEndMarkSize() / 2 * (dimensionLineOffset >= 0 ? (dimensionLineOffset == 0 ? 0 : 1) : -1))); 4957 } 4958 } 4959 4960 if (horizontalDimensionLine 4961 || dimensionLine === selectedDimensionLineWithIndicators) { 4962 var lengthText = plan.preferences.getLengthUnit().getFormat().format(dimensionLineLength); 4963 var lengthStyle = dimensionLine.getLengthStyle(); 4964 if (lengthStyle == null) { 4965 lengthStyle = plan.preferences.getDefaultTextStyle(dimensionLine.constructor); 4966 } 4967 if (feedback && plan.getFont() != null 4968 || !horizontalDimensionLine 4969 && dimensionLine == selectedDimensionLineWithIndicators) { 4970 // Call directly the overloaded deriveStyle method that takes a float parameter 4971 // to avoid confusion with the one that takes a TextStyle.Alignment parameter 4972 lengthStyle = lengthStyle.deriveStyle$float(parseInt(new Font(plan.getFont()).size) / planScale); 4973 } 4974 var font = plan.getFont(previousFont, lengthStyle); 4975 var lengthFontMetrics = plan.getFontMetrics(font, lengthStyle); 4976 var lengthTextBounds = lengthFontMetrics.getStringBounds(lengthText, g2D); 4977 g2D.setFont(font); 4978 if (!horizontalDimensionLine 4979 && dimensionLine === selectedDimensionLineWithIndicators) { 4980 g2D.rotate(angle > Math.PI ? Math.PI / 2 : -Math.PI / 2); 4981 g2D.translate(dimensionLineOffset <= 0 ^ angle <= Math.PI 4982 ? -lengthTextBounds.getWidth() - markEndWidth / 2 - 5 / planScale 4983 : markEndWidth / 2 + 5 / planScale, 4984 lengthFontMetrics.getAscent() / 2); 4985 if (elevationDimensionLine 4986 && this.resizeIndicatorVisible) { 4987 // Add room for pitch rotation indicator 4988 g2D.translate((dimensionLineOffset <= 0 ^ angle <= Math.PI ? -1 : 1) * 10 / planScale, 0); 4989 } 4990 } else { 4991 g2D.translate((dimensionLineLength - lengthTextBounds.getWidth()) / 2, 4992 dimensionLineOffset <= 0 4993 ? -lengthFontMetrics.getDescent() - 1 4994 : lengthFontMetrics.getAscent() + 1); 4995 } 4996 if (feedback 4997 || !horizontalDimensionLine 4998 && dimensionLine === selectedDimensionLineWithIndicators) { 4999 g2D.setColor(backgroundColor); 5000 var oldComposite = plan.setTransparency(g2D, 0.7); 5001 g2D.setStroke(new java.awt.BasicStroke(4 / planScale, java.awt.BasicStroke.CAP_SQUARE, java.awt.BasicStroke.CAP_ROUND)); 5002 g2D.drawStringOutline(lengthText, 0, 0); 5003 g2D.setAlpha(oldComposite); 5004 g2D.setColor(foregroundColor); 5005 if (!feedback) { 5006 g2D.setPaint(indicatorPaint); 5007 } 5008 } 5009 g2D.setFont(font); 5010 g2D.drawString(lengthText, 0, 0); 5011 } 5012 g2D.setTransform(previousTransform); 5013 } 5014 } 5015 g2D.setFont(previousFont); 5016 if (selectedDimensionLineWithIndicators != null) { 5017 this.paintDimensionLineResizeIndicators(g2D, selectedDimensionLineWithIndicators, indicatorPaint, planScale); 5018 } 5019 } 5020 5021 /** 5022 * Paints resize indicators on a given dimension line. 5023 * @param {Graphics2D} g2D 5024 * @param {DimensionLine} dimensionLine 5025 * @param {string|CanvasPattern} indicatorPaint 5026 * @param {number} planScale 5027 * @private 5028 */ 5029 PlanComponent.prototype.paintDimensionLineResizeIndicators = function(g2D, dimensionLine, indicatorPaint, planScale) { 5030 if (this.resizeIndicatorVisible) { 5031 g2D.setPaint(indicatorPaint); 5032 g2D.setStroke(PlanComponent.INDICATOR_STROKE); 5033 5034 var dimensionLineAngle = dimensionLine.isElevationDimensionLine() 5035 ? dimensionLine.getPitch() 5036 : Math.atan2(dimensionLine.getYEnd() - dimensionLine.getYStart(), dimensionLine.getXEnd() - dimensionLine.getXStart()); 5037 var horizontalDimensionLine = dimensionLine.getElevationStart() === dimensionLine.getElevationEnd(); 5038 5039 var previousTransform = g2D.getTransform(); 5040 var scaleInverse = 1 / planScale; 5041 var resizeIndicator = this.getIndicator(dimensionLine, PlanComponent.IndicatorType.RESIZE); 5042 if (horizontalDimensionLine) { 5043 g2D.translate(dimensionLine.getXStart(), dimensionLine.getYStart()); 5044 g2D.rotate(dimensionLineAngle); 5045 g2D.translate(0, dimensionLine.getOffset()); 5046 g2D.rotate(Math.PI); 5047 g2D.scale(scaleInverse, scaleInverse); 5048 g2D.draw(resizeIndicator); 5049 g2D.setTransform(previousTransform); 5050 5051 g2D.translate(dimensionLine.getXEnd(), dimensionLine.getYEnd()); 5052 g2D.rotate(dimensionLineAngle); 5053 g2D.translate(0, dimensionLine.getOffset()); 5054 g2D.scale(scaleInverse, scaleInverse); 5055 g2D.draw(resizeIndicator); 5056 g2D.setTransform(previousTransform); 5057 5058 g2D.translate((dimensionLine.getXStart() + dimensionLine.getXEnd()) / 2, 5059 (dimensionLine.getYStart() + dimensionLine.getYEnd()) / 2); 5060 } else { 5061 g2D.translate(dimensionLine.getXStart(), dimensionLine.getYStart()); 5062 } 5063 5064 g2D.rotate(dimensionLineAngle); 5065 var middlePointTransform = g2D.getTransform(); 5066 g2D.translate(0, dimensionLine.getOffset() 5067 - (horizontalDimensionLine ? 0 : dimensionLine.getEndMarkSize() / 2 * (dimensionLine.getOffset() > 0 ? 1 : -1))); 5068 g2D.rotate(dimensionLine.getOffset() <= 0 5069 ? Math.PI / 2 5070 : -Math.PI / 2); 5071 g2D.scale(scaleInverse, scaleInverse); 5072 g2D.draw(resizeIndicator); 5073 5074 if (!horizontalDimensionLine) { 5075 if (dimensionLine.isElevationDimensionLine()) { 5076 g2D.setTransform(middlePointTransform); 5077 g2D.translate(0, dimensionLine.getOffset() + dimensionLine.getEndMarkSize() / 2 * (dimensionLine.getOffset() > 0 ? 1 : -1)); 5078 g2D.rotate(dimensionLine.getOffset() <= 0 5079 ? Math.PI / 2 5080 : -Math.PI / 2); 5081 g2D.scale(scaleInverse, scaleInverse); 5082 g2D.draw(this.getIndicator(dimensionLine, PlanComponent.IndicatorType.ROTATE)); 5083 } 5084 5085 g2D.setTransform(middlePointTransform); 5086 g2D.translate(-dimensionLine.getEndMarkSize() / 2, dimensionLine.getOffset()); 5087 g2D.scale(scaleInverse, scaleInverse); 5088 g2D.draw(PlanComponent.ELEVATION_POINT_INDICATOR); 5089 g2D.translate(-9, 0); 5090 g2D.rotate(-dimensionLineAngle); 5091 g2D.draw(this.getIndicator(dimensionLine, PlanComponent.IndicatorType.ELEVATE)); 5092 5093 g2D.setTransform(middlePointTransform); 5094 g2D.translate(5, dimensionLine.getOffset()); 5095 g2D.scale(scaleInverse, scaleInverse); 5096 g2D.draw(PlanComponent.HEIGHT_POINT_INDICATOR); 5097 g2D.translate(10, 0); 5098 g2D.rotate(-dimensionLineAngle); 5099 g2D.draw(this.getIndicator(dimensionLine, PlanComponent.IndicatorType.RESIZE_HEIGHT)); 5100 } 5101 5102 g2D.setTransform(previousTransform); 5103 } 5104 } 5105 5106 /** 5107 * Paints home labels. 5108 * @param {Graphics2D} g2D 5109 * @param {Label[]} labels 5110 * @param {Object[]} selectedItems 5111 * @param {Level} level 5112 * @param {string|CanvasPattern} selectionOutlinePaint 5113 * @param {java.awt.BasicStroke} selectionOutlineStroke 5114 * @param {string|CanvasPattern} indicatorPaint 5115 * @param {number} planScale 5116 * @param {string} foregroundColor 5117 * @param {PlanComponent.PaintMode} paintMode 5118 * @private 5119 */ 5120 PlanComponent.prototype.paintLabels = function(g2D, labels, selectedItems, level, selectionOutlinePaint, selectionOutlineStroke, indicatorPaint, planScale, foregroundColor, paintMode) { 5121 var previousFont = g2D.getFont(); 5122 for (var i = 0; i < labels.length; i++) { 5123 var label = labels[i]; 5124 if (this.isViewableAtLevel(label, level)) { 5125 var selectedLabel = (selectedItems.indexOf((label)) >= 0); 5126 if (paintMode !== PlanComponent.PaintMode.CLIPBOARD || selectedLabel) { 5127 var labelText = label.getText(); 5128 var xLabel = label.getX(); 5129 var yLabel = label.getY(); 5130 var labelAngle = label.getAngle(); 5131 var labelStyle = label.getStyle(); 5132 if (labelStyle == null) { 5133 labelStyle = this.preferences.getDefaultTextStyle(label.constructor); 5134 } 5135 if (labelStyle.getFontName() == null && this.getFont() != null) { 5136 labelStyle = labelStyle.deriveStyle(new Font(this.getFont()).family); 5137 } 5138 var color = label.getColor(); 5139 g2D.setPaint(color != null ? ColorTools.integerToHexadecimalString(color) : foregroundColor); 5140 this.paintText(g2D, label.constructor, labelText, labelStyle, label.getOutlineColor(), 5141 xLabel, yLabel, labelAngle, previousFont); 5142 5143 if (paintMode === PlanComponent.PaintMode.PAINT && this.selectedItemsOutlinePainted && selectedLabel) { 5144 g2D.setPaint(selectionOutlinePaint); 5145 g2D.setStroke(selectionOutlineStroke); 5146 var textBounds = this.getTextBounds(labelText, labelStyle, xLabel, yLabel, labelAngle); 5147 g2D.draw(ShapeTools.getShape(textBounds, true, null)); 5148 g2D.setPaint(foregroundColor); 5149 if (indicatorPaint != null && selectedItems.length === 1 && selectedItems[0] === label) { 5150 this.paintTextIndicators(g2D, label, this.getLineCount(labelText), 5151 labelStyle, xLabel, yLabel, labelAngle, indicatorPaint, planScale); 5152 5153 if (this.resizeIndicatorVisible 5154 && label.getPitch() != null) { 5155 var elevationIndicator = this.getIndicator(label, PlanComponent.IndicatorType.ELEVATE); 5156 if (elevationIndicator != null) { 5157 var previousTransform = g2D.getTransform(); 5158 if (labelStyle.getAlignment() === TextStyle.Alignment.LEFT) { 5159 g2D.translate(textBounds[3][0], textBounds[3][1]); 5160 } else if (labelStyle.getAlignment() === TextStyle.Alignment.RIGHT) { 5161 g2D.translate(textBounds[2][0], textBounds[2][1]); 5162 } else { 5163 g2D.translate((textBounds[2][0] + textBounds[3][0]) / 2, (textBounds[2][1] + textBounds[3][1]) / 2); 5164 } 5165 var scaleInverse = 1 / planScale; 5166 g2D.scale(scaleInverse, scaleInverse); 5167 g2D.rotate(label.getAngle()); 5168 g2D.draw(PlanComponent.ELEVATION_POINT_INDICATOR); 5169 g2D.translate(0, 10.0); 5170 g2D.rotate(-label.getAngle()); 5171 g2D.draw(elevationIndicator); 5172 g2D.setTransform(previousTransform); 5173 } 5174 } 5175 } 5176 } 5177 } 5178 } 5179 } 5180 g2D.setFont(previousFont); 5181 } 5182 5183 /** 5184 * Paints the compass. 5185 * @param {Graphics2D} g2D 5186 * @param {Object[]} selectedItems 5187 * @param {number} planScale 5188 * @param {string} foregroundColor 5189 * @param {PlanComponent.PaintMode} paintMode 5190 * @private 5191 */ 5192 PlanComponent.prototype.paintCompass = function(g2D, selectedItems, planScale, foregroundColor, paintMode) { 5193 var compass = this.home.getCompass(); 5194 if (compass.isVisible() 5195 && (paintMode !== PlanComponent.PaintMode.CLIPBOARD 5196 || selectedItems.indexOf(compass) >= 0)) { 5197 var previousTransform = g2D.getTransform(); 5198 g2D.translate(compass.getX(), compass.getY()); 5199 g2D.rotate(compass.getNorthDirection()); 5200 var diameter = compass.getDiameter(); 5201 g2D.scale(diameter, diameter); 5202 g2D.setColor(foregroundColor); 5203 g2D.fill(PlanComponent.COMPASS); 5204 g2D.setTransform(previousTransform); 5205 } 5206 } 5207 5208 /** 5209 * Paints the outline of the compass when it's belongs to <code>items</code>. 5210 * @param {Graphics2D} g2D 5211 * @param {Object[]} items 5212 * @param {string|CanvasPattern} selectionOutlinePaint 5213 * @param {java.awt.BasicStroke} selectionOutlineStroke 5214 * @param {string|CanvasPattern} indicatorPaint 5215 * @param {number} planScale 5216 * @param {string} foregroundColor 5217 * @private 5218 */ 5219 PlanComponent.prototype.paintCompassOutline = function(g2D, items, selectionOutlinePaint, selectionOutlineStroke, indicatorPaint, planScale, foregroundColor) { 5220 var compass = this.home.getCompass(); 5221 if ((items.indexOf(compass) >= 0) 5222 && compass.isVisible()) { 5223 var previousTransform = g2D.getTransform(); 5224 g2D.translate(compass.getX(), compass.getY()); 5225 g2D.rotate(compass.getNorthDirection()); 5226 var diameter = compass.getDiameter(); 5227 g2D.scale(diameter, diameter); 5228 5229 g2D.setPaint(selectionOutlinePaint); 5230 g2D.setStroke(new java.awt.BasicStroke((5.5 + planScale) / diameter / planScale)); 5231 g2D.draw(PlanComponent.COMPASS_DISC); 5232 g2D.setColor(foregroundColor); 5233 g2D.setStroke(new java.awt.BasicStroke(1.0 / diameter / planScale)); 5234 g2D.draw(PlanComponent.COMPASS_DISC); 5235 g2D.setTransform(previousTransform); 5236 5237 if (items.length === 1 5238 && items[0] === compass) { 5239 g2D.setPaint(indicatorPaint); 5240 this.paintCompassIndicators(g2D, compass, indicatorPaint, planScale); 5241 } 5242 } 5243 } 5244 5245 /** 5246 * @private 5247 */ 5248 PlanComponent.prototype.paintCompassIndicators = function(g2D, compass, indicatorPaint, planScale) { 5249 if (this.resizeIndicatorVisible) { 5250 g2D.setPaint(indicatorPaint); 5251 g2D.setStroke(PlanComponent.INDICATOR_STROKE); 5252 5253 var previousTransform = g2D.getTransform(); 5254 var compassPoints = compass.getPoints(); 5255 var scaleInverse = 1 / planScale; 5256 g2D.translate((compassPoints[2][0] + compassPoints[3][0]) / 2, 5257 (compassPoints[2][1] + compassPoints[3][1]) / 2); 5258 g2D.scale(scaleInverse, scaleInverse); 5259 g2D.rotate(compass.getNorthDirection()); 5260 g2D.draw(this.getIndicator(compass, PlanComponent.IndicatorType.ROTATE)); 5261 g2D.setTransform(previousTransform); 5262 5263 g2D.translate((compassPoints[1][0] + compassPoints[2][0]) / 2, 5264 (compassPoints[1][1] + compassPoints[2][1]) / 2); 5265 g2D.scale(scaleInverse, scaleInverse); 5266 g2D.rotate(compass.getNorthDirection()); 5267 g2D.draw(this.getIndicator(compass, PlanComponent.IndicatorType.RESIZE)); 5268 g2D.setTransform(previousTransform); 5269 } 5270 } 5271 5272 /** 5273 * Paints wall location feedback. 5274 * @param {Graphics2D} g2D 5275 * @param {Wall} alignedWall 5276 * @param {Level} level 5277 * @param {java.awt.geom.Point2D} locationFeedback 5278 * @param {boolean} showPointFeedback 5279 * @param {string|CanvasPattern} feedbackPaint 5280 * @param {java.awt.BasicStroke} feedbackStroke 5281 * @param {number} planScale 5282 * @param {string|CanvasPattern} pointPaint 5283 * @param {java.awt.BasicStroke} pointStroke 5284 * @private 5285 */ 5286 PlanComponent.prototype.paintWallAlignmentFeedback = function(g2D, alignedWall, level, locationFeedback, showPointFeedback, feedbackPaint, feedbackStroke, planScale, pointPaint, pointStroke) { 5287 var plan = this; 5288 if (locationFeedback != null) { 5289 var margin = 0.5 / planScale; 5290 var x = locationFeedback.getX(); 5291 var y = locationFeedback.getY(); 5292 var deltaXToClosestWall = Infinity; 5293 var deltaYToClosestWall = Infinity; 5294 this.getViewedItems(this.home.getWalls(), level, this.otherLevelsWallsCache).forEach(function(wall) { 5295 if (wall !== alignedWall) { 5296 if (Math.abs(x - wall.getXStart()) < margin 5297 && (alignedWall == null 5298 || !plan.equalsWallPoint(wall.getXStart(), wall.getYStart(), alignedWall))) { 5299 if (Math.abs(deltaYToClosestWall) > Math.abs(y - wall.getYStart())) { 5300 deltaYToClosestWall = y - wall.getYStart(); 5301 } 5302 } else if (Math.abs(x - wall.getXEnd()) < margin 5303 && (alignedWall == null 5304 || !plan.equalsWallPoint(wall.getXEnd(), wall.getYEnd(), alignedWall))) { 5305 if (Math.abs(deltaYToClosestWall) > Math.abs(y - wall.getYEnd())) { 5306 deltaYToClosestWall = y - wall.getYEnd(); 5307 } 5308 } 5309 5310 if (Math.abs(y - wall.getYStart()) < margin 5311 && (alignedWall == null 5312 || !plan.equalsWallPoint(wall.getXStart(), wall.getYStart(), alignedWall))) { 5313 if (Math.abs(deltaXToClosestWall) > Math.abs(x - wall.getXStart())) { 5314 deltaXToClosestWall = x - wall.getXStart(); 5315 } 5316 } else if (Math.abs(y - wall.getYEnd()) < margin 5317 && (alignedWall == null 5318 || !plan.equalsWallPoint(wall.getXEnd(), wall.getYEnd(), alignedWall))) { 5319 if (Math.abs(deltaXToClosestWall) > Math.abs(x - wall.getXEnd())) { 5320 deltaXToClosestWall = x - wall.getXEnd(); 5321 } 5322 } 5323 5324 var wallPoints = wall.getPoints(); 5325 wallPoints = [wallPoints[0], wallPoints[(wallPoints.length / 2 | 0) - 1], 5326 wallPoints[(wallPoints.length / 2 | 0)], wallPoints[wallPoints.length - 1]]; 5327 for (var i = 0; i < wallPoints.length; i++) { 5328 if (Math.abs(x - wallPoints[i][0]) < margin 5329 && (alignedWall == null 5330 || !plan.equalsWallPoint(wallPoints[i][0], wallPoints[i][1], alignedWall))) { 5331 if (Math.abs(deltaYToClosestWall) > Math.abs(y - wallPoints[i][1])) { 5332 deltaYToClosestWall = y - wallPoints[i][1]; 5333 } 5334 } 5335 if (Math.abs(y - wallPoints[i][1]) < margin 5336 && (alignedWall == null 5337 || !plan.equalsWallPoint(wallPoints[i][0], wallPoints[i][1], alignedWall))) { 5338 if (Math.abs(deltaXToClosestWall) > Math.abs(x - wallPoints[i][0])) { 5339 deltaXToClosestWall = x - wallPoints[i][0]; 5340 } 5341 } 5342 } 5343 } 5344 }); 5345 5346 g2D.setPaint(feedbackPaint); 5347 g2D.setStroke(feedbackStroke); 5348 var alignmentLineOffset = this.pointerType === View.PointerType.TOUCH 5349 ? PlanComponent.ALIGNMENT_LINE_OFFSET * 2 5350 : PlanComponent.ALIGNMENT_LINE_OFFSET; 5351 if (deltaXToClosestWall !== Infinity) { 5352 if (deltaXToClosestWall > 0) { 5353 g2D.draw(new java.awt.geom.Line2D.Float(x + alignmentLineOffset / planScale, y, 5354 x - deltaXToClosestWall - alignmentLineOffset / planScale, y)); 5355 } else { 5356 g2D.draw(new java.awt.geom.Line2D.Float(x - alignmentLineOffset / planScale, y, 5357 x - deltaXToClosestWall + alignmentLineOffset / planScale, y)); 5358 } 5359 } 5360 5361 if (deltaYToClosestWall !== Infinity) { 5362 if (deltaYToClosestWall > 0) { 5363 g2D.draw(new java.awt.geom.Line2D.Float(x, y + alignmentLineOffset / planScale, 5364 x, y - deltaYToClosestWall - alignmentLineOffset / planScale)); 5365 } else { 5366 g2D.draw(new java.awt.geom.Line2D.Float(x, y - alignmentLineOffset / planScale, 5367 x, y - deltaYToClosestWall + alignmentLineOffset / planScale)); 5368 } 5369 } 5370 if (showPointFeedback) { 5371 this.paintPointFeedback(g2D, locationFeedback, feedbackPaint, planScale, pointPaint, pointStroke); 5372 } 5373 } 5374 } 5375 5376 /** 5377 * Returns the items viewed in the plan at the given <code>level</code>. 5378 * @param {Object[]} homeItems 5379 * @param {Level} level 5380 * @param {Object[]} otherLevelItems 5381 * @return {Object[]} 5382 * @private 5383 */ 5384 PlanComponent.prototype.getViewedItems = function(homeItems, level, otherLevelItems) { 5385 var viewedWalls = []; 5386 if (otherLevelItems != null) { 5387 viewedWalls.push.apply(viewedWalls, otherLevelItems); 5388 } 5389 for (var i = 0; i < homeItems.length; i++) { 5390 var wall = homeItems[i]; 5391 if (this.isViewableAtLevel(wall, level)) { 5392 viewedWalls.push(wall); 5393 } 5394 } 5395 return viewedWalls; 5396 } 5397 5398 /** 5399 * Paints point feedback. 5400 * @param {Graphics2D} g2D 5401 * @param {java.awt.geom.Point2D} locationFeedback 5402 * @param {string|CanvasPattern} feedbackPaint 5403 * @param {number} planScale 5404 * @param {string|CanvasPattern} pointPaint 5405 * @param {java.awt.BasicStroke} pointStroke 5406 * @private 5407 */ 5408 PlanComponent.prototype.paintPointFeedback = function(g2D, locationFeedback, feedbackPaint, planScale, pointPaint, pointStroke) { 5409 g2D.setPaint(pointPaint); 5410 g2D.setStroke(pointStroke); 5411 var radius = this.pointerType === View.PointerType.TOUCH ? 20 : 10; 5412 var circle = new java.awt.geom.Ellipse2D.Float(locationFeedback.getX() - radius / planScale, 5413 locationFeedback.getY() - radius / planScale, 2 * radius / planScale, 2 * radius / planScale); 5414 g2D.fill(circle); 5415 g2D.setPaint(feedbackPaint); 5416 g2D.setStroke(new java.awt.BasicStroke(1 / planScale)); 5417 g2D.draw(circle); 5418 g2D.draw(new java.awt.geom.Line2D.Float(locationFeedback.getX(), 5419 locationFeedback.getY() - radius / planScale, 5420 locationFeedback.getX(), 5421 locationFeedback.getY() + radius / planScale)); 5422 g2D.draw(new java.awt.geom.Line2D.Float(locationFeedback.getX() - radius / planScale, 5423 locationFeedback.getY(), 5424 locationFeedback.getX() + radius / planScale, 5425 locationFeedback.getY())); 5426 } 5427 5428 /** 5429 * Returns <code>true</code> if <code>wall</code> start or end point 5430 * equals the point (<code>x</code>, <code>y</code>). 5431 * @param {number} x 5432 * @param {number} y 5433 * @param {Wall} wall 5434 * @return {boolean} 5435 * @private 5436 */ 5437 PlanComponent.prototype.equalsWallPoint = function(x, y, wall) { 5438 return x === wall.getXStart() && y === wall.getYStart() 5439 || x === wall.getXEnd() && y === wall.getYEnd(); 5440 } 5441 5442 /** 5443 * Paints room location feedback. 5444 * @param {Graphics2D} g2D 5445 * @param {Room} alignedRoom 5446 * @param {Level} level 5447 * @param {java.awt.geom.Point2D} locationFeedback 5448 * @param {boolean} showPointFeedback 5449 * @param {string|CanvasPattern} feedbackPaint 5450 * @param {java.awt.BasicStroke} feedbackStroke 5451 * @param {number} planScale 5452 * @param {string|CanvasPattern} pointPaint 5453 * @param {java.awt.BasicStroke} pointStroke 5454 * @private 5455 */ 5456 PlanComponent.prototype.paintRoomAlignmentFeedback = function(g2D, alignedRoom, level, locationFeedback, showPointFeedback, feedbackPaint, feedbackStroke, planScale, pointPaint, pointStroke) { 5457 if (locationFeedback != null) { 5458 var margin = 0.5 / planScale; 5459 var x = locationFeedback.getX(); 5460 var y = locationFeedback.getY(); 5461 var deltaXToClosestObject = Infinity; 5462 var deltaYToClosestObject = Infinity; 5463 this.getViewedItems(this.home.getRooms(), level, this.otherLevelsRoomsCache).forEach(function(room) { 5464 var roomPoints = room.getPoints(); 5465 var editedPointIndex = -1; 5466 if (room === alignedRoom) { 5467 for (var i = 0; i < roomPoints.length; i++) { 5468 if (roomPoints[i][0] === x && roomPoints[i][1] === y) { 5469 editedPointIndex = i; 5470 break; 5471 } 5472 } 5473 } 5474 for (var i = 0; i < roomPoints.length; i++) { 5475 if (editedPointIndex === -1 || (i !== editedPointIndex && roomPoints.length > 2)) { 5476 if (Math.abs(x - roomPoints[i][0]) < margin 5477 && Math.abs(deltaYToClosestObject) > Math.abs(y - roomPoints[i][1])) { 5478 deltaYToClosestObject = y - roomPoints[i][1]; 5479 } 5480 if (Math.abs(y - roomPoints[i][1]) < margin 5481 && Math.abs(deltaXToClosestObject) > Math.abs(x - roomPoints[i][0])) { 5482 deltaXToClosestObject = x - roomPoints[i][0]; 5483 } 5484 } 5485 } 5486 }); 5487 5488 this.getViewedItems(this.home.getWalls(), level, this.otherLevelsWallsCache).forEach(function(wall) { 5489 var wallPoints = wall.getPoints(); 5490 wallPoints = [wallPoints[0], wallPoints[(wallPoints.length / 2 | 0) - 1], 5491 wallPoints[(wallPoints.length / 2 | 0)], wallPoints[wallPoints.length - 1]]; 5492 for (var i = 0; i < wallPoints.length; i++) { 5493 if (Math.abs(x - wallPoints[i][0]) < margin 5494 && Math.abs(deltaYToClosestObject) > Math.abs(y - wallPoints[i][1])) { 5495 deltaYToClosestObject = y - wallPoints[i][1]; 5496 } 5497 if (Math.abs(y - wallPoints[i][1]) < margin 5498 && Math.abs(deltaXToClosestObject) > Math.abs(x - wallPoints[i][0])) { 5499 deltaXToClosestObject = x - wallPoints[i][0]; 5500 } 5501 } 5502 }); 5503 5504 g2D.setPaint(feedbackPaint); 5505 g2D.setStroke(feedbackStroke); 5506 var alignmentLineOffset = this.pointerType === View.PointerType.TOUCH 5507 ? PlanComponent.ALIGNMENT_LINE_OFFSET * 2 5508 : PlanComponent.ALIGNMENT_LINE_OFFSET; 5509 if (deltaXToClosestObject !== Infinity) { 5510 if (deltaXToClosestObject > 0) { 5511 g2D.draw(new java.awt.geom.Line2D.Float(x + alignmentLineOffset / planScale, y, 5512 x - deltaXToClosestObject - alignmentLineOffset / planScale, y)); 5513 } else { 5514 g2D.draw(new java.awt.geom.Line2D.Float(x - alignmentLineOffset / planScale, y, 5515 x - deltaXToClosestObject + alignmentLineOffset / planScale, y)); 5516 } 5517 } 5518 if (deltaYToClosestObject !== Infinity) { 5519 if (deltaYToClosestObject > 0) { 5520 g2D.draw(new java.awt.geom.Line2D.Float(x, y + alignmentLineOffset / planScale, 5521 x, y - deltaYToClosestObject - alignmentLineOffset / planScale)); 5522 } else { 5523 g2D.draw(new java.awt.geom.Line2D.Float(x, y - alignmentLineOffset / planScale, 5524 x, y - deltaYToClosestObject + alignmentLineOffset / planScale)); 5525 } 5526 } 5527 if (showPointFeedback) { 5528 this.paintPointFeedback(g2D, locationFeedback, feedbackPaint, planScale, pointPaint, pointStroke); 5529 } 5530 } 5531 } 5532 5533 /** 5534 * Paints dimension line location feedback. 5535 * @param {Graphics2D} g2D 5536 * @param {DimensionLine} alignedDimensionLine 5537 * @param {Level} level 5538 * @param {java.awt.geom.Point2D} locationFeedback 5539 * @param {boolean} showPointFeedback 5540 * @param {string|CanvasPattern} feedbackPaint 5541 * @param {java.awt.BasicStroke} feedbackStroke 5542 * @param {number} planScale 5543 * @param {string|CanvasPattern} pointPaint 5544 * @param {java.awt.BasicStroke} pointStroke 5545 * @private 5546 */ 5547 PlanComponent.prototype.paintDimensionLineAlignmentFeedback = function(g2D, alignedDimensionLine, level, locationFeedback, showPointFeedback, feedbackPaint, feedbackStroke, planScale, pointPaint, pointStroke) { 5548 var plan = this; 5549 if (locationFeedback != null) { 5550 var margin = 0.5 / planScale; 5551 var x = locationFeedback.getX(); 5552 var y = locationFeedback.getY(); 5553 var deltaXToClosestObject = Infinity; 5554 var deltaYToClosestObject = Infinity; 5555 this.getViewedItems(this.home.getRooms(), level, this.otherLevelsRoomsCache).forEach(function(room) { 5556 var roomPoints = room.getPoints(); 5557 for (var i = 0; i < roomPoints.length; i++) { 5558 if (Math.abs(x - roomPoints[i][0]) < margin 5559 && Math.abs(deltaYToClosestObject) > Math.abs(y - roomPoints[i][1])) { 5560 deltaYToClosestObject = y - roomPoints[i][1]; 5561 } 5562 if (Math.abs(y - roomPoints[i][1]) < margin 5563 && Math.abs(deltaXToClosestObject) > Math.abs(x - roomPoints[i][0])) { 5564 deltaXToClosestObject = x - roomPoints[i][0]; 5565 } 5566 } 5567 }); 5568 5569 this.home.getDimensionLines().forEach(function(dimensionLine) { 5570 if (plan.isViewableAtLevel(dimensionLine, level) 5571 && dimensionLine !== alignedDimensionLine) { 5572 if (Math.abs(x - dimensionLine.getXStart()) < margin 5573 && (alignedDimensionLine == null 5574 || !plan.equalsDimensionLinePoint(dimensionLine.getXStart(), dimensionLine.getYStart(), 5575 alignedDimensionLine))) { 5576 if (Math.abs(deltaYToClosestObject) > Math.abs(y - dimensionLine.getYStart())) { 5577 deltaYToClosestObject = y - dimensionLine.getYStart(); 5578 } 5579 } else if (Math.abs(x - dimensionLine.getXEnd()) < margin 5580 && (alignedDimensionLine == null 5581 || !plan.equalsDimensionLinePoint(dimensionLine.getXEnd(), dimensionLine.getYEnd(), 5582 alignedDimensionLine))) { 5583 if (Math.abs(deltaYToClosestObject) > Math.abs(y - dimensionLine.getYEnd())) { 5584 deltaYToClosestObject = y - dimensionLine.getYEnd(); 5585 } 5586 } 5587 if (Math.abs(y - dimensionLine.getYStart()) < margin 5588 && (alignedDimensionLine == null 5589 || !plan.equalsDimensionLinePoint(dimensionLine.getXStart(), dimensionLine.getYStart(), 5590 alignedDimensionLine))) { 5591 if (Math.abs(deltaXToClosestObject) > Math.abs(x - dimensionLine.getXStart())) { 5592 deltaXToClosestObject = x - dimensionLine.getXStart(); 5593 } 5594 } else if (Math.abs(y - dimensionLine.getYEnd()) < margin 5595 && (alignedDimensionLine == null 5596 || !plan.equalsDimensionLinePoint(dimensionLine.getXEnd(), dimensionLine.getYEnd(), 5597 alignedDimensionLine))) { 5598 if (Math.abs(deltaXToClosestObject) > Math.abs(x - dimensionLine.getXEnd())) { 5599 deltaXToClosestObject = x - dimensionLine.getXEnd(); 5600 } 5601 } 5602 } 5603 }); 5604 5605 this.getViewedItems(this.home.getWalls(), level, this.otherLevelsWallsCache).forEach(function(wall) { 5606 var wallPoints = wall.getPoints(); 5607 wallPoints = [wallPoints[0], wallPoints[(wallPoints.length / 2 | 0) - 1], 5608 wallPoints[(wallPoints.length / 2 | 0)], wallPoints[wallPoints.length - 1]]; 5609 for (var i = 0; i < wallPoints.length; i++) { 5610 if (Math.abs(x - wallPoints[i][0]) < margin 5611 && Math.abs(deltaYToClosestObject) > Math.abs(y - wallPoints[i][1])) { 5612 deltaYToClosestObject = y - wallPoints[i][1]; 5613 } 5614 if (Math.abs(y - wallPoints[i][1]) < margin 5615 && Math.abs(deltaXToClosestObject) > Math.abs(x - wallPoints[i][0])) { 5616 deltaXToClosestObject = x - wallPoints[i][0]; 5617 } 5618 } 5619 }); 5620 5621 this.home.getFurniture().forEach(function(piece) { 5622 if (piece.isVisible() 5623 && plan.isViewableAtLevel(piece, level)) { 5624 var piecePoints = piece.getPoints(); 5625 for (var i = 0; i < piecePoints.length; i++) { 5626 if (Math.abs(x - piecePoints[i][0]) < margin 5627 && Math.abs(deltaYToClosestObject) > Math.abs(y - piecePoints[i][1])) { 5628 deltaYToClosestObject = y - piecePoints[i][1]; 5629 } 5630 if (Math.abs(y - piecePoints[i][1]) < margin 5631 && Math.abs(deltaXToClosestObject) > Math.abs(x - piecePoints[i][0])) { 5632 deltaXToClosestObject = x - piecePoints[i][0]; 5633 } 5634 } 5635 } 5636 }); 5637 5638 g2D.setPaint(feedbackPaint); 5639 g2D.setStroke(feedbackStroke); 5640 var alignmentLineOffset = this.pointerType === View.PointerType.TOUCH 5641 ? PlanComponent.ALIGNMENT_LINE_OFFSET * 2 5642 : PlanComponent.ALIGNMENT_LINE_OFFSET; 5643 if (deltaXToClosestObject !== Infinity) { 5644 if (deltaXToClosestObject > 0) { 5645 g2D.draw(new java.awt.geom.Line2D.Float(x + alignmentLineOffset / planScale, y, 5646 x - deltaXToClosestObject - alignmentLineOffset / planScale, y)); 5647 } else { 5648 g2D.draw(new java.awt.geom.Line2D.Float(x - alignmentLineOffset / planScale, y, 5649 x - deltaXToClosestObject + alignmentLineOffset / planScale, y)); 5650 } 5651 } 5652 if (deltaYToClosestObject !== Infinity) { 5653 if (deltaYToClosestObject > 0) { 5654 g2D.draw(new java.awt.geom.Line2D.Float(x, y + alignmentLineOffset / planScale, 5655 x, y - deltaYToClosestObject - alignmentLineOffset / planScale)); 5656 } else { 5657 g2D.draw(new java.awt.geom.Line2D.Float(x, y - alignmentLineOffset / planScale, 5658 x, y - deltaYToClosestObject + alignmentLineOffset / planScale)); 5659 } 5660 } 5661 if (showPointFeedback) { 5662 this.paintPointFeedback(g2D, locationFeedback, feedbackPaint, planScale, pointPaint, pointStroke); 5663 } 5664 } 5665 } 5666 5667 /** 5668 * Returns <code>true</code> if <code>dimensionLine</code> start or end point 5669 * equals the point (<code>x</code>, <code>y</code>). 5670 * @param {number} x 5671 * @param {number} y 5672 * @param {DimensionLine} dimensionLine 5673 * @return {boolean} 5674 * @private 5675 */ 5676 PlanComponent.prototype.equalsDimensionLinePoint = function(x, y, dimensionLine) { 5677 return x === dimensionLine.getXStart() && y === dimensionLine.getYStart() 5678 || x === dimensionLine.getXEnd() && y === dimensionLine.getYEnd(); 5679 } 5680 5681 /** 5682 * Paints an arc centered at <code>center</code> point that goes 5683 * @param {Graphics2D} g2D 5684 * @param {java.awt.geom.Point2D} center 5685 * @param {java.awt.geom.Point2D} point1 5686 * @param {java.awt.geom.Point2D} point2 5687 * @param {number} planScale 5688 * @param {string} selectionColor 5689 * @private 5690 */ 5691 PlanComponent.prototype.paintAngleFeedback = function(g2D, center, point1, point2, planScale, selectionColor) { 5692 if (!point1.equals(center) && !point2.equals(center)) { 5693 g2D.setColor(selectionColor); 5694 g2D.setStroke(new java.awt.BasicStroke(1 / planScale)); 5695 var angle1 = Math.atan2(center.getY() - point1.getY(), point1.getX() - center.getX()); 5696 if (angle1 < 0) { 5697 angle1 = 2 * Math.PI + angle1; 5698 } 5699 var angle2 = Math.atan2(center.getY() - point2.getY(), point2.getX() - center.getX()); 5700 if (angle2 < 0) { 5701 angle2 = 2 * Math.PI + angle2; 5702 } 5703 var extent = angle2 - angle1; 5704 if (angle1 > angle2) { 5705 extent = 2 * Math.PI + extent; 5706 } 5707 var previousTransform = g2D.getTransform(); 5708 g2D.translate(center.getX(), center.getY()); 5709 var radius = 20 / planScale; 5710 g2D.draw(new java.awt.geom.Arc2D.Double(-radius, -radius, 5711 radius * 2, radius * 2, angle1 * 180 / Math.PI, extent * 180 / Math.PI, java.awt.geom.Arc2D.OPEN)); 5712 radius += 5 / planScale; 5713 g2D.draw(new java.awt.geom.Line2D.Double(0, 0, radius * Math.cos(angle1), -radius * Math.sin(angle1))); 5714 g2D.draw(new java.awt.geom.Line2D.Double(0, 0, radius * Math.cos(angle1 + extent), -radius * Math.sin(angle1 + extent))); 5715 g2D.setTransform(previousTransform); 5716 } 5717 } 5718 5719 /** 5720 * Paints the observer camera at its current location, if home camera is the observer camera. 5721 * @param {Graphics2D} g2D 5722 * @param {Object[]} selectedItems 5723 * @param {string|CanvasPattern} selectionOutlinePaint 5724 * @param {java.awt.Stroke} selectionOutlineStroke 5725 * @param {string|CanvasPattern} indicatorPaint 5726 * @param {number} planScale 5727 * @param {string} backgroundColor 5728 * @param {string} foregroundColor 5729 * @private 5730 */ 5731 PlanComponent.prototype.paintCamera = function(g2D, selectedItems, selectionOutlinePaint, selectionOutlineStroke, indicatorPaint, planScale, backgroundColor, foregroundColor) { 5732 var camera = this.home.getObserverCamera(); 5733 if (camera === this.home.getCamera()) { 5734 var previousTransform = g2D.getTransform(); 5735 g2D.translate(camera.getX(), camera.getY()); 5736 g2D.rotate(camera.getYaw()); 5737 5738 var points = camera.getPoints(); 5739 var yScale = java.awt.geom.Point2D.distance(points[0][0], points[0][1], points[3][0], points[3][1]); 5740 var xScale = java.awt.geom.Point2D.distance(points[0][0], points[0][1], points[1][0], points[1][1]); 5741 var cameraTransform = java.awt.geom.AffineTransform.getScaleInstance(xScale, yScale); 5742 var cameraScale = camera.getPlanScale(); 5743 var scaledCameraBody = new java.awt.geom.Area(cameraScale <= 1 ? PlanComponent.CAMERA_HUMAN_BODY : PlanComponent.CAMERA_BODY).createTransformedArea(cameraTransform); 5744 var scaledCameraHead = new java.awt.geom.Area(cameraScale <= 1 ? PlanComponent.CAMERA_HUMAN_HEAD : PlanComponent.CAMERA_BUTTON).createTransformedArea(cameraTransform); 5745 5746 g2D.setPaint(backgroundColor); 5747 g2D.fill(scaledCameraBody); 5748 g2D.setPaint(foregroundColor); 5749 var stroke = new java.awt.BasicStroke(this.getStrokeWidth(ObserverCamera, PlanComponent.PaintMode.PAINT) / planScale); 5750 g2D.setStroke(stroke); 5751 g2D.draw(scaledCameraBody); 5752 5753 if (selectedItems.indexOf(camera) >= 0 5754 && this.selectedItemsOutlinePainted) { 5755 g2D.setPaint(selectionOutlinePaint); 5756 g2D.setStroke(selectionOutlineStroke); 5757 var cameraOutline = new java.awt.geom.Area(scaledCameraBody); 5758 cameraOutline.add(new java.awt.geom.Area(scaledCameraHead)); 5759 g2D.draw(cameraOutline); 5760 } 5761 5762 g2D.setPaint(backgroundColor); 5763 g2D.fill(scaledCameraHead); 5764 g2D.setPaint(foregroundColor); 5765 g2D.setStroke(stroke); 5766 g2D.draw(scaledCameraHead); 5767 var sin = Math.sin(camera.getFieldOfView() / 2); 5768 var cos = Math.cos(camera.getFieldOfView() / 2); 5769 var xStartAngle = (0.9 * yScale * sin); 5770 var yStartAngle = (0.9 * yScale * cos); 5771 var xEndAngle = (2.2 * yScale * sin); 5772 var yEndAngle = (2.2 * yScale * cos); 5773 var cameraFieldOfViewAngle = new java.awt.geom.GeneralPath(); 5774 cameraFieldOfViewAngle.moveTo(xStartAngle, yStartAngle); 5775 cameraFieldOfViewAngle.lineTo(xEndAngle, yEndAngle); 5776 cameraFieldOfViewAngle.moveTo(-xStartAngle, yStartAngle); 5777 cameraFieldOfViewAngle.lineTo(-xEndAngle, yEndAngle); 5778 g2D.draw(cameraFieldOfViewAngle); 5779 g2D.setTransform(previousTransform); 5780 5781 if (selectedItems.length === 1 5782 && selectedItems[0] === camera) { 5783 this.paintCameraRotationIndicators(g2D, camera, indicatorPaint, planScale); 5784 } 5785 } 5786 } 5787 5788 /** 5789 * @private 5790 */ 5791 PlanComponent.prototype.paintCameraRotationIndicators = function(g2D, camera, indicatorPaint, planScale) { 5792 if (this.resizeIndicatorVisible) { 5793 g2D.setPaint(indicatorPaint); 5794 g2D.setStroke(PlanComponent.INDICATOR_STROKE); 5795 5796 var previousTransform = g2D.getTransform(); 5797 var cameraPoints = camera.getPoints(); 5798 var scaleInverse = 1 / planScale; 5799 g2D.translate((cameraPoints[0][0] + cameraPoints[3][0]) / 2, 5800 (cameraPoints[0][1] + cameraPoints[3][1]) / 2); 5801 g2D.scale(scaleInverse, scaleInverse); 5802 g2D.rotate(camera.getYaw()); 5803 g2D.draw(this.getIndicator(camera, PlanComponent.IndicatorType.ROTATE)); 5804 g2D.setTransform(previousTransform); 5805 5806 g2D.translate((cameraPoints[1][0] + cameraPoints[2][0]) / 2, 5807 (cameraPoints[1][1] + cameraPoints[2][1]) / 2); 5808 g2D.scale(scaleInverse, scaleInverse); 5809 g2D.rotate(camera.getYaw()); 5810 g2D.draw(this.getIndicator(camera, PlanComponent.IndicatorType.ROTATE_PITCH)); 5811 g2D.setTransform(previousTransform); 5812 5813 var elevationIndicator = this.getIndicator(camera, PlanComponent.IndicatorType.ELEVATE); 5814 if (elevationIndicator != null) { 5815 g2D.translate((cameraPoints[0][0] + cameraPoints[1][0]) / 2, 5816 (cameraPoints[0][1] + cameraPoints[1][1]) / 2); 5817 g2D.scale(scaleInverse, scaleInverse); 5818 g2D.draw(PlanComponent.POINT_INDICATOR); 5819 g2D.translate(Math.sin(camera.getYaw()) * 8, -Math.cos(camera.getYaw()) * 8); 5820 g2D.draw(elevationIndicator); 5821 g2D.setTransform(previousTransform); 5822 } 5823 } 5824 } 5825 5826 /** 5827 * Paints rectangle feedback. 5828 * @param {Graphics2D} g2D 5829 * @param {string} selectionColor 5830 * @param {number} planScale 5831 * @private 5832 */ 5833 PlanComponent.prototype.paintRectangleFeedback = function(g2D, selectionColor, planScale) { 5834 if (this.rectangleFeedback != null) { 5835 g2D.setPaint(ColorTools.toRGBAStyle(selectionColor, 0.125)); 5836 g2D.fill(this.rectangleFeedback); 5837 g2D.setPaint(selectionColor); 5838 g2D.setStroke(new java.awt.BasicStroke(1 / planScale)); 5839 g2D.draw(this.rectangleFeedback); 5840 } 5841 } 5842 5843 /** 5844 * Sets rectangle selection feedback coordinates. 5845 * @param {number} x0 5846 * @param {number} y0 5847 * @param {number} x1 5848 * @param {number} y1 5849 */ 5850 PlanComponent.prototype.setRectangleFeedback = function(x0, y0, x1, y1) { 5851 this.rectangleFeedback = new java.awt.geom.Rectangle2D.Float(x0, y0, 0, 0); 5852 this.rectangleFeedback.add(x1, y1); 5853 this.repaint(); 5854 } 5855 5856 /** 5857 * Ensures selected items are visible at screen and moves 5858 * scroll bars if needed. 5859 */ 5860 PlanComponent.prototype.makeSelectionVisible = function() { 5861 if (this.isScrolled() && !this.selectionScrollUpdated) { 5862 this.selectionScrollUpdated = true; 5863 var plan = this; 5864 setTimeout(function() { 5865 plan.selectionScrollUpdated = false; 5866 var selectionBounds = plan.getSelectionBounds(true); 5867 if (selectionBounds != null) { 5868 var pixelBounds = plan.getShapePixelBounds(selectionBounds); 5869 pixelBounds = new java.awt.geom.Rectangle2D.Float(pixelBounds.getX() - 5, pixelBounds.getY() - 5, 5870 pixelBounds.getWidth() + 10, pixelBounds.getHeight() + 10); 5871 var visibleRectangle = new java.awt.geom.Rectangle2D.Float(0, 0, 5872 plan.scrollPane.clientWidth, plan.scrollPane.clientHeight); 5873 if (!pixelBounds.intersects(visibleRectangle)) { 5874 plan.scrollRectToVisible(pixelBounds); 5875 } 5876 } 5877 }); 5878 } 5879 } 5880 5881 /** 5882 * @private 5883 */ 5884 PlanComponent.prototype.scrollRectToVisible = function(rectangle) { 5885 if (this.isScrolled()) { 5886 var dx = 0; 5887 var dy = 0; 5888 if (rectangle.x < 0) { 5889 dx = rectangle.x; 5890 } else if (rectangle.getX() + rectangle.getWidth() > this.scrollPane.clientWidth) { 5891 dx = rectangle.getX() + rectangle.getWidth() - this.scrollPane.clientWidth; 5892 } 5893 if (rectangle.y < 0) { 5894 dy = rectangle.y; 5895 } else if (rectangle.getY() + rectangle.getHeight() > this.scrollPane.clientHeight) { 5896 dy = rectangle.getY() + rectangle.getHeight() - this.scrollPane.clientHeight; 5897 } 5898 this.moveView(this.convertPixelToLength(dx), this.convertPixelToLength(dy)); 5899 } 5900 } 5901 5902 /** 5903 * Returns the bounds of the selected items. 5904 * @param {boolean} includeCamera 5905 * @return {java.awt.geom.Rectangle2D} 5906 * @private 5907 */ 5908 PlanComponent.prototype.getSelectionBounds = function(includeCamera) { 5909 var g = this.getGraphics(); 5910 if (g != null) { 5911 this.setRenderingHints(g); 5912 } 5913 if (includeCamera) { 5914 return this.getItemsBounds(g, this.home.getSelectedItems()); 5915 } else { 5916 var selectedItems = this.home.getSelectedItems().slice(0); 5917 var index = selectedItems.indexOf(this.home.getCamera()); 5918 if (index >= 0) { 5919 selectedItems.splice(index, 1); 5920 } 5921 return this.getItemsBounds(g, selectedItems); 5922 } 5923 } 5924 5925 /** 5926 * Ensures the point at (<code>x</code>, <code>y</code>) is visible, 5927 * moving scroll bars if needed. 5928 * @param {number} x 5929 * @param {number} y 5930 */ 5931 PlanComponent.prototype.makePointVisible = function(x, y) { 5932 this.scrollRectToVisible(this.getShapePixelBounds( 5933 new java.awt.geom.Rectangle2D.Float(x, y, this.getPixelLength(), this.getPixelLength()))); 5934 } 5935 5936 /** 5937 * Moves the view from (dx, dy) unit in the scrolling zone it belongs to. 5938 * @param {number} dx 5939 * @param {number} dy 5940 */ 5941 PlanComponent.prototype.moveView = function(dx, dy) { 5942 if (this.isScrolled() 5943 && (dx != 0 || dy != 0)) { 5944 this.scrollPane.scrollLeft += this.convertLengthToPixel(dx); 5945 this.scrollPane.scrollTop += this.convertLengthToPixel(dy); 5946 this.repaint(); 5947 } 5948 } 5949 5950 /** 5951 * Returns the scale used to display the plan. 5952 * @return {number} 5953 */ 5954 PlanComponent.prototype.getScale = function() { 5955 return this.scale; 5956 } 5957 5958 /** 5959 * Sets the scale used to display the plan. 5960 * If this component is displayed in a scrolled panel the view position is updated 5961 * to ensure the center's view will remain the same after the scale change. 5962 * @param {number} scale 5963 */ 5964 PlanComponent.prototype.setScale = function(scale) { 5965 if (this.scale !== scale) { 5966 var xViewCenterPosition = 0; 5967 var yViewCenterPosition = 0; 5968 if (this.isScrolled()) { 5969 xViewCenterPosition = this.convertXPixelToModel(this.scrollPane.clientWidth / 2); 5970 yViewCenterPosition = this.convertYPixelToModel(this.scrollPane.clientHeight / 2); 5971 } 5972 5973 this.scale = scale; 5974 this.revalidate(); 5975 5976 if (this.isScrolled() 5977 && !isNaN(xViewCenterPosition)) { 5978 var viewWidth = this.convertPixelToLength(this.scrollPane.clientWidth); 5979 var viewHeight = this.convertPixelToLength(this.scrollPane.clientHeight); 5980 this.scrollPane.scrollLeft += this.convertXModelToPixel(xViewCenterPosition - viewWidth / 2); 5981 this.scrollPane.scrollTop += this.convertYModelToPixel(yViewCenterPosition - viewHeight / 2); 5982 } 5983 } 5984 } 5985 5986 /** 5987 * Returns <code>x</code> converted in model coordinates space. 5988 * @param {number} x 5989 * @return {number} 5990 */ 5991 PlanComponent.prototype.convertXPixelToModel = function(x) { 5992 var insets = this.getInsets(); 5993 var planBounds = this.getPlanBounds(); 5994 return this.convertPixelToLength(x - insets.left + (this.isScrolled() ? this.scrollPane.scrollLeft : 0)) - PlanComponent.MARGIN + planBounds.getMinX(); 5995 } 5996 5997 /** 5998 * Returns <code>y</code> converted in model coordinates space. 5999 * @param {number} y 6000 * @return {number} 6001 */ 6002 PlanComponent.prototype.convertYPixelToModel = function(y) { 6003 var insets = this.getInsets(); 6004 var planBounds = this.getPlanBounds(); 6005 return this.convertPixelToLength(y - insets.top + (this.isScrolled() ? this.scrollPane.scrollTop : 0)) - PlanComponent.MARGIN + planBounds.getMinY(); 6006 } 6007 6008 /** 6009 * Returns the length in model units (cm) of the given <code>size</code> in pixels. 6010 * @private 6011 */ 6012 PlanComponent.prototype.convertPixelToLength = function(size) { 6013 return size * this.getPixelLength(); 6014 } 6015 6016 /** 6017 * Returns <code>x</code> converted in view coordinates space. 6018 * @param {number} x 6019 * @return {number} 6020 * @private 6021 */ 6022 PlanComponent.prototype.convertXModelToPixel = function(x) { 6023 var insets = this.getInsets(); 6024 var planBounds = this.getPlanBounds(); 6025 return this.convertLengthToPixel(x - planBounds.getMinX() + PlanComponent.MARGIN) + insets.left - (this.isScrolled() ? this.scrollPane.scrollLeft : 0); 6026 } 6027 6028 /** 6029 * Returns <code>y</code> converted in view coordinates space. 6030 * @param {number} y 6031 * @return {number} 6032 * @private 6033 */ 6034 PlanComponent.prototype.convertYModelToPixel = function(y) { 6035 var insets = this.getInsets(); 6036 var planBounds = this.getPlanBounds(); 6037 return this.convertLengthToPixel(y - planBounds.getMinY() + PlanComponent.MARGIN) + insets.top - (this.isScrolled() ? this.scrollPane.scrollTop : 0); 6038 } 6039 6040 /** 6041 * Returns the size in pixels of the given <code>length</code> in model units (cm). 6042 * @private 6043 */ 6044 PlanComponent.prototype.convertLengthToPixel = function(length) { 6045 return Math.round(length / this.getPixelLength()); 6046 } 6047 6048 /** 6049 * Returns <code>x</code> converted in screen coordinates space. 6050 * @param {number} x 6051 * @return {number} 6052 */ 6053 PlanComponent.prototype.convertXModelToScreen = function(x) { 6054 return this.canvas.getBoundingClientRect().left + this.convertXModelToPixel(x); 6055 } 6056 6057 /** 6058 * Returns <code>y</code> converted in screen coordinates space. 6059 * @param {number} y 6060 * @return {number} 6061 */ 6062 PlanComponent.prototype.convertYModelToScreen = function(y) { 6063 return this.canvas.getBoundingClientRect().top + this.convertYModelToPixel(y); 6064 } 6065 6066 /** 6067 * Returns the length in centimeters of a pixel with the current scale. 6068 * @return {number} 6069 */ 6070 PlanComponent.prototype.getPixelLength = function() { 6071 // On contrary to Java version based on resolution scale, we use the actual scale 6072 return 1 / this.getScale(); 6073 } 6074 6075 /** 6076 * Returns the bounds of <code>shape</code> in pixels coordinates space. 6077 * @param {Object} shape 6078 * @return {java.awt.geom.Rectangle2D.Float} 6079 * @private 6080 */ 6081 PlanComponent.prototype.getShapePixelBounds = function(shape) { 6082 var shapeBounds = shape.getBounds2D(); 6083 return new java.awt.geom.Rectangle2D.Float( 6084 this.convertXModelToPixel(shapeBounds.getMinX()), 6085 this.convertYModelToPixel(shapeBounds.getMinY()), 6086 this.convertLengthToPixel(shapeBounds.getWidth()), 6087 this.convertLengthToPixel(shapeBounds.getHeight())); 6088 } 6089 6090 /** 6091 * Sets the cursor of this component. 6092 * @param {PlanView.CursorType|string} cursorType 6093 */ 6094 PlanComponent.prototype.setCursor = function(cursorType) { 6095 if (typeof cursorType === 'string') { 6096 this.canvas.style.cursor = cursorType; 6097 } else { 6098 switch (cursorType) { 6099 case PlanView.CursorType.ROTATION: 6100 case PlanView.CursorType.HEIGHT: 6101 case PlanView.CursorType.POWER: 6102 case PlanView.CursorType.ELEVATION: 6103 case PlanView.CursorType.RESIZE: 6104 if (this.mouseListener.longTouch != null) { 6105 clearTimeout(this.mouseListener.longTouch); 6106 this.mouseListener.longTouch = null; 6107 this.stopLongTouchAnimation(); 6108 } 6109 // No break; 6110 case PlanView.CursorType.MOVE: 6111 if (this.lastTouchX 6112 && this.lastTouchY) { 6113 this.startIndicatorAnimation(this.lastTouchX, this.lastTouchY, PlanView.CursorType[cursorType].toLowerCase()); 6114 } 6115 break; 6116 } 6117 6118 switch (cursorType) { 6119 case PlanView.CursorType.DRAW: 6120 this.setCursor('crosshair'); 6121 break; 6122 case PlanView.CursorType.ROTATION: 6123 this.setCursor(this.rotationCursor); 6124 break; 6125 case PlanView.CursorType.HEIGHT: 6126 this.setCursor(this.heightCursor); 6127 break; 6128 case PlanView.CursorType.POWER: 6129 this.setCursor(this.powerCursor); 6130 break; 6131 case PlanView.CursorType.ELEVATION: 6132 this.setCursor(this.elevationCursor); 6133 break; 6134 case PlanView.CursorType.RESIZE: 6135 this.setCursor(this.resizeCursor); 6136 break; 6137 case PlanView.CursorType.PANNING: 6138 this.setCursor(this.panningCursor); 6139 break; 6140 case PlanView.CursorType.DUPLICATION: 6141 this.setCursor(this.duplicationCursor); 6142 break; 6143 case PlanView.CursorType.MOVE: 6144 this.setCursor(this.moveCursor); 6145 break; 6146 case PlanView.CursorType.SELECTION: 6147 default: 6148 this.setCursor('default'); 6149 break; 6150 } 6151 } 6152 } 6153 6154 /** 6155 * Sets tool tip text displayed as feedback. 6156 * @param {string} toolTipFeedback the text displayed in the tool tip 6157 * or <code>null</code> to make tool tip disappear. 6158 */ 6159 PlanComponent.prototype.setToolTipFeedback = function(toolTipFeedback, x, y) { 6160 this.tooltip.style.width = ""; 6161 this.tooltip.style.marginLeft = ""; 6162 if (toolTipFeedback.indexOf("<html>") === 0) { 6163 this.tooltip.style.textAlign = "left"; 6164 } else { 6165 this.tooltip.style.textAlign = "center"; 6166 } 6167 this.tooltip.innerHTML = toolTipFeedback.replace("<html>", "").replace("</html>", ""); 6168 var marginTop = -(this.tooltip.clientHeight + (this.pointerType === View.PointerType.TOUCH ? 55 : 20)); 6169 this.tooltip.style.marginTop = (marginTop - (this.pointerType === View.PointerType.TOUCH && this.touchOverlay.style.visibility == "visible" ? 15 : 0)) + "px"; 6170 var width = this.tooltip.clientWidth + 10; 6171 this.tooltip.style.width = width + "px"; 6172 this.tooltip.style.marginLeft = -width / 2 + "px"; 6173 var containerRect = this.container.getBoundingClientRect(); 6174 this.tooltip.style.left = Math.max(5 + width / 2, 6175 Math.min(window.innerWidth - width / 2 - 10, containerRect.left + this.convertXModelToPixel(x))) + "px"; 6176 var top = containerRect.top + this.convertYModelToPixel(y); 6177 this.tooltip.style.top = (top + marginTop > 15 ? top : top - marginTop + (this.pointerType === View.PointerType.TOUCH ? 100 : 20)) + "px"; 6178 this.tooltip.style.visibility = "visible"; 6179 } 6180 6181 /** 6182 * Set tool tip edition. 6183 * @param {Array} toolTipEditedProperties 6184 * @param {Array} toolTipPropertyValues 6185 * @param {number} x 6186 * @param {number} y 6187 */ 6188 PlanComponent.prototype.setToolTipEditedProperties = function(toolTipEditedProperties, toolTipPropertyValues, x, y) { 6189 // TODO 6190 } 6191 6192 /** 6193 * Deletes tool tip text from screen. 6194 */ 6195 PlanComponent.prototype.deleteToolTipFeedback = function() { 6196 this.tooltip.style.visibility = "hidden"; 6197 this.tooltip.style.width = ""; 6198 this.tooltip.style.marginLeft = ""; 6199 this.tooltip.style.marginTop = ""; 6200 } 6201 6202 /** 6203 * Sets whether the resize indicator of selected wall or piece of furniture 6204 * should be visible or not. 6205 * @param {boolean} resizeIndicatorVisible 6206 */ 6207 PlanComponent.prototype.setResizeIndicatorVisible = function(resizeIndicatorVisible) { 6208 this.resizeIndicatorVisible = resizeIndicatorVisible; 6209 this.repaint(); 6210 } 6211 6212 /** 6213 * Sets the location point for alignment feedback. 6214 * @param {Object} alignedObjectClass 6215 * @param {Object} alignedObject 6216 * @param {number} x 6217 * @param {number} y 6218 * @param {boolean} showPointFeedback 6219 */ 6220 PlanComponent.prototype.setAlignmentFeedback = function(alignedObjectClass, alignedObject, x, y, showPointFeedback) { 6221 this.alignedObjectClass = alignedObjectClass; 6222 this.alignedObjectFeedback = alignedObject; 6223 this.locationFeeback = new java.awt.geom.Point2D.Float(x, y); 6224 this.showPointFeedback = showPointFeedback; 6225 this.repaint(); 6226 } 6227 6228 /** 6229 * Sets the points used to draw an angle in plan view. 6230 * @param {number} xCenter 6231 * @param {number} yCenter 6232 * @param {number} x1 6233 * @param {number} y1 6234 * @param {number} x2 6235 * @param {number} y2 6236 */ 6237 PlanComponent.prototype.setAngleFeedback = function(xCenter, yCenter, x1, y1, x2, y2) { 6238 this.centerAngleFeedback = new java.awt.geom.Point2D.Float(xCenter, yCenter); 6239 this.point1AngleFeedback = new java.awt.geom.Point2D.Float(x1, y1); 6240 this.point2AngleFeedback = new java.awt.geom.Point2D.Float(x2, y2); 6241 } 6242 6243 /** 6244 * Sets the feedback of dragged items drawn during a drag and drop operation, 6245 * initiated from outside of plan view. 6246 * @param {Object[]} draggedItems 6247 */ 6248 PlanComponent.prototype.setDraggedItemsFeedback = function(draggedItems) { 6249 this.draggedItemsFeedback = draggedItems; 6250 this.repaint(); 6251 } 6252 6253 /** 6254 * Sets the given dimension lines to be drawn as feedback. 6255 * @param {DimensionLine[]} dimensionLines 6256 */ 6257 PlanComponent.prototype.setDimensionLinesFeedback = function(dimensionLines) { 6258 this.dimensionLinesFeedback = dimensionLines; 6259 this.repaint(); 6260 } 6261 6262 /** 6263 * Deletes all elements shown as feedback. 6264 */ 6265 PlanComponent.prototype.deleteFeedback = function() { 6266 this.deleteToolTipFeedback(); 6267 this.rectangleFeedback = null; 6268 this.alignedObjectClass = null; 6269 this.alignedObjectFeedback = null; 6270 this.locationFeeback = null; 6271 this.centerAngleFeedback = null; 6272 this.point1AngleFeedback = null; 6273 this.point2AngleFeedback = null; 6274 this.draggedItemsFeedback = null; 6275 this.dimensionLinesFeedback = null; 6276 this.repaint(); 6277 } 6278 6279 /** 6280 * Returns <code>true</code>. 6281 * @param {Object[]} items 6282 * @param {number} x 6283 * @param {number} y 6284 * @return {boolean} 6285 */ 6286 PlanComponent.prototype.canImportDraggedItems = function(items, x, y) { 6287 return true; 6288 } 6289 6290 /** 6291 * Returns the size of the given piece of furniture in the horizontal plan, 6292 * or <code>null</code> if the view isn't able to compute such a value. 6293 * @param {HomePieceOfFurniture} piece 6294 * @return {Array} 6295 */ 6296 PlanComponent.prototype.getPieceOfFurnitureSizeInPlan = function(piece) { 6297 if (piece.getRoll() === 0 && piece.getPitch() === 0) { 6298 return [piece.getWidth(), piece.getDepth(), piece.getHeight()]; 6299 } 6300 else if (!this.isFurnitureSizeInPlanSupported()) { 6301 return null; 6302 } 6303 else { 6304 return PlanComponent.PieceOfFurnitureModelIcon.computePieceOfFurnitureSizeInPlan(piece, this.object3dFactory); 6305 } 6306 } 6307 6308 /** 6309 * Returns <code>true</code> if this component is able to compute the size of horizontally rotated furniture. 6310 * @return {boolean} 6311 */ 6312 PlanComponent.prototype.isFurnitureSizeInPlanSupported = function() { 6313 return PlanComponent.WEBGL_AVAILABLE; 6314 } 6315 6316 /** 6317 * Removes components added to this pane and their listeners. 6318 */ 6319 PlanComponent.prototype.dispose = function() { 6320 this.container.removeEventListener("keydown", this.keyDownListener, false); 6321 this.container.removeEventListener("keyup", this.keyUpListener, false); 6322 this.container.removeEventListener("focusout", this.focusOutListener); 6323 if (OperatingSystem.isInternetExplorerOrLegacyEdge() 6324 && window.PointerEvent) { 6325 window.removeEventListener("pointermove", this.mouseListener.windowPointerMoved); 6326 window.removeEventListener("pointerup", this.mouseListener.windowPointerReleased); 6327 } else { 6328 window.removeEventListener("mousemove", this.mouseListener.windowMouseMoved); 6329 window.removeEventListener("mouseup", this.mouseListener.windowMouseReleased); 6330 } 6331 document.body.removeChild(this.touchOverlay); 6332 document.body.removeChild(this.tooltip); 6333 window.removeEventListener("resize", this.windowResizeListener); 6334 if (this.scrollPane != null) { 6335 this.container.removeChild(this.canvas); 6336 this.container.removeChild(this.scrollPane); 6337 } 6338 this.preferences.removePropertyChangeListener("UNIT", this.preferencesListener); 6339 this.preferences.removePropertyChangeListener("LANGUAGE", this.preferencesListener); 6340 this.preferences.removePropertyChangeListener("GRID_VISIBLE", this.preferencesListener); 6341 this.preferences.removePropertyChangeListener("DEFAULT_FONT_NAME", this.preferencesListener); 6342 this.preferences.removePropertyChangeListener("FURNITURE_VIEWED_FROM_TOP", this.preferencesListener); 6343 this.preferences.removePropertyChangeListener("FURNITURE_MODEL_ICON_SIZE", this.preferencesListener); 6344 this.preferences.removePropertyChangeListener("ROOM_FLOOR_COLORED_OR_TEXTURED", this.preferencesListener); 6345 this.preferences.removePropertyChangeListener("WALL_PATTERN", this.preferencesListener); 6346 } 6347 6348 /** 6349 * Returns the component used as an horizontal ruler for this plan. 6350 * @return {Object} 6351 */ 6352 PlanComponent.prototype.getHorizontalRuler = function() { 6353 throw new UnsupportedOperationException("No rulers"); 6354 } 6355 6356 /** 6357 * Returns the component used as a vertical ruler for this plan. 6358 * @return {Object} 6359 */ 6360 PlanComponent.prototype.getVerticalRuler = function() { 6361 throw new UnsupportedOperationException("No rulers"); 6362 } 6363 6364 6365 /** 6366 * A proxy for the furniture icon seen from top. 6367 * @param {Image} icon 6368 * @constructor 6369 * @private 6370 */ 6371 PlanComponent.PieceOfFurnitureTopViewIcon = function(image) { 6372 this.image = image; 6373 } 6374 6375 /** 6376 * @ignore 6377 */ 6378 PlanComponent.PieceOfFurnitureTopViewIcon.prototype.getIconWidth = function() { 6379 return this.image.naturalWidth; 6380 } 6381 6382 /** 6383 * @ignore 6384 */ 6385 PlanComponent.PieceOfFurnitureTopViewIcon.prototype.getIconHeight = function() { 6386 return this.image.naturalHeight; 6387 } 6388 6389 /** 6390 * @ignore 6391 */ 6392 PlanComponent.PieceOfFurnitureTopViewIcon.prototype.paintIcon = function(g, x, y) { 6393 g.drawImage(this.image, x, y); 6394 } 6395 6396 /** 6397 * @ignore 6398 */ 6399 PlanComponent.PieceOfFurnitureTopViewIcon.prototype.isWaitIcon = function() { 6400 return this.image === TextureManager.getInstance().getWaitImage(); 6401 } 6402 6403 /** 6404 * @ignore 6405 */ 6406 PlanComponent.PieceOfFurnitureTopViewIcon.prototype.isErrorIcon = function() { 6407 return this.image === TextureManager.getInstance().getErrorImage(); 6408 } 6409 6410 /** 6411 * @ignore 6412 */ 6413 PlanComponent.PieceOfFurnitureTopViewIcon.prototype.setIcon = function(image) { 6414 this.image = image; 6415 } 6416 6417 6418 /** 6419 * Creates a plan icon proxy for a <code>piece</code> of furniture. 6420 * @param {HomePieceOfFurniture} piece an object containing a plan icon content 6421 * @param {java.awt.Component} waitingComponent a waiting component. If <code>null</code>, the returned icon will 6422 * be read immediately in the current thread. 6423 * @constructor 6424 * @extends PlanComponent.PieceOfFurnitureTopViewIcon 6425 * @private 6426 */ 6427 PlanComponent.PieceOfFurniturePlanIcon = function(piece, waitingComponent) { 6428 PlanComponent.PieceOfFurnitureTopViewIcon.call(this, TextureManager.getInstance().getWaitImage()); 6429 var planIcon = this; 6430 TextureManager.getInstance().loadTexture(piece.getPlanIcon(), false, { 6431 textureUpdated: function(textureImage) { 6432 planIcon.setIcon(textureImage); 6433 waitingComponent.repaint(); 6434 }, 6435 textureError: function(error) { 6436 planIcon.setIcon(TextureManager.getInstance().getErrorImage()); 6437 waitingComponent.repaint(); 6438 } 6439 }); 6440 } 6441 PlanComponent.PieceOfFurniturePlanIcon.prototype = Object.create(PlanComponent.PieceOfFurnitureTopViewIcon.prototype); 6442 PlanComponent.PieceOfFurniturePlanIcon.prototype.constructor = PlanComponent.PieceOfFurniturePlanIcon; 6443 6444 6445 /** 6446 * Creates a top view icon proxy for a <code>piece</code> of furniture. 6447 * @param {HomePieceOfFurniture} piece an object containing a 3D content 6448 * @param {Object} object3dFactory a factory with a <code>createObject3D(home, item, waitForLoading)</code> method 6449 * @param {Object} waitingComponent a waiting component. If <code>null</code>, the returned icon will 6450 * be read immediately in the current thread. 6451 * @param {number} iconSize the size in pixels of the generated icon 6452 * @constructor 6453 * @extends PlanComponent.PieceOfFurnitureTopViewIcon 6454 * @private 6455 */ 6456 PlanComponent.PieceOfFurnitureModelIcon = function(piece, object3dFactory, waitingComponent, iconSize) { 6457 PlanComponent.PieceOfFurnitureTopViewIcon.call(this, TextureManager.getInstance().getWaitImage()); 6458 var modelIcon = this; 6459 ModelManager.getInstance().loadModel(piece.getModel(), waitingComponent === null, { 6460 modelUpdated: function(modelRoot) { 6461 var normalizedPiece = piece.clone(); 6462 if (normalizedPiece.isResizable() 6463 && piece.getRoll() == 0) { 6464 normalizedPiece.setModelMirrored(false); 6465 } 6466 var pieceWidth = normalizedPiece.getWidthInPlan(); 6467 var pieceDepth = normalizedPiece.getDepthInPlan(); 6468 var pieceHeight = normalizedPiece.getHeightInPlan(); 6469 normalizedPiece.setX(0); 6470 normalizedPiece.setY(0); 6471 normalizedPiece.setElevation(-pieceHeight / 2); 6472 normalizedPiece.setLevel(null); 6473 normalizedPiece.setAngle(0); 6474 if (waitingComponent !== null) { 6475 var updater = function() { 6476 object3dFactory.createObject3D(null, normalizedPiece, 6477 function(pieceNode) { 6478 modelIcon.createIcon(pieceNode, pieceWidth, pieceDepth, pieceHeight, iconSize, 6479 function(icon) { 6480 modelIcon.setIcon(icon); 6481 waitingComponent.repaint(); 6482 }); 6483 }); 6484 }; 6485 setTimeout(updater, 0); 6486 } else { 6487 modelIcon.setIcon(modelIcon.createIcon(object3dFactory.createObject3D(null, normalizedPiece, true), pieceWidth, pieceDepth, pieceHeight, iconSize)); 6488 } 6489 }, 6490 modelError: function(ex) { 6491 // In case of problem use a default red box 6492 modelIcon.setIcon(TextureManager.getInstance().getErrorImage()); 6493 if (waitingComponent !== null) { 6494 waitingComponent.repaint(); 6495 } 6496 } 6497 }); 6498 } 6499 PlanComponent.PieceOfFurnitureModelIcon.prototype = Object.create(PlanComponent.PieceOfFurnitureTopViewIcon.prototype); 6500 PlanComponent.PieceOfFurnitureModelIcon.prototype.constructor = PlanComponent.PieceOfFurnitureModelIcon; 6501 6502 /** 6503 * Returns the branch group bound to a universe and a canvas for the given 6504 * resolution. 6505 * @param {number} iconSize 6506 * @return {BranchGroup3D} 6507 * @private 6508 */ 6509 PlanComponent.PieceOfFurnitureModelIcon.prototype.getSceneRoot = function(iconSize) { 6510 if (!PlanComponent.PieceOfFurnitureModelIcon.canvas3D 6511 || !PlanComponent.PieceOfFurnitureModelIcon.canvas3D [iconSize]) { 6512 var canvas = document.createElement("canvas"); 6513 canvas.width = iconSize; 6514 canvas.height = iconSize; 6515 canvas.style.backgroundColor = "rgba(0, 0, 0, 0)"; 6516 var canvas3D = new HTMLCanvas3D(canvas); 6517 var rotation = mat4.create(); 6518 mat4.fromXRotation(rotation, -Math.PI / 2); 6519 canvas3D.setViewPlatformTransform(rotation); 6520 canvas3D.setProjectionPolicy(HTMLCanvas3D.PARALLEL_PROJECTION); 6521 canvas3D.setFrontClipDistance(-1.1); 6522 canvas3D.setBackClipDistance(1.1); 6523 var sceneRoot = new BranchGroup3D(); 6524 sceneRoot.setCapability(Group3D.ALLOW_CHILDREN_EXTEND); 6525 var lights = [ 6526 new DirectionalLight3D(vec3.fromValues(0.6, 0.6, 0.6), vec3.fromValues(1.5, -0.8, -1)), 6527 new DirectionalLight3D(vec3.fromValues(0.6, 0.6, 0.6), vec3.fromValues(-1.5, -0.8, -1)), 6528 new DirectionalLight3D(vec3.fromValues(0.6, 0.6, 0.6), vec3.fromValues(0, -0.8, 1)), 6529 new AmbientLight3D(vec3.fromValues(0.2, 0.2, 0.2))]; 6530 for (var i = 0; i < lights.length; i++) { 6531 sceneRoot.addChild(lights[i]); 6532 } 6533 canvas3D.setScene(sceneRoot); 6534 if (!PlanComponent.PieceOfFurnitureModelIcon.canvas3D) { 6535 PlanComponent.PieceOfFurnitureModelIcon.canvas3D = {}; 6536 } 6537 PlanComponent.PieceOfFurnitureModelIcon.canvas3D [iconSize] = canvas3D; 6538 } 6539 6540 if (iconSize !== 128) { 6541 // Keep only canvas for 128 (default) size and the requested icon size 6542 for (var key in PlanComponent.PieceOfFurnitureModelIcon.canvas3D) { 6543 if (key != 128 6544 && key != iconSize 6545 && PlanComponent.PieceOfFurnitureModelIcon.canvas3D.hasOwnProperty(key)) { 6546 PlanComponent.PieceOfFurnitureModelIcon.canvas3D [key].clear(); 6547 delete PlanComponent.PieceOfFurnitureModelIcon.canvas3D [key]; 6548 } 6549 } 6550 } 6551 return PlanComponent.PieceOfFurnitureModelIcon.canvas3D [iconSize].getScene(); 6552 } 6553 6554 /** 6555 * Creates an icon created and scaled from piece model content, and calls <code>iconObserver</code> once the icon is ready 6556 * or returns the icon itself if <code>iconObserver</code> is not given. 6557 * @param {Object3DBranch} pieceNode 6558 * @param {number} pieceWidth 6559 * @param {number} pieceDepth 6560 * @param {number} pieceHeight 6561 * @param {number} iconSize 6562 * @param {Object} [iconObserver] a function that will receive the icon as parameter 6563 * @return {Image} the icon or <code>undefined</code> if <code>iconObserver</code> exists 6564 * @private 6565 */ 6566 PlanComponent.PieceOfFurnitureModelIcon.prototype.createIcon = function(pieceNode, pieceWidth, pieceDepth, pieceHeight, iconSize, iconObserver) { 6567 var scaleTransform = mat4.create(); 6568 mat4.scale(scaleTransform, scaleTransform, vec3.fromValues(2 / pieceWidth, 2 / pieceHeight, 2 / pieceDepth)); 6569 var modelTransformGroup = new TransformGroup3D(); 6570 modelTransformGroup.setTransform(scaleTransform); 6571 if (pieceNode.getParent() != null) { 6572 pieceNode.getParent().removeChild(pieceNode); 6573 } 6574 modelTransformGroup.addChild(pieceNode); 6575 var model = new BranchGroup3D(); 6576 model.addChild(modelTransformGroup); 6577 var sceneRoot = this.getSceneRoot(iconSize); 6578 if (iconObserver) { 6579 var observingStart = Date.now(); 6580 var iconGeneration = function() { 6581 sceneRoot.addChild(model); 6582 var loadingCompleted = PlanComponent.PieceOfFurnitureModelIcon.canvas3D [iconSize].isLoadingCompleted(); 6583 if (loadingCompleted || (Date.now() - observingStart) > 5000) { 6584 PlanComponent.PieceOfFurnitureModelIcon.canvas3D [iconSize].getImage(iconObserver); 6585 } 6586 sceneRoot.removeChild(model); 6587 if (!loadingCompleted) { 6588 setTimeout(iconGeneration, 0); 6589 } 6590 }; 6591 iconGeneration(); 6592 return undefined; 6593 } 6594 else { 6595 sceneRoot.addChild(model); 6596 var icon = PlanComponent.PieceOfFurnitureModelIcon.canvas3D [iconSize].getImage(); 6597 sceneRoot.removeChild(model); 6598 return icon; 6599 } 6600 } 6601 6602 /** 6603 * Returns the size of the given piece computed from its vertices. 6604 * @param {HomePieceOfFurniture} piece 6605 * @param {Object} object3dFactory 6606 * @return {Array} 6607 * @private 6608 */ 6609 PlanComponent.PieceOfFurnitureModelIcon.computePieceOfFurnitureSizeInPlan = function(piece, object3dFactory) { 6610 var horizontalRotation = mat4.create(); 6611 if (piece.getPitch() !== 0) { 6612 mat4.fromXRotation(horizontalRotation, -piece.getPitch()); 6613 } 6614 if (piece.getRoll() !== 0) { 6615 var rollRotation = mat4.create(); 6616 mat4.fromZRotation(rollRotation, -piece.getRoll()); 6617 mat4.mul(horizontalRotation, horizontalRotation, rollRotation, horizontalRotation); 6618 } 6619 // Compute bounds of a piece centered at the origin and rotated around the target horizontal angle 6620 piece = piece.clone(); 6621 piece.setX(0); 6622 piece.setY(0); 6623 piece.setElevation(-piece.getHeight() / 2); 6624 piece.setLevel(null); 6625 piece.setAngle(0); 6626 piece.setRoll(0); 6627 piece.setPitch(0); 6628 piece.setWidthInPlan(piece.getWidth()); 6629 piece.setDepthInPlan(piece.getDepth()); 6630 piece.setHeightInPlan(piece.getHeight()); 6631 var bounds = ModelManager.getInstance().getBounds(object3dFactory.createObject3D(null, piece, true), horizontalRotation); 6632 var lower = vec3.create(); 6633 bounds.getLower(lower); 6634 var upper = vec3.create(); 6635 bounds.getUpper(upper); 6636 return [Math.max(0.001, (upper[0] - lower[0])), 6637 Math.max(0.001, (upper[2] - lower[2])), 6638 Math.max(0.001, (upper[1] - lower[1]))]; 6639 } 6640 6641 6642 /** 6643 * A map key used to compare furniture with the same top view icon. 6644 * @param {HomePieceOfFurniture} piece 6645 * @constructor 6646 * @private 6647 */ 6648 PlanComponent.HomePieceOfFurnitureTopViewIconKey = function(piece) { 6649 this.piece = piece; 6650 this.hashCode = (piece.getPlanIcon() != null ? piece.getPlanIcon().hashCode() : piece.getModel().hashCode()) 6651 + (piece.getColor() != null ? 37 * piece.getColor() : 1234); 6652 if (piece.isHorizontallyRotated() 6653 || piece.getTexture() != null) { 6654 this.hashCode += 6655 (piece.getTexture() != null ? 37 * piece.getTexture().hashCode() : 0) 6656 + 37 * (piece.getWidthInPlan() | 0) 6657 + 37 * (piece.getDepthInPlan() | 0) 6658 + 37 * (piece.getHeightInPlan() | 0); 6659 } 6660 if (piece.getRoll() != 0) { 6661 this.hashCode += 37 * (piece.isModelMirrored() ? 1231 : 1237); 6662 } 6663 if (piece.getPlanIcon() != null) { 6664 this.hashCode += 6665 37 * (function(matrix) { 6666 return (31 * matrix[0][0] + 31 * matrix[0][1] + 31 * matrix[0][2] 6667 + 37 * matrix[1][0] + 37 * matrix[1][1] + 37 * matrix[1][2] 6668 + 41 * matrix[2][0] + 41 * matrix[2][1] + 41 * matrix[2][2]) | 0; })(piece.getModelRotation()) 6669 + 37 * (piece.isModelCenteredAtOrigin() ? 1 : 0) 6670 + 37 * (piece.isBackFaceShown() ? 1 : 0) 6671 + 37 * ((piece.getPitch() * 1000) | 0) 6672 + 37 * ((piece.getRoll() * 1000) | 0) 6673 + 37 * (function(array) { 6674 var hashCode = 0; 6675 if (array != null) { 6676 for (var i = 0; i < array.length; i++) { 6677 if (array [i] != null) { 6678 hashCode += 37 * array [i].hashCode(); 6679 } 6680 } 6681 } 6682 return hashCode | 0; })(piece.getModelTransformations()) 6683 + 37 * (function(array) { 6684 var hashCode = 0; 6685 if (array != null) { 6686 for (var i = 0; i < array.length; i++) { 6687 if (array [i] != null) { 6688 hashCode += 37 * array [i].hashCode(); 6689 } 6690 } 6691 } 6692 return hashCode | 0; })(piece.getModelMaterials()) 6693 + (piece.getShininess() != null ? 37 * ((piece.getShininess() * 1000) | 0) : 3456); 6694 } 6695 } 6696 6697 /** 6698 * @param {Object} obj 6699 * @return {boolean} 6700 * @private 6701 */ 6702 PlanComponent.HomePieceOfFurnitureTopViewIconKey.prototype.equals = function(obj) { 6703 if (obj instanceof PlanComponent.HomePieceOfFurnitureTopViewIconKey) { 6704 var piece2 = obj.piece; 6705 // Test all furniture data that could make change the plan icon 6706 // (see HomePieceOfFurniture3D and PlanComponent#addModelListeners for changes conditions) 6707 return (this.piece.getPlanIcon() != null 6708 ? this.piece.getPlanIcon().equals(piece2.getPlanIcon()) 6709 : this.piece.getModel().equals(piece2.getModel())) 6710 && (this.piece.getColor() == piece2.getColor()) 6711 && (this.piece.getTexture() == piece2.getTexture() 6712 || this.piece.getTexture() != null && this.piece.getTexture().equals(piece2.getTexture())) 6713 && (!this.piece.isHorizontallyRotated() 6714 && !piece2.isHorizontallyRotated() 6715 && this.piece.getTexture() == null 6716 && piece2.getTexture() == null 6717 || this.piece.getWidthInPlan() == piece2.getWidthInPlan() 6718 && this.piece.getDepthInPlan() == piece2.getDepthInPlan() 6719 && this.piece.getHeightInPlan() == piece2.getHeightInPlan()) 6720 && (this.piece.getRoll() == 0 6721 && piece2.getRoll() == 0 6722 || this.piece.isModelMirrored() == piece2.isModelMirrored()) 6723 && (this.piece.getPlanIcon() != null 6724 || (function(matrix1, matrix2) { 6725 return matrix1[0][0] == matrix2[0][0] && matrix1[0][1] == matrix2[0][1] && matrix1[0][2] == matrix2[0][2] 6726 && matrix1[1][0] == matrix2[1][0] && matrix1[1][1] == matrix2[1][1] && matrix1[1][2] == matrix2[1][2] 6727 && matrix1[2][0] == matrix2[2][0] && matrix1[2][1] == matrix2[2][1] && matrix1[2][2] == matrix2[2][2]; })(this.piece.getModelRotation(), piece2.getModelRotation()) 6728 && this.piece.isModelCenteredAtOrigin() == piece2.isModelCenteredAtOrigin() 6729 && this.piece.isBackFaceShown() == piece2.isBackFaceShown() 6730 && this.piece.getPitch() == piece2.getPitch() 6731 && this.piece.getRoll() == piece2.getRoll() 6732 && (function(array1, array2) { 6733 if (array1 === array2) return true; 6734 if (array1 == null || array2 == null || array1.length !== array2.length) return false; 6735 for (var i = 0; i < array1.length; i++) { 6736 if (array1[i] !== array2[i] && (array1[i] == null || !array1[i].equals(array2[i]))) return false; 6737 } 6738 return true; })(this.piece.getModelTransformations(), piece2.getModelTransformations()) 6739 && (function(array1, array2) { 6740 if (array1 === array2) return true; 6741 if (array1 == null || array2 == null || array1.length !== array2.length) return false; 6742 for (var i = 0; i < array1.length; i++) { 6743 if (array1[i] !== array2[i] && (array1[i] == null || !array1[i].equals(array2[i]))) return false; 6744 } 6745 return true; })(this.piece.getModelMaterials(), piece2.getModelMaterials()) 6746 && this.piece.getShininess() == piece2.getShininess()); 6747 } else { 6748 return false; 6749 } 6750 } 6751 6752 /** 6753 * @private 6754 */ 6755 PlanComponent.HomePieceOfFurnitureTopViewIconKey.prototype.hashCode = function() { 6756 return this.hashCode; 6757 } 6758