diff --git a/openpype/tools/publisher/widgets/__init__.py b/openpype/tools/publisher/widgets/__init__.py index 9b22a6cf25..55afc349ff 100644 --- a/openpype/tools/publisher/widgets/__init__.py +++ b/openpype/tools/publisher/widgets/__init__.py @@ -9,8 +9,6 @@ from .border_label_widget import ( from .widgets import ( SubsetAttributesWidget, - PixmapLabel, - StopBtn, ResetBtn, ValidateBtn, @@ -44,8 +42,6 @@ __all__ = ( "SubsetAttributesWidget", "BorderedLabelWidget", - "PixmapLabel", - "StopBtn", "ResetBtn", "ValidateBtn", diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py index 271d06e94c..5b59cccd25 100644 --- a/openpype/tools/publisher/widgets/card_view_widgets.py +++ b/openpype/tools/publisher/widgets/card_view_widgets.py @@ -27,12 +27,12 @@ from Qt import QtWidgets, QtCore from openpype.widgets.nice_checkbox import NiceCheckbox +from openpype.tools.utils import BaseClickableFrame from .widgets import ( AbstractInstanceView, ContextWarningLabel, - ClickableFrame, IconValuePixmapLabel, - TransparentPixmapLabel + PublishPixmapLabel ) from ..constants import ( CONTEXT_ID, @@ -140,7 +140,7 @@ class GroupWidget(QtWidgets.QWidget): widget_idx += 1 -class CardWidget(ClickableFrame): +class CardWidget(BaseClickableFrame): """Clickable card used as bigger button.""" selected = QtCore.Signal(str, str) # Group identifier of card @@ -184,7 +184,7 @@ class ContextCardWidget(CardWidget): self._id = CONTEXT_ID self._group_identifier = "" - icon_widget = TransparentPixmapLabel(self) + icon_widget = PublishPixmapLabel(None, self) icon_widget.setObjectName("FamilyIconLabel") label_widget = QtWidgets.QLabel(CONTEXT_LABEL, self) diff --git a/openpype/tools/publisher/widgets/validations_widget.py b/openpype/tools/publisher/widgets/validations_widget.py index 09e56d64cc..4af098413a 100644 --- a/openpype/tools/publisher/widgets/validations_widget.py +++ b/openpype/tools/publisher/widgets/validations_widget.py @@ -6,8 +6,8 @@ except Exception: from Qt import QtWidgets, QtCore, QtGui +from openpype.tools.utils import BaseClickableFrame from .widgets import ( - ClickableFrame, IconValuePixmapLabel ) @@ -55,7 +55,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): self._error_info = error_info self._selected = False - title_frame = ClickableFrame(self) + title_frame = BaseClickableFrame(self) title_frame.setObjectName("ValidationErrorTitleFrame") title_frame._mouse_release_callback = self._mouse_release_callback @@ -168,7 +168,7 @@ class ValidationErrorTitleWidget(QtWidgets.QWidget): self._toggle_instance_btn.setArrowType(QtCore.Qt.RightArrow) -class ActionButton(ClickableFrame): +class ActionButton(BaseClickableFrame): """Plugin's action callback button. Action may have label or icon or both. diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 2ebcf73d4e..073e5f4bc2 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -8,14 +8,20 @@ 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 import resources 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 openpype.tools.utils import ( + PlaceholderLineEdit, + IconButton, + PixmapLabel, + BaseClickableFrame +) +from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS from .icons import ( get_pixmap, get_icon_path @@ -26,49 +32,14 @@ from ..constants import ( ) -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): +class PublishPixmapLabel(PixmapLabel): + def _get_pix_size(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) + return size, size -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): +class IconValuePixmapLabel(PublishPixmapLabel): """Label resizing to width and height of font. Handle icon parsing from creators/instances. Using of QAwesome module @@ -125,7 +96,7 @@ class IconValuePixmapLabel(PixmapLabel): return self._default_pixmap() -class ContextWarningLabel(PixmapLabel): +class ContextWarningLabel(PublishPixmapLabel): """Pixmap label with warning icon.""" def __init__(self, parent): pix = get_pixmap("warning") @@ -138,29 +109,6 @@ class ContextWarningLabel(PixmapLabel): 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. @@ -314,7 +262,7 @@ class ShowPublishReportBtn(PublishIconBtn): class RemoveInstanceBtn(PublishIconBtn): """Create remove button.""" def __init__(self, parent=None): - icon_path = get_icon_path("delete") + icon_path = resources.get_icon_path("delete") super(RemoveInstanceBtn, self).__init__(icon_path, parent) self.setToolTip("Remove selected instances") @@ -359,33 +307,6 @@ class AbstractInstanceView(QtWidgets.QWidget): ).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): @@ -554,7 +475,7 @@ class ClickableLineEdit(QtWidgets.QLineEdit): event.accept() -class AssetsField(ClickableFrame): +class AssetsField(BaseClickableFrame): """Field where asset name of selected instance/s is showed. Click on the field will trigger `AssetsDialog`. diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index bb58813e55..b668888281 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -4,7 +4,10 @@ from openpype import ( resources, style ) -from openpype.tools.utils import PlaceholderLineEdit +from openpype.tools.utils import ( + PlaceholderLineEdit, + PixmapLabel +) from .control import PublisherController from .widgets import ( BorderedLabelWidget, @@ -14,8 +17,6 @@ from .widgets import ( InstanceListView, CreateDialog, - PixmapLabel, - StopBtn, ResetBtn, ValidateBtn, diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py index eb0cb1eef5..ac93595682 100644 --- a/openpype/tools/utils/__init__.py +++ b/openpype/tools/utils/__init__.py @@ -3,6 +3,8 @@ from .widgets import ( BaseClickableFrame, ClickableFrame, ExpandBtn, + PixmapLabel, + IconButton, ) from .error_dialog import ErrorMessageBox @@ -11,15 +13,22 @@ from .lib import ( paint_image_with_color ) +from .models import ( + RecursiveSortFilterProxyModel, +) __all__ = ( "PlaceholderLineEdit", "BaseClickableFrame", "ClickableFrame", "ExpandBtn", + "PixmapLabel", + "IconButton", "ErrorMessageBox", "WrappedCallbackItem", "paint_image_with_color", + + "RecursiveSortFilterProxyModel", ) diff --git a/openpype/tools/utils/assets_widget.py b/openpype/tools/utils/assets_widget.py index 1495586b04..55e34285fc 100644 --- a/openpype/tools/utils/assets_widget.py +++ b/openpype/tools/utils/assets_widget.py @@ -635,9 +635,10 @@ class AssetsWidget(QtWidgets.QWidget): selection_model = view.selectionModel() selection_model.selectionChanged.connect(self._on_selection_change) refresh_btn.clicked.connect(self.refresh) - current_asset_btn.clicked.connect(self.set_current_session_asset) + current_asset_btn.clicked.connect(self._on_current_asset_click) view.doubleClicked.connect(self.double_clicked) + self._refresh_btn = refresh_btn self._current_asset_btn = current_asset_btn self._model = model self._proxy = proxy @@ -668,11 +669,30 @@ class AssetsWidget(QtWidgets.QWidget): def stop_refresh(self): self._model.stop_refresh() + def _get_current_session_asset(self): + return self.dbcon.Session.get("AVALON_ASSET") + + def _on_current_asset_click(self): + """Trigger change of asset to current context asset. + This separation gives ability to override this method and use it + in differnt way. + """ + self.set_current_session_asset() + def set_current_session_asset(self): - asset_name = self.dbcon.Session.get("AVALON_ASSET") + asset_name = self._get_current_session_asset() if asset_name: self.select_asset_by_name(asset_name) + def set_refresh_btn_visibility(self, visible=None): + """Hide set refresh button. + Some tools may have their global refresh button or do not support + refresh at all. + """ + if visible is None: + visible = not self._refresh_btn.isVisible() + self._refresh_btn.setVisible(visible) + def set_current_asset_btn_visibility(self, visible=None): """Hide set current asset button. @@ -727,6 +747,10 @@ class AssetsWidget(QtWidgets.QWidget): def _set_loading_state(self, loading, empty): self._view.set_loading_state(loading, empty) + def _clear_selection(self): + selection_model = self._view.selectionModel() + selection_model.clearSelection() + def _select_indexes(self, indexes): valid_indexes = [ index diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index df3eee41a2..2b5b156eeb 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -199,31 +199,37 @@ class Item(dict): class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel): - """Filters to the regex if any of the children matches allow parent""" - def filterAcceptsRow(self, row, parent): + """Recursive proxy model. + Item is not filtered if any children match the filter. + Use case: Filtering by string - parent won't be filtered if does not match + the filter string but first checks if any children does. + """ + def filterAcceptsRow(self, row, parent_index): regex = self.filterRegExp() if not regex.isEmpty(): - pattern = regex.pattern() model = self.sourceModel() - source_index = model.index(row, self.filterKeyColumn(), parent) + source_index = model.index( + row, self.filterKeyColumn(), parent_index + ) if source_index.isValid(): + pattern = regex.pattern() + # Check current index itself - key = model.data(source_index, self.filterRole()) - if re.search(pattern, key, re.IGNORECASE): + value = model.data(source_index, self.filterRole()) + if re.search(pattern, value, re.IGNORECASE): return True - # Check children rows = model.rowCount(source_index) - for i in range(rows): - if self.filterAcceptsRow(i, source_index): + for idx in range(rows): + if self.filterAcceptsRow(idx, source_index): return True # Otherwise filter it return False - return super( - RecursiveSortFilterProxyModel, self - ).filterAcceptsRow(row, parent) + return super(RecursiveSortFilterProxyModel, self).filterAcceptsRow( + row, parent_index + ) class ProjectModel(QtGui.QStandardItemModel): diff --git a/openpype/tools/utils/tasks_widget.py b/openpype/tools/utils/tasks_widget.py index 6e6cd17ffd..6c7787d06a 100644 --- a/openpype/tools/utils/tasks_widget.py +++ b/openpype/tools/utils/tasks_widget.py @@ -255,6 +255,10 @@ class TasksWidget(QtWidgets.QWidget): # Force a task changed emit. self.task_changed.emit() + def _clear_selection(self): + selection_model = self._tasks_view.selectionModel() + selection_model.clearSelection() + def select_task_name(self, task_name): """Select a task by name. @@ -285,6 +289,10 @@ class TasksWidget(QtWidgets.QWidget): self._tasks_view.setCurrentIndex(index) break + last_selected_task_name = self.get_selected_task_name() + if last_selected_task_name: + self._last_selected_task_name = last_selected_task_name + def get_selected_task_name(self): """Return name of task at current index (selected) diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py index c32eae043e..ea09968e40 100644 --- a/openpype/tools/utils/widgets.py +++ b/openpype/tools/utils/widgets.py @@ -148,6 +148,65 @@ class ImageButton(QtWidgets.QPushButton): return self.iconSize() +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 PixmapLabel(QtWidgets.QLabel): + """Label resizing image to height of font.""" + def __init__(self, pixmap, parent): + super(PixmapLabel, self).__init__(parent) + self._empty_pixmap = QtGui.QPixmap(0, 0) + self._source_pixmap = pixmap + + def set_source_pixmap(self, pixmap): + """Change source image.""" + self._source_pixmap = pixmap + self._set_resized_pix() + + def _get_pix_size(self): + size = self.fontMetrics().height() + size += size % 2 + return size, size + + def _set_resized_pix(self): + if self._source_pixmap is None: + self.setPixmap(self._empty_pixmap) + return + width, height = self._get_pix_size() + self.setPixmap( + self._source_pixmap.scaled( + width, + height, + QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation + ) + ) + + def resizeEvent(self, event): + self._set_resized_pix() + super(PixmapLabel, self).resizeEvent(event) + + class OptionalMenu(QtWidgets.QMenu): """A subclass of `QtWidgets.QMenu` to work with `OptionalAction`