/* * Copyright (C) 1998-2018 by Northwoods Software Corporation * All Rights Reserved. * * FLOOR PLANN UI CLASS * Handle GUI manipulation (showing/changing data, populating windows, etc) for Floorplanner.html */ /* * Floorplan UI Constructor * @param {Floorplan} floorplan A reference to a valid instance of Floorplan * @param {String} name The name of this FloorplanUI instance known to the DOM * @param {String} The name of this UI's floorplan known to the DOM * @param {Object} state A JSON object with string ids for UI HTML elements. Format is as follows: menuButtons: { selectionInfoWindowButtonId: palettesWindowButtonId: overviewWindowButtonId: optionsWindowButtonId: statisticsWindowButtonId: } windows: { diagramHelpDiv: { id: } selectionInfoWindow: { id: textDivId: handleId: colorPickerId: heightLabelId: heightInputId: widthInputId: nodeGroupInfoId: nameInputId: notesTextareaId: } palettesWindow:{ id: furnitureSearchInputId: furniturePaletteId: } overviewWindow: { id: } optionsWindow: { id: gridSizeInputId: unitsConversionFactorInputId: unitsFormId: unitsFormName: checkboxes: { showGridCheckboxId: gridSnapCheckboxId: wallGuidelinesCheckboxId: wallLengthsCheckboxId: wallAnglesCheckboxId: smallWallAnglesCheckboxId: } } statisticsWindow: { id: textDivId: numsTableId: totalsTableId: } } scaleDisplayId: setBehaviorClass: wallThicknessInputId: wallThicknessBoxId: unitsBoxId: unitsInputId: */ function FloorplanUI(floorplan, name, floorplanName, state) { this._floorplan = floorplan; this._name = name; this._floorplanName = floorplanName; this._state = state; this._furnitureNodeData = null; // used for searchFurniture function. set only once this.floorplan.floorplanUI = this; } // Get Floorplan associated with this UI Object.defineProperty(FloorplanUI.prototype, "floorplan", { get: function () { return this._floorplan; } }); // Get state object containing many ids of various UI elements Object.defineProperty(FloorplanUI.prototype, "state", { get: function () { return this._state; } }); // Get name of this FloorplanUI instance known to the DOM Object.defineProperty(FloorplanUI.prototype, "name", { get: function () { return this._name; } }); // Get name of the Floorplan associated with this FloorplanUI instance known to the DOM Object.defineProperty(FloorplanUI.prototype, "floorplanName", { get: function () { return this._floorplanName; } }); Object.defineProperty(FloorplanUI.prototype, "furnitureData", { get: function () { return this._furnitureData; }, set: function (val) { this._furnitureData = val; } }); /* * UI manipulation: * Hide/Show Element, Adjust Scale, ChangeGridSize, Change Units Conversion Factor * Search Furniture, Checkbox Changed, Change Units, Set Behavior, Update UI */ /* * Hide or show specific help/windows (used mainly with hotkeys) * @param {String} id The ID of the window to show / hide */ FloorplanUI.prototype.hideShow = function(id) { var element = document.getElementById(id); var str; var windows = this.state.windows; switch (id) { case windows.diagramHelpDiv.id: str = 'Diagram Help'; char = 'H'; break; case windows.selectionInfoWindow.id: str = 'Selection Help'; char = 'I'; break; case windows.overviewWindow.id: str = 'Overview'; char = 'E'; break; case windows.optionsWindow.id: str = 'Options'; char = 'B'; break; case windows.statisticsWindow.id: str = 'Statistics'; char = 'G'; break; case windows.palettesWindow.id: str = 'Palettes'; char = 'P'; { furniturePalette.layoutDiagram(true); wallPartsPalette.layoutDiagram(true); break; } } var button = document.getElementById(id + 'Button'); element.style.visibility = element.style.visibility === "visible" ? "hidden" : "visible"; var verb = element.style.visibility === "visible" ? "Hide " : "Show "; if (button) button.innerHTML = verb + str + "
(Ctrl + " + char + " )
"; } /* * Set text under Diagram to suggest most common functions user could perform * @param {String} str The text to display in the Diagram Help div */ FloorplanUI.prototype.setDiagramHelper = function(str) { var helper = document.getElementById(this.state.windows.diagramHelpDiv.id); if (helper) helper.innerHTML = '' + str + '
'; } /* * Increase / decrease diagram scale to the nearest 10% * @param {String} sign Accepted values are "+" and "-" */ FloorplanUI.prototype.adjustScale = function(sign) { var floorplan = this.floorplan; var el = document.getElementById(this.state.scaleDisplayId); floorplan.startTransaction('Change Scale'); switch (sign) { case '-': floorplan.scale -= .1; break; case '+': floorplan.scale += .1; break; } floorplan.scale = parseFloat((Math.round(floorplan.scale / .1) * .1).toFixed(2)); var scale = (floorplan.scale * 100).toFixed(2); el.innerHTML = 'Scale: ' + scale + '%'; floorplan.commitTransaction('Change Scale'); } // Change edge length of the grid based on input FloorplanUI.prototype.changeGridSize = function () { var floorplan = this.floorplan; floorplan.skipsUndoManager = true; floorplan.startTransaction("change grid size"); var el = document.getElementById(this.state.windows.optionsWindow.gridSizeInputId); var input; if (!isNaN(el.value) && el.value != null && el.value != '' && el.value != undefined && el.value > 1) input = parseFloat(el.value); else { el.value = floorplan.convertPixelsToUnits(10); // if bad input given, revert to 20cm (10px) or unit equivalent input = parseFloat(el.value); } input = floorplan.convertUnitsToPixels(input); floorplan.grid.gridCellSize = new go.Size(input, input); floorplan.toolManager.draggingTool.gridCellSize = new go.Size(input, input); floorplan.model.setDataProperty(floorplan.model.modelData, "gridSize", input); floorplan.commitTransaction("change grid size"); floorplan.skipsUndoManager = false; } FloorplanUI.prototype.changeUnitsConversionFactor = function () { var floorplan = this.floorplan; var val = document.getElementById(this.state.windows.optionsWindow.unitsConversionFactorInputId).value; if (isNaN(val) || !val || val == undefined) return; floorplan.skipsUndoManager = true; floorplan.model.set(floorplan.model.modelData, "unitsConversionFactor", val); floorplan.skipsUndoManager = false; } // Search through all elements in the furniture palette (useful for a palette with many furniture nodes) FloorplanUI.prototype.searchFurniture = function () { var ui = this; var floorplan = this.floorplan; var furniturePaletteId = ui.state.windows.palettesWindow.furniturePaletteId; var str = document.getElementById(ui.state.windows.palettesWindow.furnitureSearchInputId).value; var furniturePalette = null; for (var i = 0; i < floorplan.palettes.length; i++) { var palette = floorplan.palettes[i]; if (palette.div.id == furniturePaletteId) { furniturePalette = floorplan.palettes[i]; } } if (ui.furnitureData == null) ui.furnitureData = furniturePalette.model.nodeDataArray; var items = furniturePalette.model.nodeDataArray.slice(); if (str !== null && str !== undefined && str !== "") { for (var i = 0; i < items.length; i += 0) { var item = items[i]; if (item.type.toLowerCase().indexOf(str.toLowerCase()) === -1) { items.splice(i, 1); } else i++; } furniturePalette.model.nodeDataArray = items; } else furniturePalette.model.nodeDataArray = ui.furnitureData; furniturePalette.updateAllRelationshipsFromData(); } /* * Change the "checked" value of checkboxes in the Options Menu, and to have those changes reflected in app behavior / model data * @param {String} id The ID of the changed checkbox */ FloorplanUI.prototype.checkboxChanged = function (id) { var floorplan = this.floorplan; var checkboxes = this.state.windows.optionsWindow.checkboxes; floorplan.skipsUndoManager = true; floorplan.startTransaction("change preference"); var element = document.getElementById(id); switch (id) { case checkboxes.showGridCheckboxId: { floorplan.grid.visible = element.checked; floorplan.model.modelData.preferences.showGrid = element.checked; break; } case checkboxes.gridSnapCheckboxId: { floorplan.toolManager.draggingTool.isGridSnapEnabled = element.checked; floorplan.model.modelData.preferences.gridSnap = element.checked; break; } case checkboxes.wallGuidelinesCheckboxId: floorplan.model.modelData.preferences.showWallGuidelines = element.checked; break; case checkboxes.wallLengthsCheckboxId: floorplan.model.modelData.preferences.showWallLengths = element.checked; floorplan.updateWallDimensions(); break; case checkboxes.wallAnglesCheckboxId: floorplan.model.modelData.preferences.showWallAngles = element.checked; floorplan.updateWallAngles(); break; case checkboxes.smallWallAnglesCheckboxId: floorplan.model.modelData.preferences.showOnlySmallWallAngles = element.checked; floorplan.updateWallAngles(); break; } floorplan.commitTransaction("change preference"); floorplan.skipsUndoManager = false; } // Adjust units based on the selected radio button in the Options Menu FloorplanUI.prototype.changeUnits = function() { var floorplan = this.floorplan; floorplan.startTransaction("set units"); var prevUnits = floorplan.model.modelData.units; var radios = document.forms[this.state.windows.optionsWindow.unitsFormId].elements[this.state.windows.optionsWindow.unitsFormName]; for (var i = 0; i < radios.length; i++) { if (radios[i].checked) { floorplan.model.setDataProperty(floorplan.model.modelData, "units", radios[i].id); } } var units = floorplan.model.modelData.units; switch (units) { case 'centimeters': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'cm'); break; case 'meters': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'm'); break; case 'feet': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'ft'); break; case 'inches': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'in'); break; } var unitsAbbreviation = floorplan.model.modelData.unitsAbbreviation; // update all units boxes with new units var unitAbbrevInputs = document.getElementsByClassName(this.state.unitsBoxClass); for (var i = 0; i < unitAbbrevInputs.length; i++) { unitAbbrevInputs[i].value = unitsAbbreviation; } var unitInputs = document.getElementsByClassName(this.state.unitsInputClass); for (var i = 0; i < unitInputs.length; i++) { var input = unitInputs[i]; floorplan.model.setDataProperty(floorplan.model.modelData, "units", prevUnits); var value = floorplan.convertUnitsToPixels(input.value); floorplan.model.setDataProperty(floorplan.model.modelData, "units", units) value = floorplan.convertPixelsToUnits(value); input.value = value; } if (floorplan.selection.count === 1) this.setSelectionInfo(floorplan.selection.first()); // reload node info measurements according to new units floorplan.commitTransaction("set units"); } /* * Set current tool (selecting/dragging or wallbuilding/reshaping) * @param {String} string Informs what behavior to switch to. Accepted values: "dragging", "wallbuilding" */ FloorplanUI.prototype.setBehavior = function (string) { var floorplan = this.floorplan; var ui = this; var wallBuildingTool = floorplan.toolManager.mouseDownTools.elt(0); var wallReshapingTool = floorplan.toolManager.mouseDownTools.elt(3); // style the current tool HTML button accordingly var elements = document.getElementsByClassName(this.state.setBehaviorClass); for (var i = 0; i < elements.length; i++) { var el = elements[i]; if (el.id === string + "Button") el.style.backgroundColor = '#4b545f'; else el.style.backgroundColor = '#bbbbbb'; } var wallThicknessBox = document.getElementById(this.state.wallThicknessBoxId) if (string === 'wallBuilding') { wallBuildingTool.isEnabled = true; wallReshapingTool.isEnabled = false; floorplan.skipsUndoManager = true; floorplan.startTransaction("change wallThickness"); // create walls with wallThickness in input box floorplan.model.setDataProperty(floorplan.model.modelData, 'wallThickness', parseFloat(document.getElementById(ui.state.wallThicknessInputId).value)); var wallThickness = floorplan.model.modelData.wallThickness; if (isNaN(wallThickness)) floorplan.model.setDataProperty(floorplan.model.modelData, 'wallThickness', 5); else { var width = floorplan.convertUnitsToPixels(wallThickness); floorplan.model.setDataProperty(floorplan.model.modelData, 'wallThickness', width); } floorplan.commitTransaction("change wallThickness"); floorplan.skipsUndoManager = false; wallThicknessBox.style.visibility = 'visible'; wallThicknessBox.style.display = 'inline-block'; ui.setDiagramHelper("Click and drag on the diagram to draw a wall (hold SHIFT for 45 degree angles)"); } if (string === 'dragging') { wallBuildingTool.isEnabled = false; wallReshapingTool.isEnabled = true; wallThicknessBox.style.visibility = 'hidden'; wallThicknessBox.style.display = 'none'; } // clear resize adornments on walls/windows, if there are any floorplan.nodes.iterator.each(function (n) { n.clearAdornments(); }) floorplan.clearSelection(); } /* * Populating UI Windows from Floorplan data: * Update UI, Update Statistics, Fill Rows With Nodes, Set Selection Info, Set Color, Set Height, Set Width, Apply Selection Changes */ // Update the UI properly in accordance with model.modelData (called only when a new floorplan is loaded or created) FloorplanUI.prototype.updateUI = function () { var floorplan = this.floorplan; var modelData = floorplan.model.modelData; var checkboxes = this.state.windows.optionsWindow.checkboxes; if (floorplan.floorplanUI) floorplan.floorplanUI.changeUnits(); document.getElementById(this.state.wallThicknessInputId).value = floorplan.convertPixelsToUnits(modelData.wallThickness); // update options GUI based on floorplan.model.modelData.preferences var preferences = modelData.preferences; document.getElementById(checkboxes.showGridCheckboxId).checked = preferences.showGrid; document.getElementById(checkboxes.gridSnapCheckboxId).checked = preferences.gridSnap; document.getElementById(checkboxes.wallGuidelinesCheckboxId).checked = preferences.showWallGuidelines; document.getElementById(checkboxes.wallLengthsCheckboxId).checked = preferences.showWallLengths; document.getElementById(checkboxes.wallAnglesCheckboxId).checked = preferences.showWallAngles; document.getElementById(checkboxes.smallWallAnglesCheckboxId).checked = preferences.showOnlySmallWallAngles; } // Update all statistics in Statistics Window - called when a Floorplan's model is changed FloorplanUI.prototype.updateStatistics = function () { var floorplan = this.floorplan; var statsWindow = this.state.windows.statisticsWindow; var element = document.getElementById(statsWindow.textDivId); if (element) { element.innerHTML = "' + name1 + '
' + name2 + '
' + name1 + '
' + name2 + '
' + name1 + '
' + name2 + '
' + name1 + '
' + name1 + '
' + node + '
'; return; } // if there are multiple nodes selected, show all their names, allowing user to click on the node they want if (node === 'Selection: ') { var selectionIterator = floorplan.selection.iterator; var arr = []; element.innerHTML = 'Selection (' + selectionIterator.count + ' items selected):
'; this.fillRowsWithNodes(selectionIterator, element, null); infoWindow.style.height = document.getElementById(selectionInfoWindow.textDivId).offsetHeight + document.getElementById(selectionInfoWindow.handleId).offsetHeight + 5 + 'px'; return; } // TODO clean this to be usable by a more general template scheme // if we have one node selected, gather pertinent information for that node.... floorplan.select(node); var name = ''; var length; var width; var nodeGroupCount = 0; var notes = node.data.notes; // get node name if (node.category === 'MultiPurposeNode') name = node.data.text; else name = node.data.caption; // get node height / width / length (dependent on node category) // Wall Groups if (node.category === 'WallGroup') { length = floorplan.convertPixelsToUnits(Math.sqrt(node.data.startpoint.distanceSquared(node.data.endpoint.x, node.data.endpoint.y))).toFixed(2); // wall length thickness = floorplan.convertPixelsToUnits(node.data.thickness).toFixed(2); // wall thickness } // Wall Parts (i.e. Door / Window Nodes) else if (node.category === 'DoorNode' || node.category === "WindowNode") { length = floorplan.convertPixelsToUnits(node.data.length).toFixed(2); } // Generic Groups else if (node.data.isGroup && node.category !== "WallGroup") { height = floorplan.convertPixelsToUnits(node.actualBounds.height).toFixed(2); width = floorplan.convertPixelsToUnits(node.actualBounds.width).toFixed(2); } // Furniture Nodes else { height = floorplan.convertPixelsToUnits(node.data.height).toFixed(2); width = floorplan.convertPixelsToUnits(node.data.width).toFixed(2); } // get node group info if (node.containingGroup != null && node.containingGroup != undefined) { var nodeGroupParts = node.containingGroup.memberParts; nodeGroupCount = nodeGroupParts.count; } if (node.data.isGroup) { var nodeGroupParts = node.memberParts; nodeGroupCount = nodeGroupParts.count; } var unitsAbbreviation = floorplan.model.modelData.unitsAbbreviation; // display information in the selection info window element.innerHTML = 'Name: ' + '' + '
'; // name element.innerHTML += "Notes:
"; // notes // display color as a color picker element (furniture nodes only) if (!node.data.isGroup && node.data.category !== "DoorNode" && node.data.category !== "WindowNode") { element.innerHTML += 'Color:
'; } // display ONLY "Length" input (Door / Window Nodes only) (this is still technically the "width input" as far as IDs are concerned) if (node.category === "DoorNode" || node.category === "WindowNode") element.innerHTML += 'Length:
'
+ '
Thickness:
Length:
Height:
Width: