text: # -*- coding: utf-8 -*-
  
  """
  Exception Widgets
  =================
  Provides all definitions required for handling exceptions in *GuiPy*.
  
  """
  
  
  # %% IMPORTS
  # Built-in imports
  from traceback import format_exception_only, format_tb
  
  # Package imports
  from qtpy import QtCore as QC, QtWidgets as QW
  
  # GuiPy imports
  from guipy import layouts as GL, widgets as GW
  
  # All declaration
  __all__ = ['ExceptionDialog', 'create_exception_handler']
  
  
  # %% CLASS DEFINITIONS
  # Make special class for showing exception details
  class ExceptionDialog(GW.QDialog):
      """
      Defines the :class:`~ExceptionDialog` class.
  
      This class takes a set of exception details and converts it into a format
      that can be shown using a dialog.
  
      """
  
      def __init__(self, etype, value, tb, parent=None):
          """
          Initialize an instance of the :class:`~ExceptionDialog` class.
  
          Parameters
          ----------
          etype : :class:`~Exception` class
              The :class:`~Exception` class that is associated with this error.
          value : :obj:`~Exception` object
              The :class:`~Exception` instance that is associated with this
              error.
          tb : traceback object
              The corresponding traceback object.
  
          Optional
          --------
          parent : :obj:`~PyQt5.QtWidgets.QWidget` object or None. Default: None
              The parent widget for this dialog or *None* for no parent.
  
          """
  
          # Save the provided values
          self.etype = etype
          self.value = value
          self.tb = tb
  
          # Call the super constructor
          super().__init__(parent)
  
          # Initialize the exception dialog
          self.init()
  
      # This function creates the exception dialog
      def init(self):
          """
          Sets up the exception dialog after it has been initialized.
  
          This function is mainly responsible for gathering all required
          information; formatting it; and drawing the dialog.
  
          """
  
          # Create a window layout
          grid_layout = GL.QGridLayout(self)
          grid_layout.setColumnStretch(2, 1)
          grid_layout.setRowStretch(3, 1)
  
          # Set properties of message box
          self.setWindowModality(QC.Qt.ApplicationModal)
          self.setAttribute(QC.Qt.WA_DeleteOnClose)
          self.setWindowTitle("ERROR")
          self.setWindowFlags(
              QC.Qt.MSWindowsOwnDC |
              QC.Qt.Dialog |
              QC.Qt.WindowTitleHint |
              QC.Qt.WindowSystemMenuHint |
              QC.Qt.WindowCloseButtonHint)
  
          # Set the icon of the exception on the left
          icon_label = GW.QLabel()
          pixmap = GW.QMessageBox.standardIcon(GW.QMessageBox.Critical)
          icon_label.setPixmap(pixmap)
          grid_layout.addWidget(icon_label, 0, 0, 2, 1, QC.Qt.AlignTop)
  
          # Add a spacer item
          spacer_item = QW.QSpacerItem(7, 1, QW.QSizePolicy.Fixed,
                                       QW.QSizePolicy.Fixed)
          grid_layout.addItem(spacer_item, 0, 1, 2, 1)
  
          # Set the text of the exception
          exc_str = self.format_exception()
          exc_label = GW.QLabel(exc_str)
          grid_layout.addWidget(exc_label, 0, 2, 1, 1)
  
          # Create a button box for the buttons
          button_box = QW.QDialogButtonBox()
          grid_layout.addWidget(button_box, 2, 0, 1, grid_layout.columnCount())
  
          # Create traceback box
          self.tb_box = self.create_traceback_box()
          grid_layout.addWidget(self.tb_box, 3, 0, 1, grid_layout.columnCount())
  
          # Create traceback button
          self.tb_but =\
              button_box.addButton(self.tb_labels[self.tb_box.isHidden()],
                                   button_box.ActionRole)
          self.tb_but.clicked.connect(self.toggle_traceback_box)
  
          # Create an 'ok' button
          ok_but = button_box.addButton(button_box.Ok)
          ok_but.clicked.connect(self.close)
          ok_but.setDefault(True)
  
          # Update the size
          self.update_size()
  
      # This function formats the exception string
      def format_exception(self):
          """
          Formats the exception provided during initialization and returns it.
  
          """
  
          # Format the exception
          exc_list = format_exception_only(self.etype, self.value)
          exc_str = ''.join(exc_list)
  
          # Return it
          return(exc_str)
  
      # This function formats the traceback string
      def format_traceback(self):
          """
          Formats the traceback provided during initialization and returns it.
  
          """
  
          # Format the traceback
          tb_list = format_tb(self.tb)
          tb_str = ''.join(tb_list)
  
          # Return it
          return(tb_str)
  
      # This function creates the traceback box
      def create_traceback_box(self):
          """
          Creates a special box for the exception dialog that contains the
          traceback information and returns it.
  
          """
  
          # Create a traceback box
          traceback_box = GW.QWidget()
          traceback_box.setHidden(True)
  
          # Create layout
          layout = GL.QVBoxLayout(traceback_box)
          layout.setContentsMargins(0, 0, 0, 0)
  
          # Add a horizontal line to the layout
          layout.addSeparator()
  
          # Format the traceback
          tb_str = self.format_traceback()
  
          # Add a textedit to the layout
          tb_text_box = GW.QTextEdit(traceback_box)
          tb_text_box.setMinimumHeight(100)
          tb_text_box.setFocusPolicy(QC.Qt.NoFocus)
          tb_text_box.setReadOnly(True)
          tb_text_box.setText(tb_str)
          layout.addWidget(tb_text_box)
  
          # Create a 'show traceback' button
          self.tb_labels = ['Hide Traceback...', 'Show Traceback...']
  
          # Return traceback box
          return(traceback_box)
  
      # This function shows or hides the traceback box
      @QC.Slot()
      def toggle_traceback_box(self):
          """
          Toggles the visibility of the traceback box and updates the dimensions
          of the exception dialog accordingly.
  
          """
  
          # Toggle the visibility of the traceback box
          self.tb_box.setVisible(self.tb_box.isHidden())
          self.tb_but.setText(self.tb_labels[self.tb_box.isHidden()])
  
          # Update the size of the message box
          self.update_size()
  
      # This function updates the size of the dialog
      def update_size(self):
          """
          Updates the dimensions of the exception dialog depending on its current
          state (traceback box visibility).
  
          """
  
          # Determine the minimum/maximum size required for making the dialog
          min_size = self.layout().minimumSize()
          max_size = self.layout().maximumSize()
  
          # Set the fixed width
          self.setFixedWidth(min_size.width())
  
          # If the traceback box is shown, set minimum/maximum height
          if self.tb_box.isVisible():
              self.setMinimumHeight(min_size.height())
              self.setMaximumHeight(max_size.height())
          # Else, set fixed height
          else:
              self.setFixedHeight(min_size.height())
  
  
  # %% FUNCTION DEFINITIONS
  # This function factory creates a function for handling exceptions
  def create_exception_handler(parent=None):
      """
      Function factory that returns a function definition
      ``show_exception_details(*args, **kwargs)``.
  
      Calling the returned definition will automatically initialize an
      :obj:`~guipy.widgets.exceptions.ExceptionDialog` object using the provided
      `parent`, `args` and `kwargs`; and show the dialog.
  
      Optional
      --------
      parent : :obj:`~PyQt5.QtWidgets.QWidget` object or None. Default: None
          The parent widget to use for the exception dialog or *None* for no
          parent.
  
      """
  
      # This function creates a message box with exception information
      def show_exception_details(*args, **kwargs):
          """
          Creates an instance of the :class:`~ExceptionDialog` class and shows
          it.
  
          Parameters
          ----------
          args : positional arguments
              The positional arguments that must be passed to the constructor of
              the :class:`~guipy.widgets.exceptions.ExceptionDialog` class.
          kwargs : keyword arguments
              The keyword arguments that must be passed to the constructor of the
              :class:`~guipy.widgets.exceptions.ExceptionDialog` class.
  
          """
  
          # Create exception message box
          exception_box = ExceptionDialog(*args, parent=parent, **kwargs)
  
          # Emit the exception signal of the parent if it has it
          if hasattr(parent, 'exception'):
              parent.exception.emit()
  
          # Show the exception message box
          exception_box.show()
  
      # Return function definition
      return(show_exception_details)
  