"""
Hi everybody =)

The common commands available via the hotbox menu concern shape's / mesh editing, but,
as a rigger, as an animator, as a render guy, you don't need these tools. What about
having your own :) ?

I introduce you with Radial Designer, a script made to help Maya users to customize their
marking menu.
The Marking menu is the radial menu opening when you press the right button of the mouse
in Maya's viewport, as you probably already know, this tool is crucial if you want to
work fast and efficiently.

Using this designer you'll be able to fastly set and edit your menus and command
associated, thus you can design a menu for animation, an other for rigging, it'll
help you and save a lot of time.

This first stable version allows you to create / edit your raidal menu, and save it
in the scene as a scriptNode. This means the next time the scene will be opened,
the node will be executed asynchronously and the default Maya's menu overrided,
then you just need to remove the scriptNode if you don't want your custom menu
anymore.

P.S : The node is named 'markingMenu_override'


You can check at [http://mehdilouala.com/RadialDesigner] for a nice looking explanation of
this script

You can also check at [http://3dbunk.com/markingMenu] for a tutorial explaining the
process step by step


Upcoming release will allow you to create sub menus and load the actual overriden
menu from the current scene. Also will be implemented the contextual appearing of
the menu.

May the peace be with you.
"""

__author__ = "Mehdi Louala"
__copyright__ = "Copyright 2017, Mehdi Louala"
__credits__ = ["Mehdi Louala"]
__license__ = "GPL"
__version__ = "1.3.2"
__maintainer__ = "Mehdi Louala"
__email__ = "mlouala@gmail.com"
__status__ = "Stable Version"

from functools import partial

try:
    from shiboken import wrapInstance as wrapinstance
    from PySide.QtCore import Qt
    from PySide.QtGui import QWidget, QCheckBox, QComboBox, QIcon, QLabel, QPushButton, QVBoxLayout, QTextEdit
    __2017__ = False
except ImportError:
    from shiboken2 import wrapInstance as wrapinstance
    from PySide2.QtCore import Qt
    from PySide2.QtWidgets import QWidget, QCheckBox, QComboBox, QLabel, QPushButton, QVBoxLayout, QTextEdit
    from PySide2.QtGui import QIcon
    __2017__ = True

from maya.cmds import cmdScrollFieldExecuter, columnLayout, window, deleteUI, warning, ls, delete, scriptNode, \
    confirmDialog
from maya.OpenMayaUI import MQtUtil
from maya.mel import eval as mel_evaluate

from radialDsg_rsc.widgets import Radial_View, Radial_Group, DefaultField
from radialDsg_rsc.misc import getMayaWindow, __dirs__, __nice_pos__, Abstract_Menu, line, col
from radialDsg_rsc.proc import MEL_proc, MEL_multiline


class radialDesigner(QWidget):
    def __init__(self, parent=None):
        self.title = 'Radial Designer'

        self.flush()

        super(radialDesigner, self).__init__(parent)
        self.setWindowTitle(self.title)
        self.setObjectName(self.title)
        self.setWindowFlags(Qt.Window)
        self.setWindowIcon(QIcon(':nodeGrapherAddNodes.png'))

        # Defining the UI
        self.setFixedWidth(400)

        L_main = QVBoxLayout(self)
        L_main.setAlignment(Qt.AlignTop)
        L_main.setContentsMargins(2, 4, 4, 4)
        L_main.setSpacing(2)
        self.setLayout(L_main)

        self.C_apply_on = QComboBox(self)
        self.C_apply_on.addItems(['Nurbs', 'Poly', 'Joint'])
        apply_on = QLabel("Apply On : ")
        apply_on.setFixedWidth(60)
        L_apply_on = line(apply_on, self.C_apply_on)
        L_apply_on.setContentsMargins(4, 0, 0, 0)
        L_main.addLayout(L_apply_on)

        # The radial viewport
        self.view = Radial_View(self)
        self.view.selectionChanged.connect(self.display_box)
        L_main.addWidget(self.view)

        def flip(target, state):
            if target.isChecked() and state:
                target.setChecked(False)

        # we create a new Radial Group and an abstract menu associated
        self.edit_boxes = {}
        for pos in __dirs__:
            box = Radial_Group(__nice_pos__[pos], self)

            title = DefaultField(pos, box)
            title.textChanged.connect(self.view[pos].updateText)
            bold = QCheckBox('Bold')
            italic = QCheckBox('Italic')
            bold.stateChanged.connect(partial(flip, italic))
            bold.stateChanged.connect(self.view[pos].setBold)
            italic.stateChanged.connect(partial(flip, bold))
            italic.stateChanged.connect(self.view[pos].setItalic)

            echo = QCheckBox('Echo Command')
            repeat = QCheckBox('Can Repeat Command')
            repeat.setChecked(True)

            combo = QComboBox(box)
            combo.addItems(['Python', 'MEL'])
            combo.currentIndexChanged.connect(partial(self.updateCodeWidget, pos))

            L_box = QVBoxLayout()
            code = self.codeWidget(parent=box)
            L_box.addLayout(line('Title', line(title, bold, italic)))
            L_box.addLayout(line('Type', combo))
            L_box.addLayout(line(echo, repeat))
            L_box.addLayout(line('Code', code))

            box.setLayout(L_box)
            box.setVisible(False)
            L_main.addWidget(box)

            self.edit_boxes[pos] = Abstract_Menu(self, title, combo, code,
                                                  (bold, italic),
                                                  (echo, repeat))
            self.view[pos].abstract = self.edit_boxes[pos]
            self.view[pos].abstract.position = pos
            box.selection = self.view[pos]

        # the final buttons
        self.B_apply = QPushButton(QIcon(':setEdAddCmd.png'), 'Apply Radial Menu', self)
        self.B_apply.clicked.connect(self.apply)

        self.B_reset = QPushButton(QIcon(':removeRenderable.png'), 'Restore default', self)
        self.B_reset.clicked.connect(self.mel)

        self.B_script_node = QPushButton(QIcon(':setEdEditMode.png'), 'ScriptNode', self)
        self.B_script_node.clicked.connect(self.scriptNode)

        # tweaking a bit the stylesheet
        self.B_script_node.setStyleSheet("QPushButton{background:#67905c;}")

        L_main.addLayout(col(line(self.B_apply, self.B_reset), self.B_script_node))
        def type_to_view(index):
            self.view.setType(self.C_apply_on.itemText(index))
        self.C_apply_on.currentIndexChanged.connect(type_to_view)

        # updating the viewport with a default starting selection
        self.display_box('N')
        self.view['N'].setSelected()
        self.edit_boxes['N']._title.setFocus()

    # UI RELATED
    def flush(self):
        """
        Cleaning the UI, removing all previous instances
        """
        wins = getMayaWindow().findChildren(QWidget, self.title) or []
        for c in wins:
            try:
                c.close()
            except RuntimeError:
                continue
            c.deleteLater()

    def codeWidget(self, type='python', parent=None):
        """
        Creates a new QTextEdit of the given language type
        :param   type: can be python or mel
        :type    type: str
        :param parent: the QWidget parent
        :type  parent: QWidget
        :return: the cmdScrollFieldExecuter class
        :rtype: QTextEdit
        """

        if __2017__:
            code = QTextEdit(parent)
        else:
            # creating a temporary window where we'll put the QTextEdit
            tempwin = window()
            columnLayout()

            # creating a new cmdScrollFieldExecuter of the given language type
            code_edit = cmdScrollFieldExecuter(sourceType=type, cco=False, sth=False)
            # wrapping the instance by name
            code = wrapinstance(long(MQtUtil.findControl(code_edit)), QTextEdit)

            # reassigning correct parent
            code.setParent(parent)
            code.nativeParentWidget()

            # removing old window
            deleteUI(tempwin)

        return code

    def updateCodeWidget(self, position, index):
        """
        This updates the QTextEdit of the given Radial Group, killing old one
        reparenting a new one, updating abstract
        :param position: position name
        :type position: str
        :param index: wrap the Signal
        """
        pop = self.edit_boxes[position]
        old = pop.code

        # Killing the previous code widget
        pop.box.findChildren(QLabel)[-1].deleteLater()
        pop._code.deleteLater()

        # Asking for a new one
        code = self.codeWidget(parent=pop.box, type=pop.type.lower())

        # new line layout containing our code widget
        l = line('Code', code)
        pop.box.layout().addLayout(l)

        # assigning the code widget to the Radial Group
        pop.setCodeWidget(code)
        pop.code = old

    def display_box(self, position):
        """
        This toggle the Radial Group to display only the wanted one
        :param position: Position name
        :type  position: str
        """
        for pos in self.edit_boxes:
            if pos != position:
                self.edit_boxes[pos].box.setVisible(False)

        self.edit_boxes[position].box.setVisible(True)

    # MAYA RELATED
    def apply(self):
        """
        Applying the current Radial to the scene
        """
        try:
            self.mel(**{self.C_apply_on.currentText().lower():self.view.format_items()})

        except RuntimeError:
            import sys
            print sys.exc_info()
            warning('Syntax Error occured, restoring default Marking Menu')
            self.mel()

    def mel(self, *args, **kwargs):
        """
        evaluate the MEL returned by the main procedure wrapper MEL_proc in proc.py
        """
        mel_evaluate(MEL_proc(*args, **kwargs))

    def scriptNode(self):
        """
        Create a scriptNode containing the current Marking Menu, delete the previous one
        if exists
        """
        node = 'markingMenu_override'
        var_name = 'RadialDesigner_Script_INIT'

        for item in self.view:
            if not item.empty and item.abstract.type == 'Python':
                if '"' in item.abstract.code:
                    res = confirmDialog(title='Execution hazard',
                                        message='The command <b>\'%s\'</b> is a Python command with " (double quotes) '
                                                'inside, this may create issue while reading the scriptNode, do you '
                                                'want to convert them to \' (simple quotes) ?<br>P.S : This is HIGHLY '
                                                'recommended' % item.name, button=['Yes','No'],
                                        defaultButton='Yes', cancelButton='No', dismissString='No' )
                    if res == 'Yes':
                        item.abstract.code = item.abstract.code.replace('"', '\'')

        # defining the contextual menu from the self.view
        script = MEL_proc(**{self.C_apply_on.currentText().lower():self.view.format_items(node_format=True)})
        script = '''%s
warning "Overriding Maya's Marking Menu, some of the original one features";
warning "may have been disabled, remove the \\"%s\\" node to restore original";
evalDeferred $%s;
eval $%s;''' % (MEL_multiline(script, var_name), node, var_name, var_name)

        # generating a default function for the restore
        restore = '''%s
warning "Restoring original Maya's Marking menu.";
eval $%s''' % (MEL_multiline(MEL_proc(), var_name), var_name)

        # we get rid of the previous one
        if len(ls(node)):
            delete(node)
            warning('"%s" already exists, removing..' % node)

        # beforeScript executes when file loads,
        # afterScript executes when node is deleted
        scriptNode(st=2, bs=script, afterScript=restore, n=node, stp='mel')

        warning('scriptNode "%s" correctly created, reload the scene' % node)

def displayRadialDesigner():
    """
    Display function
    """
    radialDsg_Win = radialDesigner(getMayaWindow())
    radialDsg_Win.show()
    return radialDsg_Win