1 /*
  2  * FurnitureTablePanel.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 // Requires SweetHome3D.js
 22 // Requires UserPreferences.js
 23 // Requires toolkit.js
 24 
 25 /**
 26  * Creates a new furniture tree table that displays <code>home</code> furniture.
 27  * @param {string} containerId
 28  * @param {Home} home
 29  * @param {UserPreferences} preferences
 30  * @param {FurnitureController} controller
 31  * @constructor
 32  * @author Emmanuel Puybaret
 33  * @author Louis Grignon
 34  */
 35 function FurnitureTablePanel(containerId, home, preferences, controller) {
 36   this.container = document.getElementById(containerId);
 37   this.preferences = preferences;
 38   this.controller = controller;
 39   this.defaultDecimalFormat = new DecimalFormat();  
 40   this.integerFormat = new IntegerFormat();
 41   this.treeTable = new JSTreeTable(this.container, this.preferences, this.createTableModel(home));
 42   this.addHomeListeners(home);
 43   this.addUserPreferencesListeners(home);
 44   this.updateData(home);
 45 }
 46 
 47 FurnitureTablePanel.EXPANDED_ROWS_VISUAL_PROPERTY = "com.eteks.sweethome3d.SweetHome3D.ExpandedGroups";
 48 
 49 /**
 50  * Returns the HTML element used to view this component at screen.
 51  */
 52 FurnitureTablePanel.prototype.getHTMLElement = function() {
 53   return this.container;
 54 }
 55 
 56 /**
 57  * Adds listeners to home to update furniture in table.
 58  * @param {Home} home
 59  * @private
 60  */
 61 FurnitureTablePanel.prototype.addHomeListeners = function(home) {
 62   var panel = this;
 63   var treeTable = this.treeTable;
 64   home.addSelectionListener({
 65       selectionChanged: function (ev) {
 66         treeTable.setSelectedRowsByValue(home.getSelectedItems());
 67       }
 68     });
 69 
 70   var homePropertyChangeListener = function() {
 71       treeTable.setModel(panel.createTableModel(home));
 72     };
 73   home.addPropertyChangeListener("FURNITURE_SORTED_PROPERTY", homePropertyChangeListener);
 74   home.addPropertyChangeListener("FURNITURE_DESCENDING_SORTED", homePropertyChangeListener);
 75   home.addPropertyChangeListener("FURNITURE_VISIBLE_PROPERTIES", homePropertyChangeListener);
 76 
 77   var pieceOfFurnitureChangeListener = function(ev) {
 78       panel.updatePieceOfFurnitureData(home, ev.getSource(), ev.getPropertyName());
 79     };
 80   var furniture = home.getFurniture();
 81   for (var i = 0; i < furniture.length; i++) {
 82     var piece = furniture[i];
 83     piece.addPropertyChangeListener(pieceOfFurnitureChangeListener);
 84     if (piece instanceof HomeFurnitureGroup) {
 85       var groupFurniture = piece.getAllFurniture();
 86       for (var j = 0; j < groupFurniture.length; j++) {
 87         groupFurniture[j].addPropertyChangeListener(pieceOfFurnitureChangeListener);
 88       }
 89     }
 90   }
 91 
 92   home.addFurnitureListener(function(ev) {
 93       panel.updateData(home);
 94       var piece = ev.getItem();
 95       if (ev.getType() == CollectionEvent.Type.ADD) {
 96         piece.addPropertyChangeListener(pieceOfFurnitureChangeListener);
 97         if (piece instanceof HomeFurnitureGroup) {
 98           var groupFurniture = piece.getAllFurniture();
 99           for (var j = 0; j < groupFurniture.length; j++) {
100             groupFurniture[j].addPropertyChangeListener(pieceOfFurnitureChangeListener);
101           }
102         }
103       } else {
104         piece.removePropertyChangeListener(pieceOfFurnitureChangeListener);
105         if (piece instanceof HomeFurnitureGroup) {
106           var groupFurniture = piece.getAllFurniture();
107           for (var j = 0; j < groupFurniture.length; j++) {
108             groupFurniture[j].removePropertyChangeListener(pieceOfFurnitureChangeListener);
109           }
110         }
111       }
112     });
113 
114   var levelChangeListener = function() {
115       panel.updateData(home);
116     };
117   var levels = home.getLevels();
118   for (var i = 0; i < levels.length; i++) {
119     levels[i].addPropertyChangeListener(levelChangeListener);
120   }
121   home.addLevelsListener(function(ev) {
122       if (ev.getType() == CollectionEvent.Type.ADD) {
123         ev.getItem().addPropertyChangeListener(levelChangeListener);
124       } else {
125         ev.getItem().removePropertyChangeListener(levelChangeListener);
126       }
127     });
128 }
129 
130 /**
131  * @param {Home} home
132  * @private
133  */
134 FurnitureTablePanel.prototype.addUserPreferencesListeners = function(home) {
135   var panel = this;
136   this.preferencesListener = function(ev) {
137       if (ev.getPropertyName() == "UNIT" || ev.getPropertyName() == "LANGUAGE") {
138         panel.treeTable.setModel(panel.createTableModel(home));
139       }
140     };
141   this.preferences.addPropertyChangeListener(this.preferencesListener);
142 }
143 
144 /**
145  * Returns a new table model matching home furniture.
146  * @param {Home} home 
147  * @return {TreeTableModel}
148  * @private
149  */
150 FurnitureTablePanel.prototype.createTableModel = function(home) {
151   var availableColumns = {
152       "CATALOG_ID": {
153         name: "CATALOG_ID", 
154         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "catalogIdColumn"),
155         defaultWidth: "4rem"
156       },
157       "NAME": {
158         name: "NAME", 
159         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "nameColumn"),
160         defaultWidth: "14rem"
161       },
162       "DESCRIPTION": {
163         name: "DESCRIPTION", 
164         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "descriptionColumn"),
165         defaultWidth: "14rem"
166       },
167       "WIDTH": {
168         name: "WIDTH", 
169         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "widthColumn") 
170       },
171       "DEPTH": {
172         name: "DEPTH",
173         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "depthColumn")
174       },
175       "HEIGHT": {
176         name: "HEIGHT",
177         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "heightColumn")
178       },
179       "MOVABLE": {
180         name: "MOVABLE",
181         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "movableColumn")
182       },
183       "DOOR_OR_WINDOW": {
184         name: "DOOR_OR_WINDOW",
185         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "doorOrWindowColumn")
186       },
187       "COLOR": {
188         name: "COLOR",
189         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "colorColumn")
190       },
191       "TEXTURE": {
192         name: "TEXTURE",
193         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "textureColumn")
194       },
195       "VISIBLE": {
196         name: "VISIBLE",
197         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "visibleColumn")
198       },
199       "X": {
200         name: "X",
201         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "xColumn")
202       },
203       "Y": {
204         name: "Y",
205         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "yColumn")
206       },
207       "ELEVATION": {
208         name: "ELEVATION",
209         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "elevationColumn")
210       },
211       "ANGLE": {
212         name: "ANGLE",
213         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "angleColumn")
214       },
215       "MODEL_SIZE": {
216         name: "MODEL_SIZE",
217         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "modelSizeColumn")
218       },
219       "CREATOR": {
220         name: "CREATOR",
221         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "creatorColumn")
222       },
223       "LICENSE": {
224         name: "LICENSE",
225         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "licenseColumn"),
226         defaultWidth: "5rem"
227       },
228       "PRICE": {
229         name: "PRICE",
230         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "priceColumn")
231       },
232       "VALUE_ADDED_TAX": {
233         name: "VALUE_ADDED_TAX",
234         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "valueAddedTaxColumn")
235       },
236       "VALUE_ADDED_TAX_PERCENTAGE": {
237         name: "VALUE_ADDED_TAX_PERCENTAGE",
238         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "valueAddedTaxPercentageColumn")
239       },
240       "PRICE_VALUE_ADDED_TAX_INCLUDED": {
241         name: "PRICE_VALUE_ADDED_TAX_INCLUDED",
242         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "priceValueAddedTaxIncludedColumn")
243       },
244       "LEVEL": {
245         name: "LEVEL",
246         label: ResourceAction.getLocalizedLabelText(this.preferences, "FurnitureTable", "levelColumn")
247       }
248     };
249   var visibleColumns = [];
250   var visibleProperties = home.getFurnitureVisibleProperties();
251   for (var i = 0; i < visibleProperties.length; i++) {
252     for (var j in availableColumns) {
253       if (visibleProperties[i] == availableColumns[j].name) {
254         visibleColumns.push(availableColumns[j]);
255       }
256     }
257   }
258   var expandedRowsIndices = [];
259   if (home.getVersion() >= 5000) {
260     var expandedRows = home.getProperty(FurnitureTablePanel.EXPANDED_ROWS_VISUAL_PROPERTY);
261     if (expandedRows != null) {
262       expandedRowsIndices = [];
263       var expandedRowsEntries = expandedRows.split(",");
264       for (var i = 0; i < expandedRowsEntries.length; i++) {
265         var rowIndex = parseInt(expandedRowsEntries[i]);
266         if (!isNaN(rowIndex)) {
267           expandedRowsIndices.push(rowIndex);
268         }
269       }
270     }
271   }
272 
273   var panel = this;
274   return {
275       columns: visibleColumns,
276       renderCell: function(value, columnName, cell) {
277         return panel.renderCell(value, columnName, cell);
278       },
279       getValueComparator: function(sortConfig) {
280         if (sortConfig.columnName != null) {
281           var furnitureComparator = HomePieceOfFurniture.getFurnitureComparator(sortConfig.columnName);
282           if (sortConfig.direction == "desc") {
283             return function(o1, o2) {
284                 return furnitureComparator(o2, o1);
285               };
286           } else {
287             return furnitureComparator;
288           } 
289         } else {
290           return null;
291         }
292       },
293       selectionChanged: function(selectedValues) {
294         if (panel.controller !== null) {
295           panel.controller.setSelectedFurniture(selectedValues, false);
296         }
297       },
298       rowDoubleClicked: function(value) {
299         if (panel.controller !== null) {
300           panel.controller.modifySelectedFurniture(value);
301         }
302       },
303       expandedRowsChanged: function(expandedRowsValues, expandedRowsIndices) {
304         if (panel.controller !== null) {
305           panel.storeExpandedRows(expandedRowsIndices, home, panel.controller);
306         }
307       },
308       sortChanged: function(newSort) {
309         if (panel.controller !== null) {
310           panel.controller.sortFurniture(newSort.columnName);
311         }
312       },
313       initialState: {
314         visibleColumnNames: visibleProperties,
315         sort: {
316           columnName: home.getFurnitureSortedProperty(),
317           direction: home.isFurnitureDescendingSorted() ? "desc" : "asc"
318         },
319         expandedRowsIndices: expandedRowsIndices
320       }
321     };
322 }
323 
324 /**
325  * Refreshes data list and updates UI.
326  * @param {Home} home
327  * @private
328  */
329 FurnitureTablePanel.prototype.updateData = function(home) {
330   var dataList = [];
331   var addToDataList = function(furnitureList, dataList) {
332       for (var i = 0; i < furnitureList.length; i++) {
333         var piece = furnitureList[i];
334         var dataItem = { value: piece };
335         if (piece instanceof HomeFurnitureGroup) {
336           dataItem.children = [];
337           addToDataList(piece.getFurniture(), dataItem.children);
338         }
339         dataList.push(dataItem);
340       }
341     };
342     
343   addToDataList(home.getFurniture(), dataList);
344   this.treeTable.setData(dataList);
345 }
346 
347 /**
348  * Refreshes data row matching the given piece of furniture and its parent groups.
349  * @param {Home} home
350  * @param {HomePieceOfFurniture} piece
351  * @param {string} [propertyName]
352  * @private
353  */
354 FurnitureTablePanel.prototype.updatePieceOfFurnitureData = function(home, piece, propertyName) {
355   this.treeTable.updateRowData(piece, propertyName);
356  
357   var panel = this;
358   var updatePieceGroupsRowData = function(furniture, propertyName) {
359       for (var i = 0; i < furniture.length; i++) {
360         if (furniture[i] === piece) {
361           return true;
362         } else if (furniture[i] instanceof HomeFurnitureGroup) {
363           var parent = updatePieceGroupsRowData(furniture[i].getFurniture(), propertyName);
364           if (parent) {
365             panel.treeTable.updateRowData(furniture[i], propertyName);
366           }
367           return parent;
368         }
369       }
370       return false;
371     };
372   
373   if (propertyName === undefined) {
374     updatePieceGroupsRowData(home.getFurniture(), propertyName);
375   }
376   // Update row data depending on prices and VAT
377   if (propertyName == "PRICE"
378       || propertyName == "VALUE_ADDED_TAX_PERCENTAGE") {
379     updatePieceGroupsRowData(home.getFurniture(), propertyName);
380     this.treeTable.updateRowData(piece, "PRICE_VALUE_ADDED_TAX_INCLUDED");
381     updatePieceGroupsRowData(home.getFurniture(), "PRICE_VALUE_ADDED_TAX_INCLUDED");
382   }
383   if (propertyName == "VALUE_ADDED_TAX_PERCENTAGE") {
384     updatePieceGroupsRowData(home.getFurniture(), propertyName);
385     this.treeTable.updateRowData(piece, "VALUE_ADDED_TAX");
386     updatePieceGroupsRowData(home.getFurniture(), "VALUE_ADDED_TAX");
387   }
388   if (propertyName == "CURRENCY") {
389     this.treeTable.updateRowData(piece, "PRICE");
390     updatePieceGroupsRowData(home.getFurniture(), "PRICE");
391     this.treeTable.updateRowData(piece, "PRICE_VALUE_ADDED_TAX_INCLUDED");
392     updatePieceGroupsRowData(home.getFurniture(), "PRICE_VALUE_ADDED_TAX_INCLUDED");
393   }
394 }
395 
396 /**
397  * Returns false if furniture is nested in a group.
398  * @param {HomePieceOfFurniture} piece
399  * @return {boolean}
400  * @private
401  */
402 FurnitureTablePanel.prototype.isRootPieceOfFurniture = function(piece) {
403   var dataList = this.treeTable.getData();
404   for (var i = 0; i < dataList.length; i++) {
405     if (dataList[i].value == piece) {
406       return true;
407     }
408   }
409   return false;
410 }
411 
412 /**
413  * @param {number} value
414  * @param {HTMLTableCellElement} cell
415  * @param {Format} [format] 
416  * @private
417  */
418 FurnitureTablePanel.prototype.renderNumberCellValue = function(value, cell, format) {
419   if (!format) {
420     format = this.defaultDecimalFormat;
421   }
422   var text = "";
423   if (value != null) {
424     text = format.format(value);
425   }
426   cell.innerHTML = text;
427   cell.classList.add("number");
428 }
429 
430 /**
431  * @param {number} value
432  * @param {HTMLTableCellElement} cell
433  * @private
434  */
435 FurnitureTablePanel.prototype.renderSizeCellValue = function(value, cell) {
436   this.renderNumberCellValue(value, cell, this.preferences.getLengthUnit().getFormat());
437 }
438 
439 /**
440  * @param {HomePieceOfFurniture} piece
441  * @param {HTMLTableCellElement} cell
442  * @private
443  */
444 FurnitureTablePanel.prototype.renderNameCell = function(piece, cell) {
445   while (cell.firstChild) {
446     cell.removeChild(cell.firstChild);
447   }
448   cell.classList.add("main", "name");
449   var iconElement = document.createElement("span");
450   iconElement.setAttribute("icon", true);
451   var icon = piece instanceof HomeFurnitureGroup
452       ? new URLContent(ZIPTools.getScriptFolder() + "resources/groupIcon.png")
453       : piece.getIcon();
454   if (icon != null) {
455     TextureManager.getInstance().loadTexture(icon, 0, false,
456         {
457           textureUpdated : function(image) {
458             iconElement.appendChild(image.cloneNode());
459           },
460           textureError : function(error) {
461             return this.textureUpdated(TextureManager.getInstance().getErrorImage());
462           }
463         });
464   } else {
465     var emptyImage = document.createElement("img");
466     emptyImage.width = 15;
467     iconElement.appendChild(emptyImage);
468   }
469   cell.appendChild(iconElement);
470 
471   var nameElement = document.createElement("span");
472   nameElement.textContent = piece.getName();
473   cell.appendChild(nameElement);
474 }
475 
476 /**
477  * @param {HomePieceOfFurniture} piece
478  * @param {HTMLTableCellElement} cell
479  * @private
480  */
481 FurnitureTablePanel.prototype.renderColorCell = function(piece, cell) {
482   while (cell.firstChild) {
483     cell.removeChild(cell.firstChild);
484   }
485   cell.classList.add("color");
486   if (piece.getColor() != null) {
487     var colorSquare = document.createElement("div");
488     colorSquare.style.backgroundColor = ColorTools.integerToHexadecimalString(piece.getColor());
489     cell.appendChild(colorSquare);
490   } else {
491     cell.textContent = "-";
492   }
493 }
494 
495 /**
496  * @param {HomePieceOfFurniture} piece
497  * @param {HTMLTableCellElement} cell
498  * @private
499  */
500 FurnitureTablePanel.prototype.renderTextureCell = function(piece, cell) {
501   while (cell.firstChild) {
502     cell.removeChild(cell.firstChild);
503   }
504   cell.classList.add("texture");
505   if (piece.getTexture() != null) {
506     var previewSquare = document.createElement("div");
507     TextureManager.getInstance().loadTexture(piece.getTexture().getImage(), 0, false,
508         {
509           textureUpdated : function(image) {
510             previewSquare.style.backgroundImage = "url('" + image.src + "')";
511           },
512           textureError : function(error) {
513             return this.textureUpdated(TextureManager.getInstance().getErrorImage());
514           }
515         });
516 
517     cell.appendChild(previewSquare);
518   }
519 }
520 
521 /**
522  * @param {boolean} value
523  * @param {HTMLTableCellElement} cell
524  * @param {boolean} [editEnabled] default to false
525  * @param {function(checkedState: boolean)} [stateChanged]
526  * @private
527  */
528 FurnitureTablePanel.prototype.renderBooleanCell = function(value, cell, editEnabled, stateChanged) {
529   while (cell.firstChild) {
530     cell.removeChild(cell.firstChild);
531   }
532   cell.classList.add("boolean");
533   var checkbox = document.createElement("input");
534   checkbox.type = "checkbox";
535   checkbox.disabled = editEnabled !== true;
536   checkbox.checked = value === true;
537   checkbox.tabIndex = -1;
538   if (stateChanged !== undefined) {
539     checkbox.addEventListener("click", function(ev) {
540           stateChanged(checkbox.checked);
541         }, 
542         true);
543   }
544   cell.appendChild(checkbox);
545 }
546 
547 /**
548  * @param {HomePieceOfFurniture} piece
549  * @param {Big} value
550  * @param {HTMLTableCellElement} cell
551  * @private
552  */
553 FurnitureTablePanel.prototype.renderPriceCellValue = function(piece, value, cell) {
554   var currency = piece.getCurrency() == null 
555       ? this.preferences.getCurrency() 
556       : piece.getCurrency();
557   var currencyFormat = NumberFormat.getCurrencyInstance(); 
558   currencyFormat.setCurrency(currency);
559   this.renderNumberCellValue(this.bigToNumber(value), cell, currencyFormat);
560 }
561 
562 /**
563  * @param {Big} bigNumber
564  * @return {number}
565  * @private
566  */
567 FurnitureTablePanel.prototype.bigToNumber = function(bigNumber) {
568   return bigNumber == null ? null : parseFloat(bigNumber.valueOf());
569 }
570 
571 /**
572  * @param {HomePieceOfFurniture} piece
573  * @param {HTMLTableCellElement} cell
574  * @private
575  */
576 FurnitureTablePanel.prototype.renderCreatorCell = function(piece, cell) {
577   var creator = piece.getCreator();
578   if (creator != null) {
579     var texture = piece.getTexture();
580     if (texture != null) {
581       var textureCreator = texture.getCreator();
582       if (textureCreator != null && creator != textureCreator) {
583         creator += ", " + textureCreator;
584       }
585     } else {
586       var modelCreator = creator;
587       var materials = piece.getModelMaterials();
588       if (materials != null) {
589         for (var i = 0; i < materials.length; i++) {
590           var material = materials[i];
591           if (material != null) {
592             var materialTexture = material.getTexture();
593             if (materialTexture != null) {
594               var textureCreator = materialTexture.getCreator();
595               if (textureCreator != null
596                 && modelCreator != textureCreator
597                 && creator.indexOf(", " + textureCreator) == -1) {
598                 creator += ", " + textureCreator;
599               }
600             }
601           }
602         }
603       }
604     }
605   }
606   cell.textContent = creator;
607 }
608 
609 /**
610  * @param {string} value
611  * @param {HTMLTableCellElement} cell
612  * @private
613  */
614 FurnitureTablePanel.prototype.renderTextCell = function(value, cell) {
615   cell.textContent = value;
616 }
617 
618 /**
619  * @param {HomePieceOfFurniture} piece
620  * @param {string} columnName
621  * @param {HTMLTableCellElement} cell
622  * @private
623  */
624 FurnitureTablePanel.prototype.renderCell = function(piece, columnName, cell) {
625   switch (columnName) {
626     case "CATALOG_ID":
627       return this.renderTextCell(piece.getCatalogId(), cell);
628     case "NAME":
629       return this.renderNameCell(piece, cell);
630     case "DESCRIPTION":
631       return this.renderTextCell(piece.getDescription(), cell);
632     case "CREATOR":
633       return this.renderCreatorCell(piece, cell);
634     case "LICENSE":
635       return this.renderTextCell(piece.getLicense(), cell);
636     case "WIDTH":
637       return this.renderSizeCellValue(piece.getWidth(), cell);
638     case "DEPTH":
639       return this.renderSizeCellValue(piece.getDepth(), cell);
640     case "HEIGHT":
641       return this.renderSizeCellValue(piece.getHeight(), cell);
642     case "X":
643       return this.renderSizeCellValue(piece.getX(), cell);
644     case "Y":
645       return this.renderSizeCellValue(piece.getY(), cell);
646     case "ELEVATION":
647       return this.renderSizeCellValue(piece.getElevation(), cell);
648     case "ANGLE":
649       var value = Math.round(Math.toDegrees(piece.getAngle()) + 360) % 360;
650       return this.renderNumberCellValue(value, cell, this.integerFormat);
651     case "LEVEL":
652       cell.textContent = piece != null && piece.getLevel() != null ? piece.getLevel().getName() : "";
653       break;
654     case "MODEL_SIZE":
655       var value = piece != null && piece.getModelSize() != null && piece.getModelSize() > 0
656         ? Math.max(1, Math.round(piece.getModelSize() / 1000))
657         : null;
658       return this.renderNumberCellValue(value, cell, this.integerFormat);
659     case "COLOR":
660       return this.renderColorCell(piece, cell);
661     case "TEXTURE":
662       return this.renderTextureCell(piece, cell);
663     case "MOVABLE":
664       return this.renderBooleanCell(piece.isMovable(), cell);
665     case "DOOR_OR_WINDOW":
666       return this.renderBooleanCell(piece.isDoorOrWindow(), cell);
667     case "VISIBLE":
668       var controller = this.controller;
669       return this.renderBooleanCell(piece.isVisible(), cell, this.isRootPieceOfFurniture(piece), 
670           function(checked) {
671             if (controller !== null) {
672               controller.toggleSelectedFurnitureVisibility();
673             }
674           });
675     case "PRICE":
676       return this.renderPriceCellValue(piece, piece.getPrice(), cell);
677     case "VALUE_ADDED_TAX_PERCENTAGE":
678       return this.renderNumberCellValue(piece.getValueAddedTaxPercentage() !== null 
679           ? this.bigToNumber(piece.getValueAddedTaxPercentage()) * 100 : null, cell);
680     case "VALUE_ADDED_TAX":
681       return this.renderPriceCellValue(piece, piece.getValueAddedTax(), cell);
682     case "PRICE_VALUE_ADDED_TAX_INCLUDED":
683       return this.renderPriceCellValue(piece, piece.getPriceValueAddedTaxIncluded(), cell);
684     default:
685       cell.textContent = "-";
686       break;
687   }
688 }
689 
690 /**
691  * Stores expanded rows in home.
692  * @param {number[]} expandedRowsIndices index based on filtered and sorted data list
693  * @param {Home} home
694  * @param {FurnitureController} controller
695  * @private
696  */
697 FurnitureTablePanel.prototype.storeExpandedRows = function(expandedRowsIndices, home, controller) {
698   var propertyValue = expandedRowsIndices.join(',');
699   if (home.getProperty(FurnitureTablePanel.EXPANDED_ROWS_VISUAL_PROPERTY) != null || propertyValue.length > 0) {
700     controller.setHomeProperty(FurnitureTablePanel.EXPANDED_ROWS_VISUAL_PROPERTY, propertyValue);
701   }
702 }
703 
704 /** 
705  * Removes components added to this panel and their listeners.
706  */
707 FurnitureTablePanel.prototype.dispose = function() {
708   this.treeTable.dispose();
709   this.preferences.removePropertyChangeListener(this.preferencesListener);
710 }
711