# (C) Copyright 2008-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!

# ------------------------------------------------------------------------------
# Copyright (c) 2007, Riverbank Computing Limited
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD license.
# However, when used with the GPL version of PyQt the additional terms
# described in the PyQt GPL exception also apply

#
# Author: Riverbank Computing Limited
# ------------------------------------------------------------------------------

"""Creates a PyQt user interface for a specified UI object.
"""


from pyface.qt import QtCore, QtGui

from traitsui.menu import (
    ApplyButton,
    RevertButton,
    OKButton,
    CancelButton,
    HelpButton,
)

from .ui_base import BaseDialog

from .ui_panel import panel


# -------------------------------------------------------------------------
#  Create the different modal PyQt user interfaces.
# -------------------------------------------------------------------------


def ui_modal(ui, parent):
    """Creates a modal PyQt user interface for a specified UI object."""
    _ui_dialog(ui, parent, BaseDialog.MODAL)


def ui_nonmodal(ui, parent):
    """Creates a non-modal PyQt user interface for a specified UI object."""
    _ui_dialog(ui, parent, BaseDialog.NONMODAL)


def _ui_dialog(ui, parent, style):
    """Creates a PyQt dialog box for a specified UI object.

    Changes are not immediately applied to the underlying object.  The user
    must click **Apply** or **OK** to apply changes.  The user can revert
    changes by clicking **Revert** or **Cancel**.
    """
    if ui.owner is None:
        ui.owner = _ModalDialog()

    BaseDialog.display_ui(ui, parent, style)


class _ModalDialog(BaseDialog):
    """Modal dialog box for Traits-based user interfaces."""

    def init(self, ui, parent, style):
        """Initialise the object."""
        self.ui = ui
        self.control = ui.control
        view = ui.view

        revert = apply = False

        if self.control is not None:
            if hasattr(self, "revert"):
                revert = self.revert.isEnabled()

            if hasattr(self, "apply"):
                apply = self.apply.isEnabled()

            ui.reset()
        else:
            self.create_dialog(parent, style)

            # Create the 'context' copies we will need while editing:
            context = ui.context
            ui._context = context
            ui.context = self._copy_context(context)
            ui._revert = self._copy_context(context)

        self.set_icon(view.icon)

        # Convert the buttons to actions.
        buttons = [self.coerce_button(button) for button in view.buttons]
        nr_buttons = len(buttons)

        if (nr_buttons != 1) or (not self.is_button(buttons[0], "")):
            bbox = QtGui.QDialogButtonBox()

            # Create the necessary special function buttons.
            if nr_buttons == 0:
                if view.apply:
                    self.check_button(buttons, ApplyButton)
                    if view.revert:
                        self.check_button(buttons, RevertButton)
                if view.ok:
                    self.check_button(buttons, OKButton)
                if view.cancel:
                    self.check_button(buttons, CancelButton)
                if view.help:
                    self.check_button(buttons, HelpButton)

            for raw_button, button in zip(view.buttons, buttons):
                default = raw_button == view.default_button

                if self.is_button(button, "Apply"):
                    self.apply = self.add_button(
                        button,
                        bbox,
                        QtGui.QDialogButtonBox.ButtonRole.ApplyRole,
                        self._on_apply,
                        enabled=apply,
                        default=default,
                    )
                    ui.observe(self._on_applyable, "modified", dispatch="ui")

                elif self.is_button(button, "Revert"):
                    self.revert = self.add_button(
                        button,
                        bbox,
                        QtGui.QDialogButtonBox.ButtonRole.ResetRole,
                        self._on_revert,
                        enabled=revert,
                        default=default,
                    )

                elif self.is_button(button, "OK"):
                    self.ok = self.add_button(
                        button,
                        bbox,
                        QtGui.QDialogButtonBox.ButtonRole.AcceptRole,
                        self.control.accept,
                        default=default,
                    )
                    ui.observe(self._on_error, "errors", dispatch="ui")

                elif self.is_button(button, "Cancel"):
                    self.add_button(
                        button,
                        bbox,
                        QtGui.QDialogButtonBox.ButtonRole.RejectRole,
                        self.control.reject,
                        default=default,
                    )

                elif self.is_button(button, "Help"):
                    self.add_button(
                        button,
                        bbox,
                        QtGui.QDialogButtonBox.ButtonRole.HelpRole,
                        self._on_help,
                        default=default,
                    )

                elif not self.is_button(button, ""):
                    self.add_button(
                        button,
                        bbox,
                        QtGui.QDialogButtonBox.ButtonRole.ActionRole,
                        default=default,
                    )

        else:
            bbox = None

        self.add_contents(panel(ui), bbox)

    def close(self, rc=True):
        """Close the dialog and set the given return code."""
        super().close(rc)

        self.apply = self.revert = self.help = None

    def _copy_context(self, context):
        """Creates a copy of a *context* dictionary."""
        result = {}
        for name, value in context.items():
            if value is not None:
                result[name] = value.clone_traits()
            else:
                result[name] = None

        return result

    def _apply_context(self, from_context, to_context):
        """Applies the traits in the *from_context* to the *to_context*."""
        for name, value in from_context.items():
            if value is not None:
                to_context[name].copy_traits(value)
            else:
                to_context[name] = None

        if to_context is self.ui._context:
            on_apply = self.ui.view.on_apply
            if on_apply is not None:
                on_apply()

    def _on_finished(self, result):
        """Handles the user finishing with the dialog."""
        accept = bool(result)

        if accept:
            self._apply_context(self.ui.context, self.ui._context)
        else:
            self._apply_context(self.ui._revert, self.ui._context)

        self.close(accept)

    def _on_apply(self):
        """Handles a request to apply changes."""
        ui = self.ui
        self._apply_context(ui.context, ui._context)
        self.revert.setEnabled(True)
        ui.handler.apply(ui.info)
        ui.modified = False

    def _on_applyable(self, event):
        """Handles a change to the "modified" state of the user interface ."""
        state = event.new
        self.apply.setEnabled(state)

    def _on_revert(self):
        """Handles a request to revert changes."""
        ui = self.ui
        self._apply_context(ui._revert, ui.context)
        self._apply_context(ui._revert, ui._context)
        self.revert.setEnabled(False)
        ui.handler.revert(ui.info)
        ui.modified = False
