ayon-core/openpype/tools/launcher/window.py

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)