# SPDX-License-Identifier: LGPL-2.1-or-later

# ***************************************************************************
# *                                                                         *
# *   Copyright (c) 2018 Yorik van Havre <yorik@uncreated.net>              *
# *                                                                         *
# *   This file is part of FreeCAD.                                         *
# *                                                                         *
# *   FreeCAD is free software: you can redistribute it and/or modify it    *
# *   under the terms of the GNU Lesser General Public License as           *
# *   published by the Free Software Foundation, either version 2.1 of the  *
# *   License, or (at your option) any later version.                       *
# *                                                                         *
# *   FreeCAD is distributed in the hope that it will be useful, but        *
# *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
# *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
# *   Lesser General Public License for more details.                       *
# *                                                                         *
# *   You should have received a copy of the GNU Lesser General Public      *
# *   License along with FreeCAD. If not, see                               *
# *   <https://www.gnu.org/licenses/>.                                      *
# *                                                                         *
# ***************************************************************************

"""The BIM Views command"""

import sys

import FreeCAD
import FreeCADGui

QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP
translate = FreeCAD.Qt.translate

UPDATEINTERVAL = 2000  # number of milliseconds between BIM Views Manager update
PARAMS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM")


class BIM_Views:

    def GetResources(self):
        return {
            "Pixmap": "BIM_Views",
            "MenuText": QT_TRANSLATE_NOOP("BIM_Views", "Views Manager"),
            "ToolTip": QT_TRANSLATE_NOOP("BIM_Views", "Shows or hides the views manager"),
        }

    def Activated(self):
        from PySide import QtCore, QtGui

        vm = findWidget()
        self.allItemsInTree = []
        self.oldData = [[], []]
        bimviewsbutton = None
        mw = FreeCADGui.getMainWindow()
        st = mw.statusBar()
        statuswidget = st.findChild(QtGui.QToolBar, "BIMStatusWidget")
        if statuswidget and hasattr(statuswidget, "bimviewsbutton"):
            bimviewsbutton = statuswidget.bimviewsbutton
        if vm:
            if vm.isVisible():
                vm.hide()
                if bimviewsbutton:
                    bimviewsbutton.setChecked(False)
                PARAMS.SetBool("RestoreBimViews", False)
            else:
                vm.show()
                if bimviewsbutton:
                    bimviewsbutton.setChecked(True)
                PARAMS.SetBool("RestoreBimViews", True)
                self.update()
        else:
            vm = QtGui.QDockWidget()

            # create the dialog
            self.dialog = FreeCADGui.PySideUic.loadUi(":/ui/dialogViews.ui")
            vm.setWidget(self.dialog)
            vm.tree = self.dialog.tree
            vm.viewtree = self.dialog.viewtree
            vm.closeEvent = self.onClose

            # set context menu
            self.dialog.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)

            # set button
            self.dialog.menu = QtGui.QMenu()
            for button in [
                ("Active", translate("BIM", "Active")),
                ("AddLevel", translate("BIM", "New Level Above")),
                ("AddProxy", translate("BIM", "New Working Plane Proxy")),
                ("Delete", translate("BIM", "Delete")),
                ("Toggle", translate("BIM", "Toggle Visibility")),
                ("Isolate", translate("BIM", "Isolate")),
                ("SaveView", translate("BIM", "Save View Position")),
                ("Rename", translate("BIM", "Rename")),
            ]:
                action = QtGui.QAction(button[1])

                # Make the "Activate" button bold, as this is the default one
                if button[0] == "Active":
                    font = action.font()
                    font.setBold(True)
                    action.setFont(font)
                    action.setCheckable(True)

                self.dialog.menu.addAction(action)
                setattr(self.dialog, "button" + button[0], action)

            # # set button icons
            self.dialog.buttonAddLevel.setIcon(QtGui.QIcon(":/icons/Arch_Floor_Tree.svg"))
            self.dialog.buttonAddProxy.setIcon(QtGui.QIcon(":/icons/Draft_PlaneProxy.svg"))
            self.dialog.buttonDelete.setIcon(QtGui.QIcon(":/icons/delete.svg"))
            self.dialog.buttonToggle.setIcon(QtGui.QIcon(":/icons/dagViewVisible.svg"))
            self.dialog.buttonIsolate.setIcon(QtGui.QIcon(":/icons/Std_ShowSelection.svg"))
            self.dialog.buttonSaveView.setIcon(QtGui.QIcon(":/icons/Std_ViewScreenShot.svg"))
            self.dialog.buttonRename.setIcon(QtGui.QIcon(":/icons/edit-edit.svg"))

            # set tooltips
            self.dialog.buttonAddLevel.setToolTip(
                translate("BIM", "Creates a new level above the highest existing one")
            )
            self.dialog.buttonAddProxy.setToolTip(
                translate("BIM", "Creates a new working plane proxy")
            )
            self.dialog.buttonDelete.setToolTip(translate("BIM", "Deletes the selected item"))
            self.dialog.buttonToggle.setToolTip(
                translate("BIM", "Toggles the visibility of selected items")
            )
            self.dialog.buttonIsolate.setToolTip(
                translate("BIM", "Turns all items off except the selected ones")
            )
            self.dialog.buttonSaveView.setToolTip(
                translate("BIM", "Saves the current camera position to the selected items")
            )
            self.dialog.buttonRename.setToolTip(translate("BIM", "Renames the selected item"))
            self.dialog.buttonActive.setToolTip(translate("BIM", "Activates the selected item"))

            # connect signals
            self.dialog.buttonAddLevel.triggered.connect(self.addLevel)
            self.dialog.buttonAddProxy.triggered.connect(self.addProxy)
            self.dialog.buttonDelete.triggered.connect(self.delete)
            self.dialog.buttonToggle.triggered.connect(self.toggle)
            self.dialog.buttonIsolate.triggered.connect(self.isolate)
            self.dialog.buttonSaveView.triggered.connect(self.saveView)
            self.dialog.buttonRename.triggered.connect(self.rename)
            self.dialog.buttonActive.triggered.connect(lambda: BIM_Views.activate(self.dialog))
            self.dialog.tree.itemClicked.connect(self.select)
            self.dialog.tree.itemDoubleClicked.connect(show)
            self.dialog.viewtree.itemDoubleClicked.connect(show)
            self.dialog.tree.itemChanged.connect(self.editObject)
            self.dialog.tree.customContextMenuRequested.connect(self.onContextMenu)
            # delay connecting after FreeCAD finishes setting up
            QtCore.QTimer.singleShot(UPDATEINTERVAL, self.connectDock)

            # set the dock widget
            area = PARAMS.GetInt("BimViewArea", 1)
            floating = PARAMS.GetBool("BimViewFloat", True)
            height = PARAMS.GetBool("BimViewWidth", 200)
            width = PARAMS.GetBool("BimViewHeight", 300)
            tabs = PARAMS.GetString("BimViewTabs", "")
            vm.setObjectName("BIM Views Manager")
            vm.setWindowTitle(translate("BIM", "BIM Views Manager"))
            mw = FreeCADGui.getMainWindow()
            vm.setFloating(floating)
            vm.setGeometry(vm.x(), vm.y(), width, height)
            mw.addDockWidget(self.getDockArea(area), vm)
            if tabs:
                tabs = tabs.split("+")
                for tab in tabs:
                    dw = mw.findChild(QtGui.QDockWidget, tab)
                    if dw:
                        mw.tabifyDockWidget(dw, vm)
                        break

            # restore saved settings
            vm.tree.setColumnWidth(0, PARAMS.GetInt("ViewManagerColumnWidth", 100))
            vm.setFloating(PARAMS.GetBool("ViewManagerFloating", False))

            # check the status bar button
            if bimviewsbutton:
                bimviewsbutton.setChecked(True)
            PARAMS.SetBool("RestoreBimViews", True)

            self.update()

    def onClose(self, event):
        from PySide import QtGui

        st = FreeCADGui.getMainWindow().statusBar()
        statuswidget = st.findChild(QtGui.QToolBar, "BIMStatusWidget")
        if statuswidget and hasattr(statuswidget, "bimviewsbutton"):
            statuswidget.bimviewsbutton.setChecked(False)
        PARAMS.SetBool("RestoreBimViews", False)

    def connectDock(self):
        "watch for dock location"

        vm = findWidget()
        if vm:
            vm.dockLocationChanged.connect(self.onDockLocationChanged)

    def _treeToStringList(self, treeViewItems):
        "generates a (nested) string list representation of treeViewItems"

        def _toStringList(itm):
            children = []
            for i in range(itm.childCount()):
                children.append(_toStringList(itm.child(i)))
            return [itm.toolTip(0), itm.text(0), itm.text(1), itm.text(2), children]

        return [_toStringList(itm) for itm in treeViewItems]

    def update(self, retrigger=True):
        "updates the view manager"

        from PySide import QtCore, QtGui
        import Draft

        vm = findWidget()
        if vm and vm.isVisible():
            if FreeCAD.isRestoring() or not FreeCAD.ActiveDocument:
                if vm.tree.state() != vm.tree.State.EditingState:
                    self.oldData[0] = []
                    vm.tree.clear()
                if vm.viewtree.state() != vm.viewtree.State.EditingState:
                    self.oldData[1] = []
                    vm.viewtree.clear()
            else:
                if vm.tree.state() != vm.tree.State.EditingState:
                    treeViewItems = []  # QTreeWidgetItem to Display in tree
                    lvHold = []
                    soloProxyHold = []
                    for obj in FreeCAD.ActiveDocument.Objects:
                        t = Draft.getType(obj)
                        if obj and (
                            t
                            in [
                                "Building",
                                "BuildingPart",
                                "IfcBuilding",
                                "IfcBuildingStorey",
                            ]
                        ):
                            if (
                                t in ["Building", "IfcBuilding"]
                                or getattr(obj, "IfcType", "") == "Building"
                            ):
                                building, _ = getTreeViewItem(obj)
                                subObjs = obj.Group
                                # find every levels belongs to the building
                                for subObj in subObjs:
                                    if Draft.getType(subObj) in [
                                        "BuildingPart",
                                        "Building Storey",
                                        "IfcBuildingStorey",
                                    ]:
                                        lv, lvH = getTreeViewItem(subObj)
                                        subSubObjs = subObj.Group
                                        # find every working plane proxy belongs to the level
                                        for subSubObj in subSubObjs:
                                            if Draft.getType(subSubObj) == "WorkingPlaneProxy":
                                                wp, _ = getTreeViewItem(subSubObj)
                                                lv.addChild(wp)
                                        lvHold.append((lv, lvH))
                                sortLvHold = sorted(lvHold, key=lambda x: x[1])
                                sortLvItems = [item[0] for item in sortLvHold]
                                for lvItem in sortLvItems:
                                    building.addChild(lvItem)
                                treeViewItems.append(building)
                                lvHold.clear()

                            if (
                                t in ["Building Storey", "IfcBuildingStorey"]
                                or getattr(obj, "IfcType", "") == "Building Storey"
                            ):
                                if (
                                    Draft.getType(getParent(obj)) in ["Building", "IfcBuilding"]
                                    or getattr(getParent(obj), "IfcType", "") == "Building"
                                ):
                                    continue
                                lv, lvH = getTreeViewItem(obj)
                                subObjs = obj.Group
                                # find every working plane proxy belongs to the level
                                for subObj in subObjs:
                                    if Draft.getType(subObj) == "WorkingPlaneProxy":
                                        wp, _ = getTreeViewItem(subObj)
                                        lv.addChild(wp)
                                lvHold.append((lv, lvH))
                        if obj and (t == "WorkingPlaneProxy"):
                            if obj.getParent() and obj.getParent().IfcType == "Building Storey":
                                continue
                            wp, _ = getTreeViewItem(obj)
                            soloProxyHold.append(wp)
                    sortLvHold = sorted(lvHold, key=lambda x: x[1])
                    sortLvItems = [item[0] for item in sortLvHold]
                    treeViewItems = treeViewItems + sortLvItems + soloProxyHold
                    new = self._treeToStringList(treeViewItems)
                    if new != self.oldData[0]:
                        self.oldData[0] = new
                        vm.tree.clear()
                        self.allItemsInTree.clear()
                        vm.tree.addTopLevelItems(treeViewItems)

                if vm.viewtree.state() != vm.viewtree.State.EditingState:
                    ficon = QtGui.QIcon.fromTheme("folder", QtGui.QIcon(":/icons/folder.svg"))
                    treeViewItems = []

                    views = self.getViews()
                    if views:
                        top = QtGui.QTreeWidgetItem([translate("BIM", "2D Views"), ""])
                        top.setIcon(0, ficon)
                        for v in views:
                            if hasattr(v, "Label"):
                                i = QtGui.QTreeWidgetItem([v.Label, ""])
                                if hasattr(v.ViewObject, "Icon"):
                                    i.setIcon(0, v.ViewObject.Icon)
                                i.setToolTip(0, v.Name)
                                top.addChild(i)
                        treeViewItems.append(top)

                    pages = self.getPages()
                    if pages:
                        top = QtGui.QTreeWidgetItem([translate("BIM", "Sheets"), ""])
                        top.setIcon(0, ficon)
                        for p in pages:
                            i = QtGui.QTreeWidgetItem([p.Label, ""])
                            if hasattr(p.ViewObject, "Icon"):
                                i.setIcon(0, p.ViewObject.Icon)
                            i.setToolTip(0, p.Name)
                            top.addChild(i)
                        treeViewItems.append(top)

                    new = self._treeToStringList(treeViewItems)
                    if new != self.oldData[1]:
                        self.oldData[1] = new
                        vm.viewtree.clear()
                        vm.viewtree.addTopLevelItems(treeViewItems)

            # We reuse the variable later on in "Isolate", to not traverse the tree once
            # again
            self.allItemsInTree = getAllItemsInTree(vm.tree)
            allItemsInTrees = self.allItemsInTree + getAllItemsInTree(vm.viewtree)

            if allItemsInTrees:
                # set TreeView Item selected if obj is selected
                objSelected = FreeCADGui.Selection.getSelection()
                objNameSelected = [obj.Name for obj in objSelected]
                objActive = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("NativeIFC")
                if not objActive:
                    objActive = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch")

                default_background = allItemsInTrees[0].background(1)
                default_font = allItemsInTrees[0].font(1)
                for item in allItemsInTrees:
                    item.setSelected(item.toolTip(0) in objNameSelected)
                    if objActive and item.toolTip(0) == objActive.Name:
                        tparam = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/TreeView")
                        activeColor = tparam.GetUnsigned("TreeActiveColor", 0)
                        if activeColor:
                            r = ((activeColor >> 24) & 0xFF) / 255.0
                            g = ((activeColor >> 16) & 0xFF) / 255.0
                            b = ((activeColor >> 8) & 0xFF) / 255.0
                            activeColor = QtGui.QColor.fromRgbF(r, g, b)
                            item.setBackground(0, QtGui.QBrush(activeColor, QtCore.Qt.SolidPattern))
                            bold = QtGui.QFont()
                            bold.setBold(True)
                            item.setFont(0, bold)
                    else:
                        item.setBackground(0, default_background)
                        item.setFont(0, default_font)

        if retrigger:
            QtCore.QTimer.singleShot(UPDATEINTERVAL, self.update)

        # save state
        PARAMS.SetInt("ViewManagerColumnWidth", vm.tree.columnWidth(0))
        PARAMS.SetBool("ViewManagerFloating", vm.isFloating())

        # expand
        vm.tree.expandAll()
        vm.viewtree.expandAll()

    def select(self, item, column=None):
        "selects a doc object corresponding to an item"

        item.setSelected(True)
        name = item.toolTip(0)
        if name:
            obj = FreeCAD.ActiveDocument.getObject(name)
            if obj:
                FreeCADGui.Selection.clearSelection()
                FreeCADGui.Selection.addSelection(obj)

    def addLevel(self):
        """Add a new level, auto-stacked above the highest sibling level.

        The new level is placed at the elevation of the highest existing
        level's top and added to the same parent building. This mirrors the
        level workflow in Revit and ArchiCAD: levels are sequential, sorted by
        elevation, and adding one extends the stack upward rather than
        colliding with existing storeys at z=0.
        """

        import Arch
        import Draft

        DEFAULT_SPACING = 3000.0  # mm, fallback vertical spacing for stacking

        # Determine sibling levels (children of the same parent), if any.
        sel = FreeCADGui.Selection.getSelection()
        parent = None
        if len(sel) == 1:
            s = sel[0]
            t = Draft.getType(s)
            if t in ["Building", "IfcBuilding"] or getattr(s, "IfcType", "") == "Building":
                parent = s
            elif t in ["BuildingPart", "Building Storey", "IfcBuildingStorey"]:
                parent = getParent(s)

        siblings = []
        scope = (
            parent.Group if parent and hasattr(parent, "Group") else FreeCAD.ActiveDocument.Objects
        )
        for o in scope:
            t = Draft.getType(o)
            if (
                t in ["BuildingPart", "Building Storey", "IfcBuildingStorey"]
                or getattr(o, "IfcType", "") == "Building Storey"
            ):
                siblings.append(o)

        top_elevation = 0.0
        if siblings:

            def _elev(o):
                z = o.Placement.Base.z
                if z == 0 and hasattr(o, "Elevation"):
                    z = o.Elevation.Value
                return z

            highest = max(siblings, key=_elev)
            h = getattr(highest, "Height", None)
            # Use the explicit height of the level below when set, otherwise a
            # default spacing, so the new level does not overlap the one below.
            spacing = h.Value if (h is not None and h.Value) else DEFAULT_SPACING
            top_elevation = _elev(highest) + spacing

        FreeCAD.ActiveDocument.openTransaction("Create Level")
        obj = Arch.makeFloor()
        obj.Placement.Base.z = top_elevation
        if parent is not None and hasattr(parent, "addObject"):
            parent.addObject(obj)
        FreeCAD.ActiveDocument.commitTransaction()
        FreeCAD.ActiveDocument.recompute()
        FreeCADGui.Selection.clearSelection()
        FreeCADGui.Selection.addSelection(obj)
        self.update(False)

    def addProxy(self):
        "adds a WP proxy"

        import Draft
        import WorkingPlane

        FreeCAD.ActiveDocument.openTransaction("Create WP Proxy")
        obj = Draft.makeWorkingPlaneProxy(WorkingPlane.get_working_plane().get_placement())
        self.addToSelection(obj)
        FreeCAD.ActiveDocument.commitTransaction()
        FreeCAD.ActiveDocument.recompute()
        self.update(False)

    def addToSelection(self, obj):
        "Adds the given object to the current selected item"

        import Draft
        from nativeifc import ifc_tools

        sel = FreeCADGui.Selection.getSelection()
        if len(sel) == 1:
            sel = sel[0]
            if hasattr(sel, "addObject"):
                sel.addObject(obj)
                return
            elif Draft.getType(sel).startswith("Ifc"):
                ifc_tools.aggregate(obj, sel)
            elif "Group" in sel.PropertiesList:
                g = sel.Group
                if obj not in g:
                    g.append(obj)
                sel.Group = g
                return

    def delete(self):
        "deletes the selected object"

        vm = findWidget()
        if vm:
            if vm.tree.selectedItems():
                FreeCAD.ActiveDocument.openTransaction("Delete")
                for item in vm.tree.selectedItems():
                    obj = FreeCAD.ActiveDocument.getObject(item.toolTip(0))
                    if obj:
                        FreeCAD.ActiveDocument.removeObject(obj.Name)
                FreeCAD.ActiveDocument.commitTransaction()
                FreeCAD.ActiveDocument.recompute()
                self.update(False)

    def rename(self):
        "renames the selected object"

        vm = findWidget()
        if vm:
            if vm.tree.selectedItems():
                if vm.tree.selectedItems():
                    item = vm.tree.selectedItems()[-1]
                    vm.tree.editItem(item, 0)

    @staticmethod
    def activate(dialog=None):
        from draftutils.gui_utils import toggle_working_plane

        vm = findWidget()
        if vm:
            if vm.tree.selectedItems():
                item = vm.tree.selectedItems()[-1]
                obj = FreeCAD.ActiveDocument.getObject(item.toolTip(0))
                if obj:
                    toggle_working_plane(obj, None, restore=True, dialog=dialog)
                    FreeCADGui.Selection.clearSelection()

    def editObject(self, item, column):
        "renames or edits the elevation or height of the actual object"

        obj = FreeCAD.ActiveDocument.getObject(item.toolTip(0))
        if not obj:
            return
        text = item.text(column)
        FreeCAD.ActiveDocument.openTransaction("Edit level")
        try:
            if column == 0:
                obj.Label = text
            elif column == 1:
                if text:
                    q = FreeCAD.Units.parseQuantity(text)
                    if hasattr(obj, "Elevation") and obj.Placement.Base.z == 0:
                        obj.Elevation = q
                    else:
                        obj.Placement.Base.z = q
            elif column == 2:
                if text and hasattr(obj, "Height"):
                    obj.Height = FreeCAD.Units.parseQuantity(text)
        finally:
            FreeCAD.ActiveDocument.commitTransaction()
        FreeCAD.ActiveDocument.recompute()

    def toggle(self):
        "toggle selected item on/off"

        vm = findWidget()
        if vm:
            for item in vm.tree.selectedItems():
                obj = FreeCAD.ActiveDocument.getObject(item.toolTip(0))
                if obj:
                    obj.ViewObject.Visibility = not (obj.ViewObject.Visibility)
            FreeCAD.ActiveDocument.recompute()

    def _isAncestor(self, ancestor_item, child_item):
        current = child_item.parent()
        while current is not None:
            if current == ancestor_item:
                return True
            current = current.parent()
        return False

    def isolate(self):
        import Draft

        """
        Isolate the currently selected items in the tree view.

        This function first makes all items in the tree visible to ensure a clean slate.
        Then, it hides all items that are not currently selected by the user in the GUI tree view.
        As a result, only the selected items remain visible in the 3D view, effectively isolating them.

        Assumes that `self.allItemsInTree` is a list of all QTreeWidgetItems in the tree.
        """

        # Iterate through all of the items and show them beforehand if they were hidden
        # so we can "reset" the tree state before the real processing
        for item in self.allItemsInTree:
            toolTip = item.toolTip(0)
            obj = FreeCAD.ActiveDocument.getObject(toolTip)
            if obj:
                # We switch visibility to be sure we will show childs of other childs
                # beforehand, as the Visibility may not be propagated.
                obj.ViewObject.Visibility = False
                obj.ViewObject.Visibility = True

        vm = findWidget()
        if vm:
            selectedItems = vm.tree.selectedItems()
            checkAncestors = False
            # We can get a scenario where user has just selected only Building
            # so we don't want to hide any of it's children, so just check if that's
            # the case so we will know whether we should process items further or not
            if len(selectedItems) == 1:
                toolTip = selectedItems[0].toolTip(0)
                obj = FreeCAD.ActiveDocument.getObject(toolTip)
                t = Draft.getType(obj)
                if obj and getattr(obj, "IfcType", "") == "Building":
                    checkAncestors = True

            for item in self.allItemsInTree:
                toolTip = item.toolTip(0)
                obj = FreeCAD.ActiveDocument.getObject(toolTip)
                if obj:
                    if item not in selectedItems and not (
                        checkAncestors and self._isAncestor(selectedItems[0], item)
                    ):
                        obj.ViewObject.Visibility = False
                    else:
                        obj.ViewObject.Visibility = True

    def saveView(self):
        "save the current camera angle to the selected item"

        vm = findWidget()
        if vm:
            for item in vm.tree.selectedItems():
                obj = FreeCAD.ActiveDocument.getObject(item.toolTip(0))
                if obj:
                    if hasattr(obj.ViewObject.Proxy, "writeCamera"):
                        obj.ViewObject.Proxy.writeCamera()
        FreeCAD.ActiveDocument.recompute()

    def onDockLocationChanged(self, area):
        """Saves dock widget size and location"""
        if hasattr(area, "value"):  # To support Qt5.15
            PARAMS.SetInt("BimViewArea", area.value)
        else:
            PARAMS.SetInt("BimViewArea", int(area))
        mw = FreeCADGui.getMainWindow()
        vm = findWidget()
        if vm:
            PARAMS.SetBool("BimViewFloat", vm.isFloating())
            PARAMS.SetInt("BimViewWidth", vm.width())
            PARAMS.SetInt("BimViewHeight", vm.height())
            tabs = "+".join([o.objectName() for o in mw.tabifiedDockWidgets(vm)])
            PARAMS.SetString("BimViewTabs", tabs)

    def getDockArea(self, area):
        """Turns an int into a qt dock area"""

        from PySide import QtCore

        if area == 1:
            return QtCore.Qt.LeftDockWidgetArea
        elif area == 4:
            return QtCore.Qt.TopDockWidgetArea
        elif area == 8:
            return QtCore.Qt.BottomDockWidgetArea
        else:
            return QtCore.Qt.RightDockWidgetArea

    def onContextMenu(self, pos):
        """Fires the context menu"""
        import Draft

        self.dialog.buttonAddProxy.setEnabled(True)
        selobj = self.dialog.tree.currentItem()
        if selobj:
            selobj = FreeCAD.ActiveDocument.getObject(selobj.toolTip(0))
            if selobj:
                if Draft.getType(selobj).startswith("Ifc"):
                    self.dialog.buttonAddProxy.setEnabled(False)
                if FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch") == selobj:
                    self.dialog.buttonActive.setChecked(True)
                else:
                    self.dialog.buttonActive.setChecked(False)
        self.dialog.menu.exec_(self.dialog.tree.mapToGlobal(pos))

    def getViews(self):
        """Returns a list of 2D views"""
        import Draft

        views = []
        for p in self.getPages():
            for v in p.Views:
                if getattr(v, "Source", None):
                    views.append(v.Source)
        bps = [o for o in FreeCAD.ActiveDocument.Objects if Draft.getType(o) == "BuildingPart"]
        for v in [o for o in bps if isView(o)]:
            if v not in views:
                views.append(v)
        return views

    def getPages(self):
        """Returns a list of TD pages"""
        return [o for o in FreeCAD.ActiveDocument.Objects if o.isDerivedFrom("TechDraw::DrawPage")]


# These functions need to be localized outside the command class, as they are used outside this module


def findWidget():
    "finds the manager widget, if present"

    from PySide import QtGui

    mw = FreeCADGui.getMainWindow()
    vm = mw.findChild(QtGui.QDockWidget, "BIM Views Manager")
    if vm:
        return vm
    return None


def show(item, column=None):
    "item has been double-clicked"
    import Draft

    obj = None
    vm = findWidget()
    if isinstance(item, str) or ((sys.version_info.major < 3) and isinstance(item, unicode)):
        # called from Python code
        obj = FreeCAD.ActiveDocument.getObject(item)
    else:
        # called from GUI
        if column in (1, 2):
            # user clicked the elevation or height field
            if vm:
                vm.tree.editItem(item, column)
                return
        else:
            # TODO find a way to not edit the object name
            obj = FreeCAD.ActiveDocument.getObject(item.toolTip(0))
    if obj:
        FreeCADGui.Selection.clearSelection()
        FreeCADGui.Selection.addSelection(obj)
        vparam = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View")
        if obj.isDerivedFrom("TechDraw::DrawPage"):

            # case 1: the object is a TD page. We switch to it simply
            obj.ViewObject.Visibility = True
        elif isView(obj):

            # case 2: the object is a 2D view
            ssel = [obj] + obj.Group
            FreeCADGui.Selection.clearSelection()
            for o in ssel:
                o.ViewObject.Visibility = True
                FreeCADGui.Selection.addSelection(o)
            if not hasattr(FreeCADGui.ActiveDocument.ActiveView, "getSceneGraph"):
                # Find first 3d view and switch to it
                for w in FreeCADGui.getMainWindow().getWindows():
                    if hasattr(w, "getSceneGraph"):
                        FreeCADGui.getMainWindow().setActiveWindow(w)
                        break
            FreeCADGui.runCommand("Std_OrthographicCamera")
            FreeCADGui.ActiveDocument.ActiveView.viewTop()
            FreeCADGui.SendMsgToActiveView("ViewSelection")
            FreeCADGui.ActiveDocument.ActiveView.viewTop()
            FreeCADGui.Selection.clearSelection()
            FreeCADGui.Selection.addSelection(obj)
            if PARAMS.GetBool("BimViewsSwitchBackground", False):
                vparam.SetBool("Simple", True)
                vparam.SetBool("Gradient", False)
                vparam.SetBool("RadialGradient", False)
        else:
            # case 3: This is maybe a BuildingPart. Place the WP on it")
            type = Draft.getType(obj)
            if type == "BuildingPart" or type == "IfcBuildingStorey":
                BIM_Views.activate()

    if vm:
        # store the last double-clicked item for the BIM WPView command
        if isinstance(item, str) or ((sys.version_info.major < 3) and isinstance(item, unicode)):
            vm.lastSelected = item
        else:
            vm.lastSelected = item.toolTip(0)


def isView(obj):
    """Returns true if this object is used as Source of a view on a TD page"""

    for p in obj.InList:
        if p.isDerivedFrom("TechDraw::DrawView"):
            if hasattr(p, "Source"):
                if p.Source == obj:
                    return True
    if getattr(obj, "DrawingView", False):
        return True
    if getattr(obj, "IfcType", None) == "Annotation":
        if getattr(obj, "ObjectType", "").upper() == "DRAWING":
            return True
    if getattr(obj, "Class", None) == "IfcAnnotation":
        if getattr(obj, "ObjectType", "").upper() == "DRAWING":
            return True
    return False


def getTreeViewItem(obj):
    """
    Build a QTreeWidgetItem for obj with three columns: label, elevation, height.

    Elevation comes from Placement.Base.z, falling back to the IFC Elevation
    property when the placement is at the origin. Height comes from the
    BuildingPart Height property when present. Returns the item together with
    the elevation as a number, used to sort levels vertically.
    """
    from PySide import QtCore, QtGui

    z = obj.Placement.Base.z
    elevStr = FreeCAD.Units.Quantity(z, FreeCAD.Units.Length).UserString
    if z == 0 and hasattr(obj, "Elevation"):
        z = obj.Elevation.Value
        elevStr = obj.Elevation.UserString

    heightStr = ""
    if hasattr(obj, "Height") and hasattr(obj.Height, "UserString"):
        heightStr = obj.Height.UserString

    it = QtGui.QTreeWidgetItem([obj.Label, elevStr, heightStr])
    it.setFlags(it.flags() | QtCore.Qt.ItemIsEditable)
    it.setToolTip(0, obj.Name)
    if obj.ViewObject:
        if hasattr(obj.ViewObject, "Proxy") and hasattr(obj.ViewObject.Proxy, "getIcon"):
            it.setIcon(0, QtGui.QIcon(obj.ViewObject.Proxy.getIcon()))
    return (it, z)


def getAllItemsInTree(tree_widget):
    "return list of all items in QtreeWidget"

    def get_child_items(parent_item):
        child_items = []
        # get how many sub items
        child_count = parent_item.childCount()
        for j in range(child_count):
            child_item = parent_item.child(j)
            child_items.append(child_item)
            child_items.extend(get_child_items(child_item))

        return child_items

    all_items = []
    # get top level items
    top_level_item_count = tree_widget.topLevelItemCount()
    for i in range(top_level_item_count):
        top_level_item = tree_widget.topLevelItem(i)
        all_items.append(top_level_item)
        # iterate sub-items
        all_items.extend(get_child_items(top_level_item))

    return all_items


def getParent(obj):
    "return the first parent of this object"

    if obj.getParent():
        return obj.getParent()
    else:
        for parent in obj.InList:
            if hasattr(parent, "Group") and obj in parent.Group:
                return parent


FreeCADGui.addCommand("BIM_Views", BIM_Views())
