# -*- coding: utf-8 -*-
#############################################################################
##
## Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
## Contact: http:#www.qt-project.org/legal
##
## self file is part of the Qt Solutions component.
##
## $QT_BEGIN_LICENSE:BSD$
## You may use self file under the terms of the BSD license as follows:
##
## "Redistribution and use in source and binary forms, with or without
## modification, are permitted provided that the following conditions are
## met:
##   * Redistributions of source code must retain the above copyright
##     notice, self list of conditions and the following disclaimer.
##   * Redistributions in binary form must reproduce the above copyright
##     notice, self list of conditions and the following disclaimer in
##     the documentation and/or other materials provided with the
##     distribution.
##   * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
##     of its contributors may be used to endorse or promote products derived
##     from self software without specific prior written permission.
##
##
## self SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES LOSS OF USE,
## DATA, OR PROFITS OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
## OF self SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
##
## $QT_END_LICENSE$
##
############################################################################/
import sys
sys.path.append('QtProperty')
sys.path.append('libqt5')
from PyQt5.QtCore import QVariant, QTimeLine
from PyQt5.QtGui import QTextDocument
from PyQt5.QtWidgets import (
    QDialog,
    QComboBox,
    QToolButton,
    QDialogButtonBox,
    QVBoxLayout,
    QHBoxLayout,
    QWidget,
    QPushButton,
    QTreeWidget,
    QCalendarWidget,
    QAction, 
    QApplication
)
from pyqtcore import QMap, QList, QMapMap
from qtvariantproperty import QtVariantPropertyManager, QtVariantEditorFactory
from qttreepropertybrowser import QtTreePropertyBrowser

class ObjectControllerPrivate():
    def __init__(self):
        self.q_ptr = 0
        self.m_object = 0
        self.m_classToProperty = QMap()
        self.m_propertyToClass = QMap()
        self.m_propertyToIndex = QMap()
        self.m_classToIndexToProperty = QMapMap()
        self.m_propertyToExpanded = QMap()
        self.m_topLevelProperties = QList()
        self.m_browser = 0
        self.m_manager = 0
        self.m_readOnlyManager = 0

    def enumToInt(self, metaEnum, enumValue):
        valueMap = QMap() # dont show multiple enum values which have the same values
        pos = 0
        for i in range(metaEnum.keyCount()):
            value = metaEnum.value(i)
            if (not valueMap.contains(value)):
                if (value == enumValue):
                    return pos
                valueMap[value] = pos
                pos += 1

        return -1

    def intToEnum(self, metaEnum, intValue):
        valueMap = QMap() # dont show multiple enum values which have the same values
        values = QList()
        for i in range(metaEnum.keyCount()):
            value = metaEnum.value(i)
            if (not valueMap.contains(value)):
                valueMap[value] = True
                values.append(value)

        if (intValue >= values.count()):
            return -1
        return values.at(intValue)

    def isSubValue(self, value, subValue):
        if (value == subValue):
            return True
        i = 0
        while (subValue):
            if (not (value & (1 << i))):
                if (subValue & 1):
                    return False

            i += 1
            subValue = subValue >> 1

        return True

    def isPowerOf2(self, value):
        while (value):
            if (value & 1):
                return value == 1

            value = value >> 1

        return False

    def flagToInt(self, metaEnum, flagValue):
        if (not flagValue):
            return 0
        intValue = 0
        valueMap = QMap() # dont show multiple enum values which have the same values
        pos = 0
        for i in range(metaEnum.keyCount()):
            value = metaEnum.value(i)
            if (not valueMap.contains(value) and self.isPowerOf2(value)):
                if (self.isSubValue(flagValue, value)):
                    intValue |= (1 << pos)
                valueMap[value] = pos
                pos += 1

        return intValue

    def intToFlag(self, metaEnum, intValue):
        valueMap = QMap() # dont show multiple enum values which have the same values
        values = QList()
        for i in range(metaEnum.keyCount()):
            value = metaEnum.value(i)
            if (not valueMap.contains(value) and self.isPowerOf2(value)):
                valueMap[value] = True
                values.append(value)

        flagValue = 0
        temp = intValue
        i = 0
        while (temp):
            if (i >= values.count()):
                return -1
            if (temp & 1):
                flagValue |= values.at(i)
            i += 1
            temp = temp >> 1

        return flagValue

    def updateClassProperties(self, metaObject, recursive):
        if (not metaObject):
            return

        if (recursive):
            self.updateClassProperties(metaObject.superClass(), recursive)

        classProperty = self.m_classToProperty.value(metaObject)
        if (not classProperty):
            return

        for idx in range(metaObject.propertyOffset(), metaObject.propertyCount(), 1):
            metaProperty = metaObject.property(idx)
            if (metaProperty.isReadable()):
                if (self.m_classToIndexToProperty.contains(metaObject) and self.m_classToIndexToProperty[metaObject].contains(idx)):
                    subProperty = self.m_classToIndexToProperty[metaObject][idx]
                    if (metaProperty.isEnumType()):
                        if (metaProperty.isFlagType()):
                            subProperty.setValue(self.flagToInt(metaProperty.enumerator(), metaProperty.read(self.m_object)))
                        else:
                            subProperty.setValue(self.enumToInt(metaProperty.enumerator(), metaProperty.read(self.m_object)))
                    else: 
                        subProperty.setValue(metaProperty.read(self.m_object))

    def addClassProperties(self, metaObject):
        if (not metaObject):
            return
        self.addClassProperties(metaObject.superClass())
        classProperty = self.m_classToProperty.value(metaObject)
        if (not classProperty):
            className = metaObject.className()
            classProperty = self.m_manager.addProperty(QtVariantPropertyManager.groupTypeId(), className)
            self.m_classToProperty[metaObject] = classProperty
            self.m_propertyToClass[classProperty] = metaObject

            for idx in range(metaObject.propertyOffset(), metaObject.propertyCount(), 1):
                metaProperty = metaObject.property(idx)
                type = metaProperty.userType()
                subProperty = 0
                if (not metaProperty.isReadable()):
                    subProperty = self.m_readOnlyManager.addProperty(QVariant.String, metaProperty.name())
                    subProperty.setValue("< Non Readable >")
                elif (metaProperty.isEnumType()):
                    if (metaProperty.isFlagType()):
                        subProperty = self.m_manager.addProperty(QtVariantPropertyManager.flagTypeId(), metaProperty.name())
                        metaEnum = metaProperty.enumerator()
                        valueMap = QMap()
                        flagNames = QList()
                        for i in range(metaEnum.keyCount()):
                            value = metaEnum.value(i)
                            if (not valueMap.contains(value) and self.isPowerOf2(value)):
                                valueMap[value] = True
                                flagNames.append(metaEnum.key(i))

                        subProperty.setAttribute("flagNames", flagNames)
                        subProperty.setValue(self.flagToInt(metaEnum, metaProperty.read(self.m_object)))

                    else: 
                        subProperty = self.m_manager.addProperty(QtVariantPropertyManager.enumTypeId(), metaProperty.name())
                        metaEnum = metaProperty.enumerator()
                        valueMap = QMap() # dont show multiple enum values which have the same values
                        enumNames = QList()
                        for i in range(metaEnum.keyCount()):
                            value = metaEnum.value(i)
                            if (not valueMap.contains(value)):
                                valueMap[value] = True
                                enumNames.append(metaEnum.key(i))

                        subProperty.setAttribute("enumNames", enumNames)
                        subProperty.setValue(self.enumToInt(metaEnum, metaProperty.read(self.m_object)))

                elif (self.m_manager.isPropertyTypeSupported(type)):
                    if (not metaProperty.isWritable()):
                        subProperty = self.m_readOnlyManager.addProperty(type, metaProperty.name() + " (Non Writable)")
                    if (not metaProperty.isDesignable()):
                        subProperty = self.m_readOnlyManager.addProperty(type, metaProperty.name() + " (Non Designable)")
                    else:
                        subProperty = self.m_manager.addProperty(type, metaProperty.name())
                    subProperty.setValue(metaProperty.read(self.m_object))
                else: 
                    subProperty = self.m_readOnlyManager.addProperty(QVariant.String, metaProperty.name())
                    subProperty.setValue("< Unknown Type >")
                    subProperty.setEnabled(False)

                classProperty.addSubProperty(subProperty)
                self.m_propertyToIndex[subProperty] = idx
                self.m_classToIndexToProperty[metaObject][idx] = subProperty

        else: 
            self.updateClassProperties(metaObject, False)

        self.m_topLevelProperties.append(classProperty)
        self.m_browser.addProperty(classProperty)

    def saveExpandedState(self):
        pass

    def restoreExpandedState(self):
        pass

    def slotValueChanged(self, property, value):
        if (not self.m_propertyToIndex.contains(property)):
            return

        idx = self.m_propertyToIndex.value(property)

        metaObject = self.m_object.metaObject()
        metaProperty = metaObject.property(idx)
        if (metaProperty.isEnumType()):
            if (metaProperty.isFlagType()):
                metaProperty.write(self.m_object, self.intToFlag(metaProperty.enumerator(), value))
            else:
                metaProperty.write(self.m_object, self.intToEnum(metaProperty.enumerator(), value))
        else: 
            metaProperty.write(self.m_object, value)

        self.updateClassProperties(metaObject, True)

##########

class ObjectController(QWidget):
    def __init__(self, parent=None):
        super(ObjectController, self).__init__(parent)
        self.d_ptr = ObjectControllerPrivate()
        self.d_ptr.q_ptr = self
        self.d_ptr.m_object = 0

        ##
        #    scroll = QScrollArea(self)
        #    scroll.setWidgetResizable(True)
        #
        #    self.d_ptr.m_browser = QtGroupBoxPropertyBrowser(self)
        #    layout = QVBoxLayout(self)
        #    layout.setMargin(0)
        #    layout.addWidget(scroll)
        #    scroll.setWidget(self.d_ptr.m_browser)
        ##
        browser = QtTreePropertyBrowser(self)
        browser.setRootIsDecorated(False)
        self.d_ptr.m_browser = browser
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.d_ptr.m_browser)

        self.d_ptr.m_readOnlyManager = QtVariantPropertyManager(self)
        self.d_ptr.m_manager = QtVariantPropertyManager(self)
        factory = QtVariantEditorFactory(self)
        self.d_ptr.m_browser.setFactoryForManager(self.d_ptr.m_manager, factory)

        self.d_ptr.m_manager.valueChangedSignal.connect(self.d_ptr.slotValueChanged)

    def __del__(self):
        del self.d_ptr

    def setObject(self, object):
        if (self.d_ptr.m_object == object):
            return

        if (self.d_ptr.m_object):
            self.d_ptr.saveExpandedState()
            for it in self.d_ptr.m_topLevelProperties:
                self.d_ptr.m_browser.removeProperty(it)

            self.d_ptr.m_topLevelProperties.clear()

        self.d_ptr.m_object = object

        if (not self.d_ptr.m_object):
            return

        self.d_ptr.addClassProperties(self.d_ptr.m_object.metaObject())

        self.d_ptr.restoreExpandedState()

    def object(self):
        return self.d_ptr.m_object

class MyController(QDialog):
    def __init__(self, parent=None):
        super(MyController, self).__init__(parent)

        self.theClassNames = QList()
        self.theClassCombo = QComboBox(self)
        self.theControlledObject = None

        button = QToolButton(self)
        self.theController = ObjectController(self)
        buttonBox = QDialogButtonBox(self)

        button.clicked.connect(self.createAndControl)
        buttonBox.rejected.connect(self.reject)

        button.setText(self.tr("Create And Control"))
        buttonBox.setStandardButtons(QDialogButtonBox.Close)

        layout = QVBoxLayout(self)
        internalLayout = QHBoxLayout()
        internalLayout.addWidget(self.theClassCombo)
        internalLayout.addWidget(button)
        layout.addLayout(internalLayout)
        layout.addWidget(self.theController)
        layout.addWidget(buttonBox)

        self.theClassNames.append("QWidget")
        self.theClassNames.append("QPushButton")
        self.theClassNames.append("QDialogButtonBox")
        self.theClassNames.append("QTreeWidget")
        self.theClassNames.append("QCalendarWidget")
        self.theClassNames.append("QAction")
        self.theClassNames.append("QTimeLine")
        self.theClassNames.append("QTextDocument")

        self.theClassCombo.addItems(self.theClassNames)

    def __del__(self):
        if (self.theControlledObject):
            del self.theControlledObject

    def createAndControl(self):
        newObject = 0
        className = self.theClassNames.at(self.theClassCombo.currentIndex())
        if (className == "QWidget"):
            newObject = QWidget()
        elif (className == "QPushButton"):
            newObject = QPushButton()
        elif (className == "QDialogButtonBox"):
            newObject = QDialogButtonBox()
        elif (className == "QTreeWidget"):
            newObject = QTreeWidget()
        elif (className == "QCalendarWidget"):
            newObject = QCalendarWidget()
        elif (className == "QAction"):
            newObject = QAction(None)
        elif (className == "QTimeLine"):
            newObject = QTimeLine()
        elif (className == "QTextDocument"):
            newObject = QTextDocument()

        if (not newObject):
            return

        newWidget = newObject
        if hasattr(newWidget, 'geometry'):
            r = newWidget.geometry()
            r.setSize(newWidget.sizeHint())
            r.setWidth(max(r.width(), 150))
            r.setHeight(max(r.height(), 50))
            r.moveCenter(QApplication.desktop().geometry().center())
            newWidget.setGeometry(r)
            newWidget.setWindowTitle(self.tr("Controlled Object: %s"%className))
            newWidget.show()

        if (self.theControlledObject):
            del self.theControlledObject

        self.theControlledObject = newObject
        self.theController.setObject(self.theControlledObject)

if __name__ == '__main__':
    app = QApplication(sys.argv)

    controller = MyController()
    controller.show()

    ret = app.exec()
    sys.exit(ret)
