Merge pull request #2672 from pypeclub/feature/OP-2416_Launcher-tool-model

Launcher: Added Launcher model with more abilities to filter
This commit is contained in:
Jakub Trllo 2022-02-22 17:19:20 +01:00 committed by GitHub
commit cfcdc80f74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 630 additions and 140 deletions

View file

@ -15,7 +15,7 @@ provides a bridge between the file-based project inventory and configuration.
"""
import os
from Qt import QtGui, QtCore
from Qt import QtGui
from avalon.vendor import qtawesome
from openpype.api import resources
@ -23,77 +23,6 @@ ICON_CACHE = {}
NOT_FOUND = type("NotFound", (object, ), {})
class ProjectHandler(QtCore.QObject):
"""Handler of project model and current project in Launcher tool.
Helps to organize two separate widgets handling current project selection.
It is easier to trigger project change callbacks from one place than from
multiple different places without proper handling or sequence changes.
Args:
dbcon(AvalonMongoDB): Mongo connection with Session.
model(ProjectModel): Object of projects model which is shared across
all widgets using projects. Arg dbcon should be used as source for
the model.
"""
# Project list will be refreshed each 10000 msecs
# - this is not part of helper implementation but should be used by widgets
# that may require reshing of projects
refresh_interval = 10000
# Signal emitted when project has changed
project_changed = QtCore.Signal(str)
projects_refreshed = QtCore.Signal()
timer_timeout = QtCore.Signal()
def __init__(self, dbcon, model):
super(ProjectHandler, self).__init__()
self._active = False
# Store project model for usage
self.model = model
# Store dbcon
self.dbcon = dbcon
self.current_project = dbcon.Session.get("AVALON_PROJECT")
refresh_timer = QtCore.QTimer()
refresh_timer.setInterval(self.refresh_interval)
refresh_timer.timeout.connect(self._on_timeout)
self.refresh_timer = refresh_timer
def _on_timeout(self):
if self._active:
self.timer_timeout.emit()
self.refresh_model()
def set_active(self, active):
self._active = active
def start_timer(self, trigger=False):
self.refresh_timer.start()
if trigger:
self._on_timeout()
def stop_timer(self):
self.refresh_timer.stop()
def set_project(self, project_name):
# Change current project of this handler
self.current_project = project_name
# Change session project to take effect for other widgets using the
# dbcon object.
self.dbcon.Session["AVALON_PROJECT"] = project_name
# Trigger change signal when everything is updated to new project
self.project_changed.emit(project_name)
def refresh_model(self):
self.model.refresh()
self.projects_refreshed.emit()
def get_action_icon(action):
icon_name = action.icon
if not icon_name:

View file

@ -1,8 +1,30 @@
import re
import uuid
import copy
import logging
import collections
import time
import appdirs
from Qt import QtCore, QtGui
from avalon.vendor import qtawesome
from avalon import api
from openpype.lib import JSONSettingRegistry
from openpype.lib.applications import (
CUSTOM_LAUNCH_APP_GROUPS,
ApplicationManager
)
from openpype.tools.utils.lib import DynamicQThread
from openpype.tools.utils.assets_widget import (
AssetModel,
ASSET_NAME_ROLE
)
from openpype.tools.utils.tasks_widget import (
TasksModel,
TasksProxyModel,
TASK_TYPE_ROLE,
TASK_ASSIGNEE_ROLE
)
from . import lib
from .constants import (
@ -13,17 +35,13 @@ from .constants import (
FORCE_NOT_OPEN_WORKFILE_ROLE
)
from .actions import ApplicationAction
from Qt import QtCore, QtGui
from avalon.vendor import qtawesome
from avalon import api
from openpype.lib import JSONSettingRegistry
from openpype.lib.applications import (
CUSTOM_LAUNCH_APP_GROUPS,
ApplicationManager
)
log = logging.getLogger(__name__)
# Must be different than roles in default asset model
ASSET_TASK_TYPES_ROLE = QtCore.Qt.UserRole + 10
ASSET_ASSIGNEE_ROLE = QtCore.Qt.UserRole + 11
class ActionModel(QtGui.QStandardItemModel):
def __init__(self, dbcon, parent=None):
@ -330,21 +348,483 @@ class ActionModel(QtGui.QStandardItemModel):
return compare_data
class LauncherModel(QtCore.QObject):
# Refresh interval of projects
refresh_interval = 10000
# Signals
# Current project has changed
project_changed = QtCore.Signal(str)
# Filters has changed (any)
filters_changed = QtCore.Signal()
# Projects were refreshed
projects_refreshed = QtCore.Signal()
# Signals ONLY for assets model!
# - other objects should listen to asset model signals
# Asset refresh started
assets_refresh_started = QtCore.Signal()
# Assets refresh finished
assets_refreshed = QtCore.Signal()
# Refresh timer timeout
# - give ability to tell parent window that this timer still runs
timer_timeout = QtCore.Signal()
# Duplication from AssetsModel with "data.tasks"
_asset_projection = {
"name": 1,
"parent": 1,
"data.visualParent": 1,
"data.label": 1,
"data.icon": 1,
"data.color": 1,
"data.tasks": 1
}
def __init__(self, dbcon):
super(LauncherModel, self).__init__()
# Refresh timer
# - should affect only projects
refresh_timer = QtCore.QTimer()
refresh_timer.setInterval(self.refresh_interval)
refresh_timer.timeout.connect(self._on_timeout)
self._refresh_timer = refresh_timer
# Launcher is active
self._active = False
# Global data
self._dbcon = dbcon
# Available project names
self._project_names = set()
# Context data
self._asset_docs = []
self._asset_docs_by_id = {}
self._asset_filter_data_by_id = {}
self._assignees = set()
self._task_types = set()
# Filters
self._asset_name_filter = ""
self._assignee_filters = set()
self._task_type_filters = set()
# Last project for which were assets queried
self._last_project_name = None
# Asset refresh thread is running
self._refreshing_assets = False
# Asset refresh thread
self._asset_refresh_thread = None
def _on_timeout(self):
"""Refresh timer timeout."""
if self._active:
self.timer_timeout.emit()
self.refresh_projects()
def set_active(self, active):
"""Window change active state."""
self._active = active
def start_refresh_timer(self, trigger=False):
"""Start refresh timer."""
self._refresh_timer.start()
if trigger:
self._on_timeout()
def stop_refresh_timer(self):
"""Stop refresh timer."""
self._refresh_timer.stop()
@property
def project_name(self):
"""Current project name."""
return self._dbcon.Session.get("AVALON_PROJECT")
@property
def refreshing_assets(self):
"""Refreshing thread is running."""
return self._refreshing_assets
@property
def asset_docs(self):
"""Access to asset docs."""
return self._asset_docs
@property
def project_names(self):
"""Available project names."""
return self._project_names
@property
def asset_filter_data_by_id(self):
"""Prepared filter data by asset id."""
return self._asset_filter_data_by_id
@property
def assignees(self):
"""All assignees for all assets in current project."""
return self._assignees
@property
def task_types(self):
"""All task types for all assets in current project.
TODO: This could be maybe taken from project document where are all
task types...
"""
return self._task_types
@property
def task_type_filters(self):
"""Currently set task type filters."""
return self._task_type_filters
@property
def assignee_filters(self):
"""Currently set assignee filters."""
return self._assignee_filters
@property
def asset_name_filter(self):
"""Asset name filter (can be used as regex filter)."""
return self._asset_name_filter
def get_asset_doc(self, asset_id):
"""Get single asset document by id."""
return self._asset_docs_by_id.get(asset_id)
def set_project_name(self, project_name):
"""Change project name and refresh asset documents."""
if project_name == self.project_name:
return
self._dbcon.Session["AVALON_PROJECT"] = project_name
self.project_changed.emit(project_name)
self.refresh_assets(force=True)
def refresh(self):
"""Trigger refresh of whole model."""
self.refresh_projects()
self.refresh_assets(force=False)
def refresh_projects(self):
"""Refresh projects."""
current_project = self.project_name
project_names = set()
for project_doc in self._dbcon.projects(only_active=True):
project_names.add(project_doc["name"])
self._project_names = project_names
self.projects_refreshed.emit()
if (
current_project is not None
and current_project not in project_names
):
self.set_project_name(None)
def _set_asset_docs(self, asset_docs=None):
"""Set asset documents and all related data.
Method extract and prepare data needed for assets and tasks widget and
prepare filtering data.
"""
if asset_docs is None:
asset_docs = []
all_task_types = set()
all_assignees = set()
asset_docs_by_id = {}
asset_filter_data_by_id = {}
for asset_doc in asset_docs:
task_types = set()
assignees = set()
asset_id = asset_doc["_id"]
asset_docs_by_id[asset_id] = asset_doc
asset_tasks = asset_doc.get("data", {}).get("tasks")
asset_filter_data_by_id[asset_id] = {
"assignees": assignees,
"task_types": task_types
}
if not asset_tasks:
continue
for task_data in asset_tasks.values():
task_assignees = set()
_task_assignees = task_data.get("assignees")
if _task_assignees:
for assignee in _task_assignees:
task_assignees.add(assignee["username"])
task_type = task_data.get("type")
if task_assignees:
assignees |= set(task_assignees)
if task_type:
task_types.add(task_type)
all_task_types |= task_types
all_assignees |= assignees
self._asset_docs_by_id = asset_docs_by_id
self._asset_docs = asset_docs
self._asset_filter_data_by_id = asset_filter_data_by_id
self._assignees = all_assignees
self._task_types = all_task_types
self.assets_refreshed.emit()
def set_task_type_filter(self, task_types):
"""Change task type filter.
Args:
task_types (set): Set of task types that should be visible.
Pass empty set to turn filter off.
"""
self._task_type_filters = task_types
self.filters_changed.emit()
def set_assignee_filter(self, assignees):
"""Change assignees filter.
Args:
assignees (set): Set of assignees that should be visible.
Pass empty set to turn filter off.
"""
self._assignee_filters = assignees
self.filters_changed.emit()
def set_asset_name_filter(self, text_filter):
"""Change asset name filter.
Args:
text_filter (str): Asset name filter. Pass empty string to
turn filter off.
"""
self._asset_name_filter = text_filter
self.filters_changed.emit()
def refresh_assets(self, force=True):
"""Refresh assets."""
self.assets_refresh_started.emit()
if self.project_name is None:
self._set_asset_docs()
return
if (
not force
and self._last_project_name == self.project_name
):
return
self._stop_fetch_thread()
self._refreshing_assets = True
self._last_project_name = self.project_name
self._asset_refresh_thread = DynamicQThread(self._refresh_assets)
self._asset_refresh_thread.start()
def _stop_fetch_thread(self):
self._refreshing_assets = False
if self._asset_refresh_thread is not None:
while self._asset_refresh_thread.isRunning():
# TODO this is blocking UI should be done in a different way
time.sleep(0.01)
self._asset_refresh_thread = None
def _refresh_assets(self):
asset_docs = list(self._dbcon.find(
{"type": "asset"},
self._asset_projection
))
if not self._refreshing_assets:
return
self._refreshing_assets = False
self._set_asset_docs(asset_docs)
class LauncherTasksProxyModel(TasksProxyModel):
"""Tasks proxy model with more filtering.
TODO:
This can be (with few modifications) used in default tasks widget too.
"""
def __init__(self, launcher_model, *args, **kwargs):
self._launcher_model = launcher_model
super(LauncherTasksProxyModel, self).__init__(*args, **kwargs)
launcher_model.filters_changed.connect(self._on_filter_change)
self._task_types_filter = set()
self._assignee_filter = set()
def _on_filter_change(self):
self._task_types_filter = self._launcher_model.task_type_filters
self._assignee_filter = self._launcher_model.assignee_filters
self.invalidateFilter()
def filterAcceptsRow(self, row, parent):
if not self._task_types_filter and not self._assignee_filter:
return True
model = self.sourceModel()
source_index = model.index(row, self.filterKeyColumn(), parent)
if not source_index.isValid():
return False
# Check current index itself
if self._task_types_filter:
task_type = model.data(source_index, TASK_TYPE_ROLE)
if task_type not in self._task_types_filter:
return False
if self._assignee_filter:
assignee = model.data(source_index, TASK_ASSIGNEE_ROLE)
if not self._assignee_filter.intersection(assignee):
return False
return True
class LauncherTaskModel(TasksModel):
def __init__(self, launcher_model, *args, **kwargs):
self._launcher_model = launcher_model
super(LauncherTaskModel, self).__init__(*args, **kwargs)
def set_asset_id(self, asset_id):
asset_doc = None
if self._context_is_valid():
asset_doc = self._launcher_model.get_asset_doc(asset_id)
self._set_asset(asset_doc)
class AssetRecursiveSortFilterModel(QtCore.QSortFilterProxyModel):
def __init__(self, launcher_model, *args, **kwargs):
self._launcher_model = launcher_model
super(AssetRecursiveSortFilterModel, self).__init__(*args, **kwargs)
launcher_model.filters_changed.connect(self._on_filter_change)
self._name_filter = ""
self._task_types_filter = set()
self._assignee_filter = set()
def _on_filter_change(self):
self._name_filter = self._launcher_model.asset_name_filter
self._task_types_filter = self._launcher_model.task_type_filters
self._assignee_filter = self._launcher_model.assignee_filters
self.invalidateFilter()
"""Filters to the regex if any of the children matches allow parent"""
def filterAcceptsRow(self, row, parent):
if (
not self._name_filter
and not self._task_types_filter
and not self._assignee_filter
):
return True
model = self.sourceModel()
source_index = model.index(row, self.filterKeyColumn(), parent)
if not source_index.isValid():
return False
# Check current index itself
valid = True
if self._name_filter:
name = model.data(source_index, ASSET_NAME_ROLE)
if (
name is None
or not re.search(self._name_filter, name, re.IGNORECASE)
):
valid = False
if valid and self._task_types_filter:
task_types = model.data(source_index, ASSET_TASK_TYPES_ROLE)
if not self._task_types_filter.intersection(task_types):
valid = False
if valid and self._assignee_filter:
assignee = model.data(source_index, ASSET_ASSIGNEE_ROLE)
if not self._assignee_filter.intersection(assignee):
valid = False
if valid:
return True
# Check children
rows = model.rowCount(source_index)
for child_row in range(rows):
if self.filterAcceptsRow(child_row, source_index):
return True
return False
class LauncherAssetsModel(AssetModel):
def __init__(self, launcher_model, dbcon, parent=None):
self._launcher_model = launcher_model
# Make sure that variable is available (even if is in AssetModel)
self._last_project_name = None
super(LauncherAssetsModel, self).__init__(dbcon, parent)
launcher_model.project_changed.connect(self._on_project_change)
launcher_model.assets_refresh_started.connect(
self._on_launcher_refresh_start
)
launcher_model.assets_refreshed.connect(self._on_launcher_refresh)
def _on_launcher_refresh_start(self):
self._refreshing = True
project_name = self._launcher_model.project_name
if self._last_project_name != project_name:
self._clear_items()
self._last_project_name = project_name
def _on_launcher_refresh(self):
self._fill_assets(self._launcher_model.asset_docs)
self._refreshing = False
self.refreshed.emit(bool(self._items_by_asset_id))
def _fill_assets(self, *args, **kwargs):
super(LauncherAssetsModel, self)._fill_assets(*args, **kwargs)
asset_filter_data_by_id = self._launcher_model.asset_filter_data_by_id
for asset_id, item in self._items_by_asset_id.items():
filter_data = asset_filter_data_by_id.get(asset_id)
assignees = filter_data["assignees"]
task_types = filter_data["task_types"]
item.setData(assignees, ASSET_ASSIGNEE_ROLE)
item.setData(task_types, ASSET_TASK_TYPES_ROLE)
def _on_project_change(self):
self._clear_items()
def refresh(self, *args, **kwargs):
raise ValueError("This is a bug!")
def stop_refresh(self, *args, **kwargs):
raise ValueError("This is a bug!")
class ProjectModel(QtGui.QStandardItemModel):
"""List of projects"""
def __init__(self, dbcon, parent=None):
def __init__(self, launcher_model, parent=None):
super(ProjectModel, self).__init__(parent=parent)
self.dbcon = dbcon
self._launcher_model = launcher_model
self.project_icon = qtawesome.icon("fa.map", color="white")
self._project_names = set()
def refresh(self):
project_names = set()
for project_doc in self.get_projects():
project_names.add(project_doc["name"])
launcher_model.projects_refreshed.connect(self._on_refresh)
def _on_refresh(self):
project_names = set(self._launcher_model.project_names)
origin_project_names = set(self._project_names)
self._project_names = project_names
@ -387,7 +867,3 @@ class ProjectModel(QtGui.QStandardItemModel):
items.append(item)
self.invisibleRootItem().insertRows(row, items)
def get_projects(self):
return sorted(self.dbcon.projects(only_active=True),
key=lambda x: x["name"])

View file

@ -4,11 +4,21 @@ import collections
from Qt import QtWidgets, QtCore, QtGui
from avalon.vendor import qtawesome
from openpype.tools.flickcharm import FlickCharm
from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
from openpype.tools.utils.tasks_widget import TasksWidget
from .delegates import ActionDelegate
from . import lib
from .models import (
ActionModel,
ProjectModel,
LauncherAssetsModel,
AssetRecursiveSortFilterModel,
LauncherTaskModel,
LauncherTasksProxyModel
)
from .actions import ApplicationAction
from .models import ActionModel
from openpype.tools.flickcharm import FlickCharm
from .constants import (
ACTION_ROLE,
GROUP_ROLE,
@ -22,15 +32,15 @@ from .constants import (
class ProjectBar(QtWidgets.QWidget):
def __init__(self, project_handler, parent=None):
def __init__(self, launcher_model, parent=None):
super(ProjectBar, self).__init__(parent)
project_combobox = QtWidgets.QComboBox(self)
# Change delegate so stylysheets are applied
project_delegate = QtWidgets.QStyledItemDelegate(project_combobox)
project_combobox.setItemDelegate(project_delegate)
project_combobox.setModel(project_handler.model)
model = ProjectModel(launcher_model)
project_combobox.setModel(model)
project_combobox.setRootModelIndex(QtCore.QModelIndex())
layout = QtWidgets.QHBoxLayout(self)
@ -42,16 +52,17 @@ class ProjectBar(QtWidgets.QWidget):
QtWidgets.QSizePolicy.Maximum
)
self.project_handler = project_handler
self._launcher_model = launcher_model
self.project_delegate = project_delegate
self.project_combobox = project_combobox
self._model = model
# Signals
self.project_combobox.currentIndexChanged.connect(self.on_index_change)
project_handler.project_changed.connect(self._on_project_change)
launcher_model.project_changed.connect(self._on_project_change)
# Set current project by default if it's set.
project_name = project_handler.current_project
project_name = launcher_model.project_name
if project_name:
self.set_project(project_name)
@ -67,7 +78,7 @@ class ProjectBar(QtWidgets.QWidget):
index = self.project_combobox.findText(project_name)
if index < 0:
# Try refresh combobox model
self.project_handler.refresh_model()
self._launcher_model.refresh_projects()
index = self.project_combobox.findText(project_name)
if index >= 0:
@ -78,7 +89,70 @@ class ProjectBar(QtWidgets.QWidget):
return
project_name = self.get_current_project()
self.project_handler.set_project(project_name)
self._launcher_model.set_project_name(project_name)
class LauncherTaskWidget(TasksWidget):
def __init__(self, launcher_model, *args, **kwargs):
self._launcher_model = launcher_model
super(LauncherTaskWidget, self).__init__(*args, **kwargs)
def _create_source_model(self):
return LauncherTaskModel(self._launcher_model, self._dbcon)
def _create_proxy_model(self, source_model):
proxy = LauncherTasksProxyModel(self._launcher_model)
proxy.setSourceModel(source_model)
return proxy
class LauncherAssetsWidget(SingleSelectAssetsWidget):
def __init__(self, launcher_model, *args, **kwargs):
self._launcher_model = launcher_model
super(LauncherAssetsWidget, self).__init__(*args, **kwargs)
launcher_model.assets_refresh_started.connect(self._on_refresh_start)
self.set_current_asset_btn_visibility(False)
def _on_refresh_start(self):
self._set_loading_state(loading=True, empty=True)
self.refresh_triggered.emit()
@property
def refreshing(self):
return self._model.refreshing
def refresh(self):
self._launcher_model.refresh_assets(force=True)
def stop_refresh(self):
raise ValueError("bug stop_refresh called")
def _refresh_model(self, clear=False):
raise ValueError("bug _refresh_model called")
def _create_source_model(self):
model = LauncherAssetsModel(self._launcher_model, self.dbcon)
model.refreshed.connect(self._on_model_refresh)
return model
def _create_proxy_model(self, source_model):
proxy = AssetRecursiveSortFilterModel(self._launcher_model)
proxy.setSourceModel(source_model)
proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
proxy.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
return proxy
def _on_model_refresh(self, has_item):
self._proxy.sort(0)
self._set_loading_state(loading=False, empty=not has_item)
self.refreshed.emit()
def _on_filter_text_change(self, new_text):
self._launcher_model.set_asset_name_filter(new_text)
class ActionBar(QtWidgets.QWidget):
@ -86,10 +160,10 @@ class ActionBar(QtWidgets.QWidget):
action_clicked = QtCore.Signal(object)
def __init__(self, project_handler, dbcon, parent=None):
def __init__(self, launcher_model, dbcon, parent=None):
super(ActionBar, self).__init__(parent)
self.project_handler = project_handler
self._launcher_model = launcher_model
self.dbcon = dbcon
view = QtWidgets.QListView(self)
@ -136,7 +210,7 @@ class ActionBar(QtWidgets.QWidget):
self.set_row_height(1)
project_handler.projects_refreshed.connect(self._on_projects_refresh)
launcher_model.projects_refreshed.connect(self._on_projects_refresh)
view.clicked.connect(self.on_clicked)
view.customContextMenuRequested.connect(self.on_context_menu)
@ -182,8 +256,8 @@ class ActionBar(QtWidgets.QWidget):
self.update()
def _start_animation(self, index):
# Offset refresh timeout
self.project_handler.start_timer()
# Offset refresh timout
self._launcher_model.start_refresh_timer()
action_id = index.data(ACTION_ID_ROLE)
item = self.model.items_by_id.get(action_id)
if item:
@ -250,8 +324,8 @@ class ActionBar(QtWidgets.QWidget):
self.action_clicked.emit(action)
return
# Offset refresh timeout
self.project_handler.start_timer()
# Offset refresh timout
self._launcher_model.start_refresh_timer()
actions = index.data(ACTION_ROLE)

View file

@ -8,17 +8,19 @@ from avalon.api import AvalonMongoDB
from openpype import style
from openpype.api import resources
from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
from openpype.tools.utils.tasks_widget import TasksWidget
from avalon.vendor import qtawesome
from .models import ProjectModel
from .lib import get_action_label, ProjectHandler
from .models import (
LauncherModel,
ProjectModel
)
from .lib import get_action_label
from .widgets import (
ProjectBar,
ActionBar,
ActionHistory,
SlidePageWidget
SlidePageWidget,
LauncherAssetsWidget,
LauncherTaskWidget
)
from openpype.tools.flickcharm import FlickCharm
@ -89,15 +91,15 @@ class ProjectIconView(QtWidgets.QListView):
class ProjectsPanel(QtWidgets.QWidget):
"""Projects Page"""
def __init__(self, project_handler, parent=None):
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)
view.setModel(project_handler.model)
model = ProjectModel(launcher_model)
view.setModel(model)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
@ -105,13 +107,14 @@ class ProjectsPanel(QtWidgets.QWidget):
view.clicked.connect(self.on_clicked)
self._model = model
self.view = view
self.project_handler = project_handler
self._launcher_model = launcher_model
def on_clicked(self, index):
if index.isValid():
project_name = index.data(QtCore.Qt.DisplayRole)
self.project_handler.set_project(project_name)
self._launcher_model.set_project_name(project_name)
class AssetsPanel(QtWidgets.QWidget):
@ -119,7 +122,7 @@ class AssetsPanel(QtWidgets.QWidget):
back_clicked = QtCore.Signal()
session_changed = QtCore.Signal()
def __init__(self, project_handler, dbcon, parent=None):
def __init__(self, launcher_model, dbcon, parent=None):
super(AssetsPanel, self).__init__(parent=parent)
self.dbcon = dbcon
@ -129,7 +132,7 @@ class AssetsPanel(QtWidgets.QWidget):
btn_back = QtWidgets.QPushButton(self)
btn_back.setIcon(btn_back_icon)
project_bar = ProjectBar(project_handler, self)
project_bar = ProjectBar(launcher_model, self)
project_bar_layout = QtWidgets.QHBoxLayout()
project_bar_layout.setContentsMargins(0, 0, 0, 0)
@ -138,12 +141,14 @@ class AssetsPanel(QtWidgets.QWidget):
project_bar_layout.addWidget(project_bar)
# Assets widget
assets_widget = SingleSelectAssetsWidget(dbcon=self.dbcon, parent=self)
assets_widget = LauncherAssetsWidget(
launcher_model, dbcon=self.dbcon, parent=self
)
# Make assets view flickable
assets_widget.activate_flick_charm()
# Tasks widget
tasks_widget = TasksWidget(self.dbcon, self)
tasks_widget = LauncherTaskWidget(launcher_model, self.dbcon, self)
# Body
body = QtWidgets.QSplitter(self)
@ -165,19 +170,20 @@ class AssetsPanel(QtWidgets.QWidget):
layout.addWidget(body)
# signals
project_handler.project_changed.connect(self._on_project_changed)
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_handler = project_handler
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)
@ -196,8 +202,6 @@ class AssetsPanel(QtWidgets.QWidget):
def _on_project_changed(self):
self.session_changed.emit()
self.assets_widget.refresh()
def _on_asset_changed(self):
"""Callback on asset selection changed
@ -250,18 +254,17 @@ class LauncherWindow(QtWidgets.QDialog):
| QtCore.Qt.WindowCloseButtonHint
)
project_model = ProjectModel(self.dbcon)
project_handler = ProjectHandler(self.dbcon, project_model)
launcher_model = LauncherModel(self.dbcon)
project_panel = ProjectsPanel(project_handler)
asset_panel = AssetsPanel(project_handler, 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(project_handler, self.dbcon, self)
actions_bar = ActionBar(launcher_model, self.dbcon, self)
# statusbar
message_label = QtWidgets.QLabel(self)
@ -303,8 +306,8 @@ class LauncherWindow(QtWidgets.QDialog):
# signals
actions_bar.action_clicked.connect(self.on_action_clicked)
action_history.trigger_history.connect(self.on_history_action)
project_handler.project_changed.connect(self.on_project_change)
project_handler.timer_timeout.connect(self._on_refresh_timeout)
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)
@ -314,7 +317,7 @@ class LauncherWindow(QtWidgets.QDialog):
self._message_timer = message_timer
self.project_handler = project_handler
self._launcher_model = launcher_model
self._message_label = message_label
self.project_panel = project_panel
@ -324,19 +327,19 @@ class LauncherWindow(QtWidgets.QDialog):
self.page_slider = page_slider
def showEvent(self, event):
self.project_handler.set_active(True)
self.project_handler.start_timer(True)
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.project_handler.stop_timer()
self._launcher_model.stop_refresh_timer()
def changeEvent(self, event):
if event.type() == QtCore.QEvent.ActivationChange:
self.project_handler.set_active(self.isActiveWindow())
self._launcher_model.set_active(self.isActiveWindow())
super(LauncherWindow, self).changeEvent(event)
def set_page(self, page):
@ -371,7 +374,7 @@ class LauncherWindow(QtWidgets.QDialog):
self.discover_actions()
def on_back_clicked(self):
self.project_handler.set_project(None)
self._launcher_model.set_project_name(None)
self.set_page(0)
self.discover_actions()

View file

@ -354,7 +354,6 @@ class AssetModel(QtGui.QStandardItemModel):
Args:
force (bool): Stop currently running refresh start new refresh.
clear (bool): Clear model before refresh thread starts.
"""
# Skip fetch if there is already other thread fetching documents
if self._refreshing:

View file

@ -9,6 +9,7 @@ from .views import DeselectableTreeView
TASK_NAME_ROLE = QtCore.Qt.UserRole + 1
TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2
TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3
TASK_ASSIGNEE_ROLE = QtCore.Qt.UserRole + 4
class TasksModel(QtGui.QStandardItemModel):
@ -144,11 +145,19 @@ class TasksModel(QtGui.QStandardItemModel):
task_type_icon = task_type_info.get("icon")
icon = self._get_icon(task_icon, task_type_icon)
task_assignees = set()
assignees_data = task_info.get("assignees") or []
for assignee in assignees_data:
username = assignee.get("username")
if username:
task_assignees.add(username)
label = "{} ({})".format(task_name, task_type or "type N/A")
item = QtGui.QStandardItem(label)
item.setData(task_name, TASK_NAME_ROLE)
item.setData(task_type, TASK_TYPE_ROLE)
item.setData(task_order, TASK_ORDER_ROLE)
item.setData(task_assignees, TASK_ASSIGNEE_ROLE)
item.setData(icon, QtCore.Qt.DecorationRole)
item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable)
items.append(item)