text: # -*- coding: utf-8 -*-
  import os
  import re
  import copy
  import collections
  from Qt import QtWidgets, QtCore, QtGui
  
  from avalon.vendor import qtawesome
  
  from openpype.widgets.attribute_defs import create_widget_for_attr_def
  from openpype.tools.flickcharm import FlickCharm
  from openpype.tools.utils import PlaceholderLineEdit
  from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
  from .models import (
      AssetsHierarchyModel,
      TasksModel,
      RecursiveSortFilterProxyModel,
  )
  from .icons import (
      get_pixmap,
      get_icon_path
  )
  
  from ..constants import (
      VARIANT_TOOLTIP
  )
  
  
  class PixmapLabel(QtWidgets.QLabel):
      """Label resizing image to height of font."""
      def __init__(self, pixmap, parent):
          super(PixmapLabel, self).__init__(parent)
          self._source_pixmap = pixmap
  
      def set_source_pixmap(self, pixmap):
          """Change source image."""
          self._source_pixmap = pixmap
          self._set_resized_pix()
  
      def _set_resized_pix(self):
          size = self.fontMetrics().height()
          size += size % 2
          self.setPixmap(
              self._source_pixmap.scaled(
                  size,
                  size,
                  QtCore.Qt.KeepAspectRatio,
                  QtCore.Qt.SmoothTransformation
              )
          )
  
      def resizeEvent(self, event):
          self._set_resized_pix()
          super(PixmapLabel, self).resizeEvent(event)
  
  
  class TransparentPixmapLabel(QtWidgets.QLabel):
      """Transparent label resizing to width and height of font."""
      def __init__(self, *args, **kwargs):
          super(TransparentPixmapLabel, self).__init__(*args, **kwargs)
  
      def resizeEvent(self, event):
          size = self.fontMetrics().height()
          size += size % 2
          pix = QtGui.QPixmap(size, size)
          pix.fill(QtCore.Qt.transparent)
          self.setPixmap(pix)
          super(TransparentPixmapLabel, self).resizeEvent(event)
  
  
  class IconValuePixmapLabel(PixmapLabel):
      """Label resizing to width and height of font.
  
      Handle icon parsing from creators/instances. Using of QAwesome module
      of path to images.
      """
      fa_prefixes = ["", "fa."]
      default_size = 200
  
      def __init__(self, icon_def, parent):
          source_pixmap = self._parse_icon_def(icon_def)
  
          super(IconValuePixmapLabel, self).__init__(source_pixmap, parent)
  
      def set_icon_def(self, icon_def):
          """Set icon by it's definition name.
  
          Args:
              icon_def (str): Name of FontAwesome icon or path to image.
          """
          source_pixmap = self._parse_icon_def(icon_def)
          self.set_source_pixmap(source_pixmap)
  
      def _default_pixmap(self):
          pix = QtGui.QPixmap(1, 1)
          pix.fill(QtCore.Qt.transparent)
          return pix
  
      def _parse_icon_def(self, icon_def):
          if not icon_def:
              return self._default_pixmap()
  
          if isinstance(icon_def, QtGui.QPixmap):
              return icon_def
  
          if isinstance(icon_def, QtGui.QIcon):
              return icon_def.pixmap(self.default_size, self.default_size)
  
          try:
              if os.path.exists(icon_def):
                  return QtGui.QPixmap(icon_def)
          except Exception:
              # TODO logging
              pass
  
          for prefix in self.fa_prefixes:
              try:
                  icon_name = "{}{}".format(prefix, icon_def)
                  icon = qtawesome.icon(icon_name, color="white")
                  return icon.pixmap(self.default_size, self.default_size)
              except Exception:
                  # TODO logging
                  continue
  
          return self._default_pixmap()
  
  
  class ContextWarningLabel(PixmapLabel):
      """Pixmap label with warning icon."""
      def __init__(self, parent):
          pix = get_pixmap("warning")
  
          super(ContextWarningLabel, self).__init__(pix, parent)
  
          self.setToolTip(
              "Contain invalid context. Please check details."
          )
          self.setObjectName("FamilyIconLabel")
  
  
  class IconButton(QtWidgets.QPushButton):
      """PushButton with icon and size of font.
  
      Using font metrics height as icon size reference.
      """
  
      def __init__(self, *args, **kwargs):
          super(IconButton, self).__init__(*args, **kwargs)
          self.setObjectName("IconButton")
  
      def sizeHint(self):
          result = super(IconButton, self).sizeHint()
          icon_h = self.iconSize().height()
          font_height = self.fontMetrics().height()
          text_set = bool(self.text())
          if not text_set and icon_h < font_height:
              new_size = result.height() - icon_h + font_height
              result.setHeight(new_size)
              result.setWidth(new_size)
  
          return result
  
  
  class PublishIconBtn(IconButton):
      """Button using alpha of source image to redraw with different color.
  
      Main class for buttons showed in publisher.
  
      TODO:
      Add different states:
      - normal           : before publishing
      - publishing       : publishing is running
      - validation error : validation error happened
      - error            : other error happened
      - success          : publishing finished
      """
      def __init__(self, pixmap_path, *args, **kwargs):
          super(PublishIconBtn, self).__init__(*args, **kwargs)
  
          loaded_image = QtGui.QImage(pixmap_path)
  
          pixmap = self.paint_image_with_color(loaded_image, QtCore.Qt.white)
  
          self._base_image = loaded_image
          self._enabled_icon = QtGui.QIcon(pixmap)
          self._disabled_icon = None
  
          self.setIcon(self._enabled_icon)
  
      def get_enabled_icon(self):
          """Enabled icon."""
          return self._enabled_icon
  
      def get_disabled_icon(self):
          """Disabled icon."""
          if self._disabled_icon is None:
              pixmap = self.paint_image_with_color(
                  self._base_image, QtCore.Qt.gray
              )
              self._disabled_icon = QtGui.QIcon(pixmap)
          return self._disabled_icon
  
      @staticmethod
      def paint_image_with_color(image, color):
          """Redraw image with single color using it's alpha.
  
          It is expected that input image is singlecolor image with alpha.
  
          Args:
              image (QImage): Loaded image with alpha.
              color (QColor): Color that will be used to paint image.
          """
          width = image.width()
          height = image.height()
          partition = 8
          part_w = int(width / partition)
          part_h = int(height / partition)
          part_w -= part_w % 2
          part_h -= part_h % 2
          scaled_image = image.scaled(
              width - (2 * part_w),
              height - (2 * part_h),
              QtCore.Qt.IgnoreAspectRatio,
              QtCore.Qt.SmoothTransformation
          )
          alpha_mask = scaled_image.createAlphaMask()
          alpha_region = QtGui.QRegion(QtGui.QBitmap.fromImage(alpha_mask))
          alpha_region.translate(part_w, part_h)
  
          pixmap = QtGui.QPixmap(width, height)
          pixmap.fill(QtCore.Qt.transparent)
  
          painter = QtGui.QPainter(pixmap)
          painter.setClipRegion(alpha_region)
          painter.setPen(QtCore.Qt.NoPen)
          painter.setBrush(color)
          painter.drawRect(QtCore.QRect(0, 0, width, height))
          painter.end()
  
          return pixmap
  
      def setEnabled(self, enabled):
          super(PublishIconBtn, self).setEnabled(enabled)
          if self.isEnabled():
              self.setIcon(self.get_enabled_icon())
          else:
              self.setIcon(self.get_disabled_icon())
  
  
  class ResetBtn(PublishIconBtn):
      """Publish reset button."""
      def __init__(self, parent=None):
          icon_path = get_icon_path("refresh")
          super(ResetBtn, self).__init__(icon_path, parent)
          self.setToolTip("Refresh publishing")
  
  
  class StopBtn(PublishIconBtn):
      """Publish stop button."""
      def __init__(self, parent):
          icon_path = get_icon_path("stop")
          super(StopBtn, self).__init__(icon_path, parent)
          self.setToolTip("Stop/Pause publishing")
  
  
  class ValidateBtn(PublishIconBtn):
      """Publish validate button."""
      def __init__(self, parent=None):
          icon_path = get_icon_path("validate")
          super(ValidateBtn, self).__init__(icon_path, parent)
          self.setToolTip("Validate")
  
  
  class PublishBtn(PublishIconBtn):
      """Publish start publish button."""
      def __init__(self, parent=None):
          icon_path = get_icon_path("play")
          super(PublishBtn, self).__init__(icon_path, "Publish", parent)
          self.setToolTip("Publish")
  
  
  class CreateInstanceBtn(PublishIconBtn):
      """Create add button."""
      def __init__(self, parent=None):
          icon_path = get_icon_path("add")
          super(CreateInstanceBtn, self).__init__(icon_path, parent)
          self.setToolTip("Create new instance")
  
  
  class CopyPublishReportBtn(PublishIconBtn):
      """Copy report button."""
      def __init__(self, parent=None):
          icon_path = get_icon_path("copy")
          super(CopyPublishReportBtn, self).__init__(icon_path, parent)
          self.setToolTip("Copy report")
  
  
  class SavePublishReportBtn(PublishIconBtn):
      """Save report button."""
      def __init__(self, parent=None):
          icon_path = get_icon_path("download_arrow")
          super(SavePublishReportBtn, self).__init__(icon_path, parent)
          self.setToolTip("Export and save report")
  
  
  class ShowPublishReportBtn(PublishIconBtn):
      """Show report button."""
      def __init__(self, parent=None):
          icon_path = get_icon_path("view_report")
          super(ShowPublishReportBtn, self).__init__(icon_path, parent)
          self.setToolTip("Show details")
  
  
  class RemoveInstanceBtn(PublishIconBtn):
      """Create remove button."""
      def __init__(self, parent=None):
          icon_path = get_icon_path("delete")
          super(RemoveInstanceBtn, self).__init__(icon_path, parent)
          self.setToolTip("Remove selected instances")
  
  
  class ChangeViewBtn(PublishIconBtn):
      """Create toggle view button."""
      def __init__(self, parent=None):
          icon_path = get_icon_path("change_view")
          super(ChangeViewBtn, self).__init__(icon_path, parent)
          self.setToolTip("Swap between views")
  
  
  class AbstractInstanceView(QtWidgets.QWidget):
      """Abstract class for instance view in creation part."""
      selection_changed = QtCore.Signal()
      active_changed = QtCore.Signal()
      # Refreshed attribute is not changed by view itself
      # - widget which triggers `refresh` is changing the state
      # TODO store that information in widget which cares about refreshing
      refreshed = False
  
      def set_refreshed(self, refreshed):
          """View is refreshed with last instances.
  
          Views are not updated all the time. Only if are visible.
          """
          self.refreshed = refreshed
  
      def refresh(self):
          """Refresh instances in the view from current `CreatedContext`."""
          raise NotImplementedError((
              "{} Method 'refresh' is not implemented."
          ).format(self.__class__.__name__))
  
      def get_selected_items(self):
          """Selected instances required for callbacks.
  
          Example: When delete button is clicked to know what should be deleted.
          """
          raise NotImplementedError((
              "{} Method 'get_selected_items' is not implemented."
          ).format(self.__class__.__name__))
  
  
  class ClickableFrame(QtWidgets.QFrame):
      """Widget that catch left mouse click and can trigger a callback.
  
      Callback is defined by overriding `_mouse_release_callback`.
      """
      def __init__(self, parent):
          super(ClickableFrame, self).__init__(parent)
  
          self._mouse_pressed = False
  
      def _mouse_release_callback(self):
          pass
  
      def mousePressEvent(self, event):
          if event.button() == QtCore.Qt.LeftButton:
              self._mouse_pressed = True
          super(ClickableFrame, self).mousePressEvent(event)
  
      def mouseReleaseEvent(self, event):
          if self._mouse_pressed:
              self._mouse_pressed = False
              if self.rect().contains(event.pos()):
                  self._mouse_release_callback()
  
          super(ClickableFrame, self).mouseReleaseEvent(event)
  
  
  class AssetsDialog(QtWidgets.QDialog):
      """Dialog to select asset for a context of instance."""
      def __init__(self, controller, parent):
          super(AssetsDialog, self).__init__(parent)
          self.setWindowTitle("Select asset")
  
          model = AssetsHierarchyModel(controller)
          proxy_model = RecursiveSortFilterProxyModel()
          proxy_model.setSourceModel(model)
          proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
  
          filter_input = PlaceholderLineEdit(self)
          filter_input.setPlaceholderText("Filter assets..")
  
          asset_view = QtWidgets.QTreeView(self)
          asset_view.setModel(proxy_model)
          asset_view.setHeaderHidden(True)
          asset_view.setFrameShape(QtWidgets.QFrame.NoFrame)
          asset_view.setEditTriggers(QtWidgets.QTreeView.NoEditTriggers)
          asset_view.setAlternatingRowColors(True)
          asset_view.setSelectionBehavior(QtWidgets.QTreeView.SelectRows)
          asset_view.setAllColumnsShowFocus(True)
  
          ok_btn = QtWidgets.QPushButton("OK", self)
          cancel_btn = QtWidgets.QPushButton("Cancel", self)
  
          btns_layout = QtWidgets.QHBoxLayout()
          btns_layout.addStretch(1)
          btns_layout.addWidget(ok_btn)
          btns_layout.addWidget(cancel_btn)
  
          layout = QtWidgets.QVBoxLayout(self)
          layout.addWidget(filter_input, 0)
          layout.addWidget(asset_view, 1)
          layout.addLayout(btns_layout, 0)
  
          filter_input.textChanged.connect(self._on_filter_change)
          ok_btn.clicked.connect(self._on_ok_clicked)
          cancel_btn.clicked.connect(self._on_cancel_clicked)
  
          self._filter_input = filter_input
          self._ok_btn = ok_btn
          self._cancel_btn = cancel_btn
  
          self._model = model
          self._proxy_model = proxy_model
  
          self._asset_view = asset_view
  
          self._selected_asset = None
          # Soft refresh is enabled
          # - reset will happen at all cost if soft reset is enabled
          # - adds ability to call reset on multiple places without repeating
          self._soft_reset_enabled = True
  
      def showEvent(self, event):
          """Refresh asset model on show."""
          super(AssetsDialog, self).showEvent(event)
          # Refresh on show
          self.reset(False)
  
      def reset(self, force=True):
          """Reset asset model."""
          if not force and not self._soft_reset_enabled:
              return
  
          if self._soft_reset_enabled:
              self._soft_reset_enabled = False
  
          self._model.reset()
  
      def name_is_valid(self, name):
          """Is asset name valid.
  
          Args:
              name(str): Asset name that should be checked.
          """
          # Make sure we're reset
          self.reset(False)
          # Valid the name by model
          return self._model.name_is_valid(name)
  
      def _on_filter_change(self, text):
          """Trigger change of filter of assets."""
          self._proxy_model.setFilterFixedString(text)
  
      def _on_cancel_clicked(self):
          self.done(0)
  
      def _on_ok_clicked(self):
          index = self._asset_view.currentIndex()
          asset_name = None
          if index.isValid():
              asset_name = index.data(QtCore.Qt.DisplayRole)
          self._selected_asset = asset_name
          self.done(1)
  
      def set_selected_assets(self, asset_names):
          """Change preselected asset before showing the dialog.
  
          This also resets model and clean filter.
          """
          self.reset(False)
          self._asset_view.collapseAll()
          self._filter_input.setText("")
  
          indexes = []
          for asset_name in asset_names:
              index = self._model.get_index_by_name(asset_name)
              if index.isValid():
                  indexes.append(index)
  
          if not indexes:
              return
  
          index_deque = collections.deque()
          for index in indexes:
              index_deque.append(index)
  
          all_indexes = []
          while index_deque:
              index = index_deque.popleft()
              all_indexes.append(index)
  
              parent_index = index.parent()
              if parent_index.isValid():
                  index_deque.append(parent_index)
  
          for index in all_indexes:
              proxy_index = self._proxy_model.mapFromSource(index)
              self._asset_view.expand(proxy_index)
  
      def get_selected_asset(self):
          """Get selected asset name."""
          return self._selected_asset
  
  
  class ClickableLineEdit(QtWidgets.QLineEdit):
      """QLineEdit capturing left mouse click.
  
      Triggers `clicked` signal on mouse click.
      """
      clicked = QtCore.Signal()
  
      def __init__(self, *args, **kwargs):
          super(ClickableLineEdit, self).__init__(*args, **kwargs)
          self.setReadOnly(True)
          self._mouse_pressed = False
  
      def mousePressEvent(self, event):
          if event.button() == QtCore.Qt.LeftButton:
              self._mouse_pressed = True
          event.accept()
  
      def mouseMoveEvent(self, event):
          event.accept()
  
      def mouseReleaseEvent(self, event):
          if self._mouse_pressed:
              self._mouse_pressed = False
              if self.rect().contains(event.pos()):
                  self.clicked.emit()
          event.accept()
  
      def mouseDoubleClickEvent(self, event):
          event.accept()
  
  
  class AssetsField(ClickableFrame):
      """Field where asset name of selected instance/s is showed.
  
      Click on the field will trigger `AssetsDialog`.
      """
      value_changed = QtCore.Signal()
  
      def __init__(self, controller, parent):
          super(AssetsField, self).__init__(parent)
  
          dialog = AssetsDialog(controller, self)
  
          name_input = ClickableLineEdit(self)
          name_input.setObjectName("AssetNameInput")
  
          layout = QtWidgets.QHBoxLayout(self)
          layout.setContentsMargins(0, 0, 0, 0)
          layout.addWidget(name_input, 1)
  
          name_input.clicked.connect(self._mouse_release_callback)
          dialog.finished.connect(self._on_dialog_finish)
  
          self._dialog = dialog
          self._name_input = name_input
  
          self._origin_value = []
          self._origin_selection = []
          self._selected_items = []
          self._has_value_changed = False
          self._is_valid = True
          self._multiselection_text = None
  
      def _on_dialog_finish(self, result):
          if not result:
              return
  
          asset_name = self._dialog.get_selected_asset()
          if asset_name is None:
              return
  
          self._selected_items = [asset_name]
          self._has_value_changed = (
              self._origin_value != self._selected_items
          )
          self.set_text(asset_name)
          self._set_is_valid(True)
  
          self.value_changed.emit()
  
      def _mouse_release_callback(self):
          self._dialog.set_selected_assets(self._selected_items)
          self._dialog.open()
  
      def set_multiselection_text(self, text):
          """Change text for multiselection of different assets.
  
          When there are selected multiple instances at once and they don't have
          same asset in context.
          """
          self._multiselection_text = text
  
      def _set_is_valid(self, valid):
          if valid == self._is_valid:
              return
          self._is_valid = valid
          state = ""
          if not valid:
              state = "invalid"
          self._set_state_property(state)
  
      def _set_state_property(self, state):
          current_value = self._name_input.property("state")
          if current_value != state:
              self._name_input.setProperty("state", state)
              self._name_input.style().polish(self._name_input)
  
      def is_valid(self):
          """Is asset valid."""
          return self._is_valid
  
      def has_value_changed(self):
          """Value of asset has changed."""
          return self._has_value_changed
  
      def get_selected_items(self):
          """Selected asset names."""
          return list(self._selected_items)
  
      def set_text(self, text):
          """Set text in text field.
  
          Does not change selected items (assets).
          """
          self._name_input.setText(text)
  
      def set_selected_items(self, asset_names=None):
          """Set asset names for selection of instances.
  
          Passed asset names are validated and if there are 2 or more different
          asset names then multiselection text is shown.
  
          Args:
              asset_names (list, tuple, set, NoneType): List of asset names.
          """
          if asset_names is None:
              asset_names = []
  
          self._has_value_changed = False
          self._origin_value = list(asset_names)
          self._selected_items = list(asset_names)
          is_valid = True
          if not asset_names:
              self.set_text("")
  
          elif len(asset_names) == 1:
              asset_name = tuple(asset_names)[0]
              is_valid = self._dialog.name_is_valid(asset_name)
              self.set_text(asset_name)
          else:
              for asset_name in asset_names:
                  is_valid = self._dialog.name_is_valid(asset_name)
                  if not is_valid:
                      break
  
              multiselection_text = self._multiselection_text
              if multiselection_text is None:
                  multiselection_text = "|".join(asset_names)
              self.set_text(multiselection_text)
  
          self._set_is_valid(is_valid)
  
      def reset_to_origin(self):
          """Change to asset names set with last `set_selected_items` call."""
          self.set_selected_items(self._origin_value)
  
  
  class TasksCombobox(QtWidgets.QComboBox):
      """Combobox to show tasks for selected instances.
  
      Combobox gives ability to select only from intersection of task names for
      asset names in selected instances.
  
      If asset names in selected instances does not have same tasks then combobox
      will be empty.
      """
      value_changed = QtCore.Signal()
  
      def __init__(self, controller, parent):
          super(TasksCombobox, self).__init__(parent)
          self.setObjectName("TasksCombobox")
  
          # Set empty delegate to propagate stylesheet to a combobox
          delegate = QtWidgets.QStyledItemDelegate()
          self.setItemDelegate(delegate)
  
          model = TasksModel(controller)
          self.setModel(model)
  
          self.currentIndexChanged.connect(self._on_index_change)
  
          self._delegate = delegate
          self._model = model
          self._origin_value = []
          self._origin_selection = []
          self._selected_items = []
          self._has_value_changed = False
          self._ignore_index_change = False
          self._multiselection_text = None
          self._is_valid = True
  
          self._text = None
  
      def set_multiselection_text(self, text):
          """Change text shown when multiple different tasks are in context."""
          self._multiselection_text = text
  
      def _on_index_change(self):
          if self._ignore_index_change:
              return
  
          self.set_text(None)
          text = self.currentText()
          idx = self.findText(text)
          if idx < 0:
              return
  
          self._set_is_valid(True)
          self._selected_items = [text]
          self._has_value_changed = (
              self._origin_selection != self._selected_items
          )
  
          self.value_changed.emit()
  
      def set_text(self, text):
          """Set context shown in combobox without chaning selected items."""
          if text == self._text:
              return
  
          self._text = text
  
      def paintEvent(self, event):
          """Paint custom text without using QLineEdit.
  
          The easiest way how to draw custom text in combobox and keep combobox
          properties and event handling.
          """
          painter = QtGui.QPainter(self)
          painter.setPen(self.palette().color(QtGui.QPalette.Text))
          opt = QtWidgets.QStyleOptionComboBox()
          self.initStyleOption(opt)
          if self._text is not None:
              opt.currentText = self._text
          style = self.style()
          style.drawComplexControl(
              QtWidgets.QStyle.CC_ComboBox, opt, painter, self
          )
          style.drawControl(
              QtWidgets.QStyle.CE_ComboBoxLabel, opt, painter, self
          )
  
      def is_valid(self):
          """Are all selected items valid."""
          return self._is_valid
  
      def has_value_changed(self):
          """Did selection of task changed."""
          return self._has_value_changed
  
      def _set_is_valid(self, valid):
          if valid == self._is_valid:
              return
          self._is_valid = valid
          state = ""
          if not valid:
              state = "invalid"
          self._set_state_property(state)
  
      def _set_state_property(self, state):
          current_value = self.property("state")
          if current_value != state:
              self.setProperty("state", state)
              self.style().polish(self)
  
      def get_selected_items(self):
          """Get selected tasks.
  
          If value has changed then will return list with single item.
  
          Returns:
              list: Selected tasks.
          """
          return list(self._selected_items)
  
      def set_asset_names(self, asset_names):
          """Set asset names for which should show tasks."""
          self._ignore_index_change = True
  
          self._model.set_asset_names(asset_names)
  
          self._ignore_index_change = False
  
          # It is a bug if not exactly one asset got here
          if len(asset_names) != 1:
              self.set_selected_item("")
              self._set_is_valid(False)
              return
  
          asset_name = tuple(asset_names)[0]
  
          is_valid = False
          if self._selected_items:
              is_valid = True
  
          for task_name in self._selected_items:
              is_valid = self._model.is_task_name_valid(asset_name, task_name)
              if not is_valid:
                  break
  
          if len(self._selected_items) == 0:
              self.set_selected_item("")
  
          elif len(self._selected_items) == 1:
              self.set_selected_item(self._selected_items[0])
  
          else:
              multiselection_text = self._multiselection_text
              if multiselection_text is None:
                  multiselection_text = "|".join(self._selected_items)
              self.set_selected_item(multiselection_text)
          self._set_is_valid(is_valid)
  
      def set_selected_items(self, asset_task_combinations=None):
          """Set items for selected instances.
  
          Args:
              asset_task_combinations (list): List of tuples. Each item in
                  the list contain asset name and task name.
          """
          if asset_task_combinations is None:
              asset_task_combinations = []
  
          task_names = set()
          task_names_by_asset_name = collections.defaultdict(set)
          for asset_name, task_name in asset_task_combinations:
              task_names.add(task_name)
              task_names_by_asset_name[asset_name].add(task_name)
          asset_names = set(task_names_by_asset_name.keys())
  
          self._ignore_index_change = True
  
          self._model.set_asset_names(asset_names)
  
          self._has_value_changed = False
  
          self._origin_value = copy.deepcopy(asset_task_combinations)
  
          self._origin_selection = list(task_names)
          self._selected_items = list(task_names)
          # Reset current index
          self.setCurrentIndex(-1)
          is_valid = True
          if not task_names:
              self.set_selected_item("")
  
          elif len(task_names) == 1:
              task_name = tuple(task_names)[0]
              idx = self.findText(task_name)
              is_valid = not idx < 0
              if not is_valid and len(asset_names) > 1:
                  is_valid = self._validate_task_names_by_asset_names(
                      task_names_by_asset_name
                  )
              self.set_selected_item(task_name)
  
          else:
              for task_name in task_names:
                  idx = self.findText(task_name)
                  is_valid = not idx < 0
                  if not is_valid:
                      break
  
              if not is_valid and len(asset_names) > 1:
                  is_valid = self._validate_task_names_by_asset_names(
                      task_names_by_asset_name
                  )
              multiselection_text = self._multiselection_text
              if multiselection_text is None:
                  multiselection_text = "|".join(task_names)
              self.set_selected_item(multiselection_text)
  
          self._set_is_valid(is_valid)
  
          self._ignore_index_change = False
  
          self.value_changed.emit()
  
      def _validate_task_names_by_asset_names(self, task_names_by_asset_name):
          for asset_name, task_names in task_names_by_asset_name.items():
              for task_name in task_names:
                  if not self._model.is_task_name_valid(asset_name, task_name):
                      return False
          return True
  
      def set_selected_item(self, item_name):
          """Set task which is set on selected instance.
  
          Args:
              item_name(str): Task name which should be selected.
          """
          idx = self.findText(item_name)
          # Set current index (must be set to -1 if is invalid)
          self.setCurrentIndex(idx)
          if idx < 0:
              self.set_text(item_name)
  
      def reset_to_origin(self):
          """Change to task names set with last `set_selected_items` call."""
          self.set_selected_items(self._origin_value)
  
  
  class VariantInputWidget(PlaceholderLineEdit):
      """Input widget for variant."""
      value_changed = QtCore.Signal()
  
      def __init__(self, parent):
          super(VariantInputWidget, self).__init__(parent)
  
          self.setObjectName("VariantInput")
          self.setToolTip(VARIANT_TOOLTIP)
  
          name_pattern = "^[{}]*$".format(SUBSET_NAME_ALLOWED_SYMBOLS)
          self._name_pattern = name_pattern
          self._compiled_name_pattern = re.compile(name_pattern)
  
          self._origin_value = []
          self._current_value = []
  
          self._ignore_value_change = False
          self._has_value_changed = False
          self._multiselection_text = None
  
          self._is_valid = True
  
          self.textChanged.connect(self._on_text_change)
  
      def is_valid(self):
          """Is variant text valid."""
          return self._is_valid
  
      def has_value_changed(self):
          """Value of variant has changed."""
          return self._has_value_changed
  
      def _set_state_property(self, state):
          current_value = self.property("state")
          if current_value != state:
              self.setProperty("state", state)
              self.style().polish(self)
  
      def set_multiselection_text(self, text):
          """Change text of multiselection."""
          self._multiselection_text = text
  
      def _set_is_valid(self, valid):
          if valid == self._is_valid:
              return
          self._is_valid = valid
          state = ""
          if not valid:
              state = "invalid"
          self._set_state_property(state)
  
      def _on_text_change(self):
          if self._ignore_value_change:
              return
  
          is_valid = bool(self._compiled_name_pattern.match(self.text()))
          self._set_is_valid(is_valid)
  
          self._current_value = [self.text()]
          self._has_value_changed = self._current_value != self._origin_value
  
          self.value_changed.emit()
  
      def reset_to_origin(self):
          """Set origin value of selected instnaces."""
          self.set_value(self._origin_value)
  
      def get_value(self):
          """Get current value.
  
          Origin value returned if didn't change.
          """
          return copy.deepcopy(self._current_value)
  
      def set_value(self, variants=None):
          """Set value of currently selected instances."""
          if variants is None:
              variants = []
  
          self._ignore_value_change = True
  
          self._origin_value = list(variants)
          self._current_value = list(variants)
  
          self.setPlaceholderText("")
          if not variants:
              self.setText("")
  
          elif len(variants) == 1:
              self.setText(self._current_value[0])
  
          else:
              multiselection_text = self._multiselection_text
              if multiselection_text is None:
                  multiselection_text = "|".join(variants)
              self.setText("")
              self.setPlaceholderText(multiselection_text)
  
          self._ignore_value_change = False
  
  
  class MultipleItemWidget(QtWidgets.QWidget):
      """Widget for immutable text which can have more than one value.
  
      Content may be bigger than widget's size and does not have scroll but has
      flick widget on top (is possible to move around with clicked mouse).
      """
  
      def __init__(self, parent):
          super(MultipleItemWidget, self).__init__(parent)
  
          model = QtGui.QStandardItemModel()
  
          view = QtWidgets.QListView(self)
          view.setObjectName("MultipleItemView")
          view.setLayoutMode(QtWidgets.QListView.Batched)
          view.setViewMode(QtWidgets.QListView.IconMode)
          view.setResizeMode(QtWidgets.QListView.Adjust)
          view.setWrapping(False)
          view.setSpacing(2)
          view.setModel(model)
          view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
          view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
  
          flick = FlickCharm(parent=view)
          flick.activateOn(view)
  
          layout = QtWidgets.QHBoxLayout(self)
          layout.setContentsMargins(0, 0, 0, 0)
          layout.addWidget(view)
  
          self._view = view
          self._model = model
  
          self._value = []
  
      def showEvent(self, event):
          super(MultipleItemWidget, self).showEvent(event)
          tmp_item = None
          if not self._value:
              # Add temp item to be able calculate maximum height of widget
              tmp_item = QtGui.QStandardItem("tmp")
              self._model.appendRow(tmp_item)
  
          height = self._view.sizeHintForRow(0)
          self.setMaximumHeight(height + (2 * self._view.spacing()))
  
          if tmp_item is not None:
              self._model.clear()
  
      def set_value(self, value=None):
          """Set value/s of currently selected instance."""
          if value is None:
              value = []
          self._value = value
  
          self._model.clear()
          for item_text in value:
              item = QtGui.QStandardItem(item_text)
              item.setEditable(False)
              item.setSelectable(False)
              self._model.appendRow(item)
  
  
  class GlobalAttrsWidget(QtWidgets.QWidget):
      """Global attributes mainly to define context and subset name of instances.
  
      Subset name is or may be affected on context. Gives abiity to modify
      context and subset name of instance. This change is not autopromoted but
      must be submited.
  
      Warning: Until artist hit `Submit` changes must not be propagated to
      instance data.
  
      Global attributes contain these widgets:
      Variant:      [  text input  ]
      Asset:        [ asset dialog ]
      Task:         [   combobox   ]
      Family:       [   immutable  ]
      Subset name:  [   immutable  ]
                       [Submit] [Cancel]
      """
      instance_context_changed = QtCore.Signal()
  
      multiselection_text = "< Multiselection >"
      unknown_value = "N/A"
  
      def __init__(self, controller, parent):
          super(GlobalAttrsWidget, self).__init__(parent)
  
          self.controller = controller
          self._current_instances = []
  
          variant_input = VariantInputWidget(self)
          asset_value_widget = AssetsField(controller, self)
          task_value_widget = TasksCombobox(controller, self)
          family_value_widget = MultipleItemWidget(self)
          subset_value_widget = MultipleItemWidget(self)
  
          variant_input.set_multiselection_text(self.multiselection_text)
          asset_value_widget.set_multiselection_text(self.multiselection_text)
          task_value_widget.set_multiselection_text(self.multiselection_text)
  
          variant_input.set_value()
          asset_value_widget.set_selected_items()
          task_value_widget.set_selected_items()
          family_value_widget.set_value()
          subset_value_widget.set_value()
  
          submit_btn = QtWidgets.QPushButton("Submit", self)
          cancel_btn = QtWidgets.QPushButton("Cancel", self)
          submit_btn.setEnabled(False)
          cancel_btn.setEnabled(False)
  
          btns_layout = QtWidgets.QHBoxLayout()
          btns_layout.setContentsMargins(0, 0, 0, 0)
          btns_layout.addStretch(1)
          btns_layout.addWidget(submit_btn)
          btns_layout.addWidget(cancel_btn)
  
          main_layout = QtWidgets.QFormLayout(self)
          main_layout.addRow("Name", variant_input)
          main_layout.addRow("Asset", asset_value_widget)
          main_layout.addRow("Task", task_value_widget)
          main_layout.addRow("Family", family_value_widget)
          main_layout.addRow("Subset", subset_value_widget)
          main_layout.addRow(btns_layout)
  
          variant_input.value_changed.connect(self._on_variant_change)
          asset_value_widget.value_changed.connect(self._on_asset_change)
          task_value_widget.value_changed.connect(self._on_task_change)
          submit_btn.clicked.connect(self._on_submit)
          cancel_btn.clicked.connect(self._on_cancel)
  
          self.variant_input = variant_input
          self.asset_value_widget = asset_value_widget
          self.task_value_widget = task_value_widget
          self.family_value_widget = family_value_widget
          self.subset_value_widget = subset_value_widget
          self.submit_btn = submit_btn
          self.cancel_btn = cancel_btn
  
      def _on_submit(self):
          """Commit changes for selected instnaces."""
          variant_value = None
          asset_name = None
          task_name = None
          if self.variant_input.has_value_changed():
              variant_value = self.variant_input.get_value()[0]
  
          if self.asset_value_widget.has_value_changed():
              asset_name = self.asset_value_widget.get_selected_items()[0]
  
          if self.task_value_widget.has_value_changed():
              task_name = self.task_value_widget.get_selected_items()[0]
  
          asset_docs_by_name = {}
          asset_names = set()
          if asset_name is None:
              for instance in self._current_instances:
                  asset_names.add(instance.get("asset"))
          else:
              asset_names.add(asset_name)
  
          for asset_doc in self.controller.get_asset_docs():
              _asset_name = asset_doc["name"]
              if _asset_name in asset_names:
                  asset_names.remove(_asset_name)
                  asset_docs_by_name[_asset_name] = asset_doc
  
              if not asset_names:
                  break
  
          project_name = self.controller.project_name
          subset_names = set()
          for instance in self._current_instances:
              if variant_value is not None:
                  instance["variant"] = variant_value
  
              if asset_name is not None:
                  instance["asset"] = asset_name
                  instance.set_asset_invalid(False)
  
              if task_name is not None:
                  instance["task"] = task_name
                  instance.set_task_invalid(False)
  
              new_variant_value = instance.get("variant")
              new_asset_name = instance.get("asset")
              new_task_name = instance.get("task")
  
              asset_doc = asset_docs_by_name[new_asset_name]
  
              new_subset_name = instance.creator.get_subset_name(
                  new_variant_value, new_task_name, asset_doc, project_name
              )
              subset_names.add(new_subset_name)
              instance["subset"] = new_subset_name
  
          self.subset_value_widget.set_value(subset_names)
  
          self._set_btns_enabled(False)
          self._set_btns_visible(False)
  
          self.instance_context_changed.emit()
  
      def _on_cancel(self):
          """Cancel changes and set back to their irigin value."""
          self.variant_input.reset_to_origin()
          self.asset_value_widget.reset_to_origin()
          self.task_value_widget.reset_to_origin()
          self._set_btns_enabled(False)
  
      def _on_value_change(self):
          any_invalid = (
              not self.variant_input.is_valid()
              or not self.asset_value_widget.is_valid()
              or not self.task_value_widget.is_valid()
          )
          any_changed = (
              self.variant_input.has_value_changed()
              or self.asset_value_widget.has_value_changed()
              or self.task_value_widget.has_value_changed()
          )
          self._set_btns_visible(any_changed or any_invalid)
          self.cancel_btn.setEnabled(any_changed)
          self.submit_btn.setEnabled(not any_invalid)
  
      def _on_variant_change(self):
          self._on_value_change()
  
      def _on_asset_change(self):
          asset_names = self.asset_value_widget.get_selected_items()
          self.task_value_widget.set_asset_names(asset_names)
          self._on_value_change()
  
      def _on_task_change(self):
          self._on_value_change()
  
      def _set_btns_visible(self, visible):
          self.cancel_btn.setVisible(visible)
          self.submit_btn.setVisible(visible)
  
      def _set_btns_enabled(self, enabled):
          self.cancel_btn.setEnabled(enabled)
          self.submit_btn.setEnabled(enabled)
  
      def set_current_instances(self, instances):
          """Set currently selected instances.
  
          Args:
              instances(list<CreatedInstance>): List of selected instances.
                  Empty instances tells that nothing or context is selected.
          """
          self._set_btns_visible(False)
  
          self._current_instances = instances
  
          asset_names = set()
          variants = set()
          families = set()
          subset_names = set()
  
          editable = True
          if len(instances) == 0:
              editable = False
  
          asset_task_combinations = []
          for instance in instances:
              if instance.creator is None:
                  editable = False
  
              variants.add(instance.get("variant") or self.unknown_value)
              families.add(instance.get("family") or self.unknown_value)
              asset_name = instance.get("asset") or self.unknown_value
              task_name = instance.get("task") or self.unknown_value
              asset_names.add(asset_name)
              asset_task_combinations.append((asset_name, task_name))
              subset_names.add(instance.get("subset") or self.unknown_value)
  
          self.variant_input.set_value(variants)
  
          # Set context of asset widget
          self.asset_value_widget.set_selected_items(asset_names)
          # Set context of task widget
          self.task_value_widget.set_selected_items(asset_task_combinations)
          self.family_value_widget.set_value(families)
          self.subset_value_widget.set_value(subset_names)
  
          self.variant_input.setEnabled(editable)
          self.asset_value_widget.setEnabled(editable)
          self.task_value_widget.setEnabled(editable)
  
  
  class CreatorAttrsWidget(QtWidgets.QWidget):
      """Widget showing creator specific attributes for selected instances.
  
      Attributes are defined on creator so are dynamic. Their look and type is
      based on attribute definitions that are defined in
      `~/openpype/pipeline/lib/attribute_definitions.py` and their widget
      representation in `~/openpype/widgets/attribute_defs/*`.
  
      Widgets are disabled if context of instance is not valid.
  
      Definitions are shown for all instance no matter if they are created with
      different creators. If creator have same (similar) definitions their
      widgets are merged into one (different label does not count).
      """
      def __init__(self, controller, parent):
          super(CreatorAttrsWidget, self).__init__(parent)
  
          scroll_area = QtWidgets.QScrollArea(self)
          scroll_area.setWidgetResizable(True)
  
          main_layout = QtWidgets.QHBoxLayout(self)
          main_layout.setContentsMargins(0, 0, 0, 0)
          main_layout.setSpacing(0)
          main_layout.addWidget(scroll_area, 1)
  
          self._main_layout = main_layout
  
          self.controller = controller
          self._scroll_area = scroll_area
  
          self._attr_def_id_to_instances = {}
          self._attr_def_id_to_attr_def = {}
  
          # To store content of scroll area to prevend garbage collection
          self._content_widget = None
  
      def set_instances_valid(self, valid):
          """Change valid state of current instances."""
          if (
              self._content_widget is not None
              and self._content_widget.isEnabled() != valid
          ):
              self._content_widget.setEnabled(valid)
  
      def set_current_instances(self, instances):
          """Set current instances for which are attribute definitons shown."""
          prev_content_widget = self._scroll_area.widget()
          if prev_content_widget:
              self._scroll_area.takeWidget()
              prev_content_widget.hide()
              prev_content_widget.deleteLater()
  
          self._content_widget = None
          self._attr_def_id_to_instances = {}
          self._attr_def_id_to_attr_def = {}
  
          result = self.controller.get_creator_attribute_definitions(
              instances
          )
  
          content_widget = QtWidgets.QWidget(self._scroll_area)
          content_layout = QtWidgets.QFormLayout(content_widget)
          for attr_def, attr_instances, values in result:
              widget = create_widget_for_attr_def(attr_def, content_widget)
              if len(values) == 1:
                  value = values[0]
                  if value is not None:
                      widget.set_value(values[0])
              else:
                  widget.set_value(values, True)
  
              label = attr_def.label or attr_def.key
              content_layout.addRow(label, widget)
              widget.value_changed.connect(self._input_value_changed)
  
              self._attr_def_id_to_instances[attr_def.id] = attr_instances
              self._attr_def_id_to_attr_def[attr_def.id] = attr_def
  
          self._scroll_area.setWidget(content_widget)
          self._content_widget = content_widget
  
      def _input_value_changed(self, value, attr_id):
          instances = self._attr_def_id_to_instances.get(attr_id)
          attr_def = self._attr_def_id_to_attr_def.get(attr_id)
          if not instances or not attr_def:
              return
  
          for instance in instances:
              creator_attributes = instance["creator_attributes"]
              if attr_def.key in creator_attributes:
                  creator_attributes[attr_def.key] = value
  
  
  class PublishPluginAttrsWidget(QtWidgets.QWidget):
      """Widget showing publsish plugin attributes for selected instances.
  
      Attributes are defined on publish plugins. Publihs plugin may define
      attribute definitions but must inherit `OpenPypePyblishPluginMixin`
      (~/openpype/pipeline/publish). At the moment requires to implement
      `get_attribute_defs` and `convert_attribute_values` class methods.
  
      Look and type of attributes is based on attribute definitions that are
      defined in `~/openpype/pipeline/lib/attribute_definitions.py` and their
      widget representation in `~/openpype/widgets/attribute_defs/*`.
  
      Widgets are disabled if context of instance is not valid.
  
      Definitions are shown for all instance no matter if they have different
      families. Similar definitions are merged into one (different label
      does not count).
      """
      def __init__(self, controller, parent):
          super(PublishPluginAttrsWidget, self).__init__(parent)
  
          scroll_area = QtWidgets.QScrollArea(self)
          scroll_area.setWidgetResizable(True)
  
          main_layout = QtWidgets.QHBoxLayout(self)
          main_layout.setContentsMargins(0, 0, 0, 0)
          main_layout.setSpacing(0)
          main_layout.addWidget(scroll_area, 1)
  
          self._main_layout = main_layout
  
          self.controller = controller
          self._scroll_area = scroll_area
  
          self._attr_def_id_to_instances = {}
          self._attr_def_id_to_attr_def = {}
          self._attr_def_id_to_plugin_name = {}
  
          # Store content of scroll area to prevend garbage collection
          self._content_widget = None
  
      def set_instances_valid(self, valid):
          """Change valid state of current instances."""
          if (
              self._content_widget is not None
              and self._content_widget.isEnabled() != valid
          ):
              self._content_widget.setEnabled(valid)
  
      def set_current_instances(self, instances, context_selected):
          """Set current instances for which are attribute definitons shown."""
          prev_content_widget = self._scroll_area.widget()
          if prev_content_widget:
              self._scroll_area.takeWidget()
              prev_content_widget.hide()
              prev_content_widget.deleteLater()
  
          self._content_widget = None
  
          self._attr_def_id_to_instances = {}
          self._attr_def_id_to_attr_def = {}
          self._attr_def_id_to_plugin_name = {}
  
          result = self.controller.get_publish_attribute_definitions(
              instances, context_selected
          )
  
          content_widget = QtWidgets.QWidget(self._scroll_area)
          content_layout = QtWidgets.QFormLayout(content_widget)
          for plugin_name, attr_defs, all_plugin_values in result:
              plugin_values = all_plugin_values[plugin_name]
  
              for attr_def in attr_defs:
                  widget = create_widget_for_attr_def(
                      attr_def, content_widget
                  )
                  label = attr_def.label or attr_def.key
                  content_layout.addRow(label, widget)
  
                  widget.value_changed.connect(self._input_value_changed)
  
                  attr_values = plugin_values[attr_def.key]
                  multivalue = len(attr_values) > 1
                  values = []
                  instances = []
                  for instance, value in attr_values:
                      values.append(value)
                      instances.append(instance)
  
                  self._attr_def_id_to_attr_def[attr_def.id] = attr_def
                  self._attr_def_id_to_instances[attr_def.id] = instances
                  self._attr_def_id_to_plugin_name[attr_def.id] = plugin_name
  
                  if multivalue:
                      widget.set_value(values, multivalue)
                  else:
                      widget.set_value(values[0])
  
          self._scroll_area.setWidget(content_widget)
          self._content_widget = content_widget
  
      def _input_value_changed(self, value, attr_id):
          instances = self._attr_def_id_to_instances.get(attr_id)
          attr_def = self._attr_def_id_to_attr_def.get(attr_id)
          plugin_name = self._attr_def_id_to_plugin_name.get(attr_id)
          if not instances or not attr_def or not plugin_name:
              return
  
          for instance in instances:
              plugin_val = instance.publish_attributes[plugin_name]
              plugin_val[attr_def.key] = value
  
  
  class SubsetAttributesWidget(QtWidgets.QWidget):
      """Wrapper widget where attributes of instance/s are modified.
      ┌─────────────────┬─────────────┐
      │   Global        │             │
      │   attributes    │  Thumbnail  │  TOP
      │                 │             │
      ├─────────────┬───┴─────────────┤
      │  Family     │   Publish       │
      │  attributes │   plugin        │  BOTTOM
      │             │   attributes    │
      └───────────────────────────────┘
      """
      instance_context_changed = QtCore.Signal()
  
      def __init__(self, controller, parent):
          super(SubsetAttributesWidget, self).__init__(parent)
  
          # TOP PART
          top_widget = QtWidgets.QWidget(self)
  
          # Global attributes
          global_attrs_widget = GlobalAttrsWidget(controller, top_widget)
          thumbnail_widget = ThumbnailWidget(top_widget)
  
          top_layout = QtWidgets.QHBoxLayout(top_widget)
          top_layout.setContentsMargins(0, 0, 0, 0)
          top_layout.addWidget(global_attrs_widget, 7)
          top_layout.addWidget(thumbnail_widget, 3)
  
          # BOTTOM PART
          bottom_widget = QtWidgets.QWidget(self)
          creator_attrs_widget = CreatorAttrsWidget(
              controller, bottom_widget
          )
          publish_attrs_widget = PublishPluginAttrsWidget(
              controller, bottom_widget
          )
  
          bottom_separator = QtWidgets.QWidget(bottom_widget)
          bottom_separator.setObjectName("Separator")
          bottom_separator.setMinimumWidth(1)
  
          bottom_layout = QtWidgets.QHBoxLayout(bottom_widget)
          bottom_layout.setContentsMargins(0, 0, 0, 0)
          bottom_layout.addWidget(creator_attrs_widget, 1)
          bottom_layout.addWidget(bottom_separator, 0)
          bottom_layout.addWidget(publish_attrs_widget, 1)
  
          top_bottom = QtWidgets.QWidget(self)
          top_bottom.setObjectName("Separator")
          top_bottom.setMinimumHeight(1)
  
          layout = QtWidgets.QVBoxLayout(self)
          layout.addWidget(top_widget, 0)
          layout.addWidget(top_bottom, 0)
          layout.addWidget(bottom_widget, 1)
  
          self._current_instances = None
          self._context_selected = False
          self._all_instances_valid = True
  
          global_attrs_widget.instance_context_changed.connect(
              self._on_instance_context_changed
          )
  
          self.controller = controller
  
          self.global_attrs_widget = global_attrs_widget
  
          self.creator_attrs_widget = creator_attrs_widget
          self.publish_attrs_widget = publish_attrs_widget
          self.thumbnail_widget = thumbnail_widget
  
          self.top_bottom = top_bottom
          self.bottom_separator = bottom_separator
  
      def _on_instance_context_changed(self):
          all_valid = True
          for instance in self._current_instances:
              if not instance.has_valid_context:
                  all_valid = False
                  break
  
          self._all_instances_valid = all_valid
          self.creator_attrs_widget.set_instances_valid(all_valid)
          self.publish_attrs_widget.set_instances_valid(all_valid)
  
          self.instance_context_changed.emit()
  
      def set_current_instances(self, instances, context_selected):
          """Change currently selected items.
  
          Args:
              instances(list<CreatedInstance>): List of currently selected
                  instances.
              context_selected(bool): Is context selected.
          """
          all_valid = True
          for instance in instances:
              if not instance.has_valid_context:
                  all_valid = False
                  break
  
          self._current_instances = instances
          self._context_selected = context_selected
          self._all_instances_valid = all_valid
  
          self.global_attrs_widget.set_current_instances(instances)
          self.creator_attrs_widget.set_current_instances(instances)
          self.publish_attrs_widget.set_current_instances(
              instances, context_selected
          )
          self.creator_attrs_widget.set_instances_valid(all_valid)
          self.publish_attrs_widget.set_instances_valid(all_valid)
  
  
  class ThumbnailWidget(QtWidgets.QWidget):
      """Instance thumbnail widget.
  
      Logic implementation of this widget is missing but widget is used
      to offset `GlobalAttrsWidget` inputs visually.
      """
      def __init__(self, parent):
          super(ThumbnailWidget, self).__init__(parent)
  
          # Missing implementation for thumbnail
          # - widget kept to make a visial offset of global attr widget offset
          # default_pix = get_pixmap("thumbnail")
          default_pix = QtGui.QPixmap(10, 10)
          default_pix.fill(QtCore.Qt.transparent)
  
          thumbnail_label = QtWidgets.QLabel(self)
          thumbnail_label.setPixmap(
              default_pix.scaled(
                  200, 100,
                  QtCore.Qt.KeepAspectRatio,
                  QtCore.Qt.SmoothTransformation
              )
          )
  
          layout = QtWidgets.QHBoxLayout(self)
          layout.setContentsMargins(0, 0, 0, 0)
          layout.addWidget(thumbnail_label, alignment=QtCore.Qt.AlignCenter)
  
          self.thumbnail_label = thumbnail_label
          self.default_pix = default_pix
          self.current_pix = None
  