mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 13:24:54 +01:00
437 lines
14 KiB
Python
437 lines
14 KiB
Python
import copy
|
|
import logging
|
|
|
|
from qtpy import QtWidgets, QtCore, QtGui
|
|
|
|
from openpype import style
|
|
from openpype import resources
|
|
from openpype.pipeline import AvalonMongoDB
|
|
|
|
import qtawesome
|
|
from .models import (
|
|
LauncherModel,
|
|
ProjectModel
|
|
)
|
|
from .lib import get_action_label
|
|
from .widgets import (
|
|
ProjectBar,
|
|
ActionBar,
|
|
ActionHistory,
|
|
SlidePageWidget,
|
|
LauncherAssetsWidget,
|
|
LauncherTaskWidget
|
|
)
|
|
|
|
from openpype.tools.flickcharm import FlickCharm
|
|
|
|
|
|
class ProjectIconView(QtWidgets.QListView):
|
|
"""Styled ListView that allows to toggle between icon and list mode.
|
|
|
|
Toggling between the two modes is done by Right Mouse Click.
|
|
|
|
"""
|
|
|
|
IconMode = 0
|
|
ListMode = 1
|
|
|
|
def __init__(self, parent=None, mode=ListMode):
|
|
super(ProjectIconView, self).__init__(parent=parent)
|
|
|
|
# Workaround for scrolling being super slow or fast when
|
|
# toggling between the two visual modes
|
|
self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
|
|
self.setObjectName("IconView")
|
|
|
|
self._mode = None
|
|
self.set_mode(mode)
|
|
|
|
def set_mode(self, mode):
|
|
if mode == self._mode:
|
|
return
|
|
|
|
self._mode = mode
|
|
|
|
if mode == self.IconMode:
|
|
self.setViewMode(QtWidgets.QListView.IconMode)
|
|
self.setResizeMode(QtWidgets.QListView.Adjust)
|
|
self.setWrapping(True)
|
|
self.setWordWrap(True)
|
|
self.setGridSize(QtCore.QSize(151, 90))
|
|
self.setIconSize(QtCore.QSize(50, 50))
|
|
self.setSpacing(0)
|
|
self.setAlternatingRowColors(False)
|
|
|
|
self.setProperty("mode", "icon")
|
|
self.style().polish(self)
|
|
|
|
self.verticalScrollBar().setSingleStep(30)
|
|
|
|
elif self.ListMode:
|
|
self.setProperty("mode", "list")
|
|
self.style().polish(self)
|
|
|
|
self.setViewMode(QtWidgets.QListView.ListMode)
|
|
self.setResizeMode(QtWidgets.QListView.Adjust)
|
|
self.setWrapping(False)
|
|
self.setWordWrap(False)
|
|
self.setIconSize(QtCore.QSize(20, 20))
|
|
self.setGridSize(QtCore.QSize(100, 25))
|
|
self.setSpacing(0)
|
|
self.setAlternatingRowColors(False)
|
|
|
|
self.verticalScrollBar().setSingleStep(33.33)
|
|
|
|
def mousePressEvent(self, event):
|
|
if event.button() == QtCore.Qt.RightButton:
|
|
self.set_mode(int(not self._mode))
|
|
return super(ProjectIconView, self).mousePressEvent(event)
|
|
|
|
|
|
class ProjectsPanel(QtWidgets.QWidget):
|
|
"""Projects Page"""
|
|
def __init__(self, launcher_model, parent=None):
|
|
super(ProjectsPanel, self).__init__(parent=parent)
|
|
|
|
view = ProjectIconView(parent=self)
|
|
view.setSelectionMode(QtWidgets.QListView.NoSelection)
|
|
flick = FlickCharm(parent=self)
|
|
flick.activateOn(view)
|
|
model = ProjectModel(launcher_model)
|
|
view.setModel(model)
|
|
|
|
layout = QtWidgets.QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.addWidget(view)
|
|
|
|
view.clicked.connect(self.on_clicked)
|
|
|
|
self._model = model
|
|
self.view = view
|
|
self._launcher_model = launcher_model
|
|
|
|
def on_clicked(self, index):
|
|
if index.isValid():
|
|
project_name = index.data(QtCore.Qt.DisplayRole)
|
|
self._launcher_model.set_project_name(project_name)
|
|
|
|
|
|
class AssetsPanel(QtWidgets.QWidget):
|
|
"""Assets page"""
|
|
back_clicked = QtCore.Signal()
|
|
session_changed = QtCore.Signal()
|
|
|
|
def __init__(self, launcher_model, dbcon, parent=None):
|
|
super(AssetsPanel, self).__init__(parent=parent)
|
|
|
|
self.dbcon = dbcon
|
|
|
|
# Project bar
|
|
btn_back_icon = qtawesome.icon("fa.angle-left", color="white")
|
|
btn_back = QtWidgets.QPushButton(self)
|
|
btn_back.setIcon(btn_back_icon)
|
|
|
|
project_bar = ProjectBar(launcher_model, self)
|
|
|
|
project_bar_layout = QtWidgets.QHBoxLayout()
|
|
project_bar_layout.setContentsMargins(0, 0, 0, 0)
|
|
project_bar_layout.setSpacing(4)
|
|
project_bar_layout.addWidget(btn_back)
|
|
project_bar_layout.addWidget(project_bar)
|
|
|
|
# Assets widget
|
|
assets_widget = LauncherAssetsWidget(
|
|
launcher_model, dbcon=self.dbcon, parent=self
|
|
)
|
|
# Make assets view flickable
|
|
assets_widget.activate_flick_charm()
|
|
|
|
# Tasks widget
|
|
tasks_widget = LauncherTaskWidget(launcher_model, self.dbcon, self)
|
|
|
|
# Body
|
|
body = QtWidgets.QSplitter(self)
|
|
body.setContentsMargins(0, 0, 0, 0)
|
|
body.setSizePolicy(
|
|
QtWidgets.QSizePolicy.Expanding,
|
|
QtWidgets.QSizePolicy.Expanding
|
|
)
|
|
body.setOrientation(QtCore.Qt.Horizontal)
|
|
body.addWidget(assets_widget)
|
|
body.addWidget(tasks_widget)
|
|
body.setStretchFactor(0, 100)
|
|
body.setStretchFactor(1, 65)
|
|
|
|
# main layout
|
|
layout = QtWidgets.QVBoxLayout(self)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.addLayout(project_bar_layout)
|
|
layout.addWidget(body)
|
|
|
|
# signals
|
|
launcher_model.project_changed.connect(self._on_project_changed)
|
|
assets_widget.selection_changed.connect(self._on_asset_changed)
|
|
assets_widget.refreshed.connect(self._on_asset_changed)
|
|
tasks_widget.task_changed.connect(self._on_task_change)
|
|
|
|
btn_back.clicked.connect(self.back_clicked)
|
|
|
|
self.project_bar = project_bar
|
|
self.assets_widget = assets_widget
|
|
self._tasks_widget = tasks_widget
|
|
self._btn_back = btn_back
|
|
|
|
self._launcher_model = launcher_model
|
|
|
|
def select_asset(self, asset_name):
|
|
self.assets_widget.select_asset_by_name(asset_name)
|
|
|
|
def showEvent(self, event):
|
|
super(AssetsPanel, self).showEvent(event)
|
|
|
|
# Change size of a btn
|
|
# WARNING does not handle situation if combobox is bigger
|
|
btn_size = self.project_bar.height()
|
|
self._btn_back.setFixedSize(QtCore.QSize(btn_size, btn_size))
|
|
|
|
def select_task_name(self, task_name):
|
|
self._on_asset_changed()
|
|
self._tasks_widget.select_task_name(task_name)
|
|
|
|
def _on_project_changed(self):
|
|
self.session_changed.emit()
|
|
|
|
def _on_asset_changed(self):
|
|
"""Callback on asset selection changed
|
|
|
|
This updates the task view.
|
|
"""
|
|
|
|
# Check asset on current index and selected assets
|
|
asset_id = self.assets_widget.get_selected_asset_id()
|
|
asset_name = self.assets_widget.get_selected_asset_name()
|
|
|
|
self.dbcon.Session["AVALON_TASK"] = None
|
|
self.dbcon.Session["AVALON_ASSET"] = asset_name
|
|
|
|
self.session_changed.emit()
|
|
|
|
self._tasks_widget.set_asset_id(asset_id)
|
|
|
|
def _on_task_change(self):
|
|
task_name = self._tasks_widget.get_selected_task_name()
|
|
self.dbcon.Session["AVALON_TASK"] = task_name
|
|
self.session_changed.emit()
|
|
|
|
|
|
class LauncherWindow(QtWidgets.QDialog):
|
|
"""Launcher interface"""
|
|
message_timeout = 5000
|
|
|
|
def __init__(self, parent=None):
|
|
super(LauncherWindow, self).__init__(parent)
|
|
|
|
self.log = logging.getLogger(
|
|
".".join([__name__, self.__class__.__name__])
|
|
)
|
|
self.dbcon = AvalonMongoDB()
|
|
|
|
self.setWindowTitle("Launcher")
|
|
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
|
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, False)
|
|
|
|
icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
|
|
self.setWindowIcon(icon)
|
|
self.setStyleSheet(style.load_stylesheet())
|
|
|
|
# Allow minimize
|
|
self.setWindowFlags(
|
|
QtCore.Qt.Window
|
|
| QtCore.Qt.CustomizeWindowHint
|
|
| QtCore.Qt.WindowTitleHint
|
|
| QtCore.Qt.WindowMinimizeButtonHint
|
|
| QtCore.Qt.WindowCloseButtonHint
|
|
)
|
|
|
|
launcher_model = LauncherModel(self.dbcon)
|
|
|
|
project_panel = ProjectsPanel(launcher_model)
|
|
asset_panel = AssetsPanel(launcher_model, self.dbcon)
|
|
|
|
page_slider = SlidePageWidget()
|
|
page_slider.addWidget(project_panel)
|
|
page_slider.addWidget(asset_panel)
|
|
|
|
# actions
|
|
actions_bar = ActionBar(launcher_model, self.dbcon, self)
|
|
|
|
# statusbar
|
|
message_label = QtWidgets.QLabel(self)
|
|
|
|
action_history = ActionHistory(self)
|
|
action_history.setStatusTip("Show Action History")
|
|
|
|
status_layout = QtWidgets.QHBoxLayout()
|
|
status_layout.addWidget(message_label, 1)
|
|
status_layout.addWidget(action_history, 0)
|
|
|
|
# Vertically split Pages and Actions
|
|
body = QtWidgets.QSplitter(self)
|
|
body.setContentsMargins(0, 0, 0, 0)
|
|
body.setSizePolicy(
|
|
QtWidgets.QSizePolicy.Expanding,
|
|
QtWidgets.QSizePolicy.Expanding
|
|
)
|
|
body.setOrientation(QtCore.Qt.Vertical)
|
|
body.addWidget(page_slider)
|
|
body.addWidget(actions_bar)
|
|
|
|
# Set useful default sizes and set stretch
|
|
# for the pages so that is the only one that
|
|
# stretches on UI resize.
|
|
body.setStretchFactor(0, 10)
|
|
body.setSizes([580, 160])
|
|
|
|
layout = QtWidgets.QVBoxLayout(self)
|
|
layout.addWidget(body)
|
|
layout.addLayout(status_layout)
|
|
|
|
message_timer = QtCore.QTimer()
|
|
message_timer.setInterval(self.message_timeout)
|
|
message_timer.setSingleShot(True)
|
|
|
|
message_timer.timeout.connect(self._on_message_timeout)
|
|
|
|
# signals
|
|
actions_bar.action_clicked.connect(self.on_action_clicked)
|
|
action_history.trigger_history.connect(self.on_history_action)
|
|
launcher_model.project_changed.connect(self.on_project_change)
|
|
launcher_model.timer_timeout.connect(self._on_refresh_timeout)
|
|
asset_panel.back_clicked.connect(self.on_back_clicked)
|
|
asset_panel.session_changed.connect(self.on_session_changed)
|
|
|
|
self.resize(520, 740)
|
|
|
|
self._page = 0
|
|
|
|
self._message_timer = message_timer
|
|
|
|
self._launcher_model = launcher_model
|
|
|
|
self._message_label = message_label
|
|
self.project_panel = project_panel
|
|
self.asset_panel = asset_panel
|
|
self.actions_bar = actions_bar
|
|
self.action_history = action_history
|
|
self.page_slider = page_slider
|
|
|
|
def showEvent(self, event):
|
|
self._launcher_model.set_active(True)
|
|
self._launcher_model.start_refresh_timer(True)
|
|
|
|
super(LauncherWindow, self).showEvent(event)
|
|
|
|
def _on_refresh_timeout(self):
|
|
# Stop timer if widget is not visible
|
|
if not self.isVisible():
|
|
self._launcher_model.stop_refresh_timer()
|
|
|
|
def changeEvent(self, event):
|
|
if event.type() == QtCore.QEvent.ActivationChange:
|
|
self._launcher_model.set_active(self.isActiveWindow())
|
|
super(LauncherWindow, self).changeEvent(event)
|
|
|
|
def set_page(self, page):
|
|
current = self.page_slider.currentIndex()
|
|
if current == page and self._page == page:
|
|
return
|
|
|
|
direction = "right" if page > current else "left"
|
|
self._page = page
|
|
self.page_slider.slide_view(page, direction=direction)
|
|
|
|
def _on_message_timeout(self):
|
|
self._message_label.setText("")
|
|
|
|
def echo(self, message):
|
|
self._message_label.setText(str(message))
|
|
self._message_timer.start()
|
|
self.log.debug(message)
|
|
|
|
def on_session_changed(self):
|
|
self.filter_actions()
|
|
|
|
def discover_actions(self):
|
|
self.actions_bar.discover_actions()
|
|
|
|
def filter_actions(self):
|
|
self.actions_bar.filter_actions()
|
|
|
|
def on_project_change(self, project_name):
|
|
# Update the Action plug-ins available for the current project
|
|
self.set_page(1)
|
|
self.discover_actions()
|
|
|
|
def on_back_clicked(self):
|
|
self._launcher_model.set_project_name(None)
|
|
self.set_page(0)
|
|
self.discover_actions()
|
|
|
|
def on_action_clicked(self, action):
|
|
self.echo("Running action: {}".format(get_action_label(action)))
|
|
self.run_action(action)
|
|
|
|
def on_history_action(self, history_data):
|
|
action, session = history_data
|
|
app = QtWidgets.QApplication.instance()
|
|
modifiers = app.keyboardModifiers()
|
|
|
|
is_control_down = QtCore.Qt.ControlModifier & modifiers
|
|
if is_control_down:
|
|
# Revert to that "session" location
|
|
self.set_session(session)
|
|
else:
|
|
# User is holding control, rerun the action
|
|
self.run_action(action, session=session)
|
|
|
|
def run_action(self, action, session=None):
|
|
if session is None:
|
|
session = copy.deepcopy(self.dbcon.Session)
|
|
|
|
filtered_session = {
|
|
key: value
|
|
for key, value in session.items()
|
|
if value
|
|
}
|
|
# Add to history
|
|
self.action_history.add_action(action, filtered_session)
|
|
|
|
# Process the Action
|
|
try:
|
|
action().process(filtered_session)
|
|
except Exception as exc:
|
|
self.log.warning("Action launch failed.", exc_info=True)
|
|
self.echo("Failed: {}".format(str(exc)))
|
|
|
|
def set_session(self, session):
|
|
project_name = session.get("AVALON_PROJECT")
|
|
asset_name = session.get("AVALON_ASSET")
|
|
task_name = session.get("AVALON_TASK")
|
|
|
|
if project_name:
|
|
# Force the "in project" view.
|
|
self.page_slider.slide_view(1, direction="right")
|
|
index = self.asset_panel.project_bar.project_combobox.findText(
|
|
project_name
|
|
)
|
|
if index >= 0:
|
|
self.asset_panel.project_bar.project_combobox.setCurrentIndex(
|
|
index
|
|
)
|
|
|
|
if asset_name:
|
|
self.asset_panel.select_asset(asset_name)
|
|
|
|
if task_name:
|
|
# requires a forced refresh first
|
|
self.asset_panel.select_task_name(task_name)
|