Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d450026
created _delegates subfolder in tools that contains all delegates fro…
iLLiCiTiT Aug 5, 2019
191b016
created _models subfolder in tools that contains all models, proxies …
iLLiCiTiT Aug 5, 2019
999940e
created _views subfolder in tools that contains all views from tools
iLLiCiTiT Aug 5, 2019
99607f2
created _widgets subfolder in tools that contains reusable widgets fr…
iLLiCiTiT Aug 5, 2019
dc2eb48
replaced imports of delegates, models, views and widgets in tools
iLLiCiTiT Aug 5, 2019
4c6de0f
(fix) continuation line under-indented for visual indent and line length
iLLiCiTiT Aug 6, 2019
7b58a35
(fix) added missing imports and removed unused
iLLiCiTiT Aug 6, 2019
51ff23c
(fix) added missing log in model_assets
iLLiCiTiT Aug 6, 2019
75f8981
changed plural and singular names to match MVC naming conventions
iLLiCiTiT Aug 6, 2019
b58f706
(fix) QtGui import bug fixed and last found indent issue
iLLiCiTiT Aug 6, 2019
b220fa2
removed underscore and moved delegates, models, views and widgets to …
iLLiCiTiT Aug 7, 2019
7f5e850
moved style to gui
iLLiCiTiT Aug 7, 2019
a7dc83d
modified imports so avalon.gui works as api (first version)
iLLiCiTiT Aug 7, 2019
2112aba
(fix) removed custom code pipeline.py
iLLiCiTiT Aug 7, 2019
2229c78
(fix) add missing commas in __all__
iLLiCiTiT Aug 7, 2019
4efc978
(fix) removed unused QtCore from style
iLLiCiTiT Aug 7, 2019
101d0a8
(fix) fixed Qt imports when Qt is not in PYTHONPATH
iLLiCiTiT Aug 7, 2019
c3b9ba3
(fix) imports without subimporting
iLLiCiTiT Aug 7, 2019
1e178aa
gui is now access point to its subfolders not to classes in subfolders
iLLiCiTiT Aug 9, 2019
867fd91
(fix) update SubsetModel with last master changes
iLLiCiTiT Aug 9, 2019
7728a31
Merge branch 'master_origin_upstream' into cleanup/PYPE-465_tools_parts
iLLiCiTiT Aug 9, 2019
22010f9
modified path of moved style in run_maya_tests.py and modified packag…
iLLiCiTiT Aug 11, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added avalon/gui/__init__.py
Empty file.
12 changes: 12 additions & 0 deletions avalon/gui/delegates/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from . import lib

from .delegate_pretty_time import PrettyTimeDelegate
from .delegate_version import VersionDelegate


__all__ = [
"lib",

"PrettyTimeDelegate",
"VersionDelegate"
]
13 changes: 13 additions & 0 deletions avalon/gui/delegates/delegate_pretty_time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from ...vendor.Qt import QtWidgets
from .lib import pretty_timestamp


class PrettyTimeDelegate(QtWidgets.QStyledItemDelegate):
"""A delegate that displays a timestamp as a pretty date.

This displays dates like `pretty_date`.

"""

def displayText(self, value, locale):
return pretty_timestamp(value)
70 changes: 70 additions & 0 deletions avalon/gui/delegates/delegate_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from ...vendor.Qt import QtWidgets, QtCore
from ... import io
from ..models import SubsetModel


class VersionDelegate(QtWidgets.QStyledItemDelegate):
"""A delegate that display version integer formatted as version string."""

version_changed = QtCore.Signal()
first_run = False
lock = False

def _format_version(self, value):
"""Formats integer to displayable version name"""
return "v{0:03d}".format(value)

def displayText(self, value, locale):
assert isinstance(value, int), "Version is not `int`"
return self._format_version(value)

def createEditor(self, parent, option, index):
node = index.data(SubsetModel.NodeRole)
if node.get("isGroup"):
return

editor = QtWidgets.QComboBox(parent)

def commit_data():
if not self.first_run:
self.commitData.emit(editor) # Update model data
self.version_changed.emit() # Display model data
editor.currentIndexChanged.connect(commit_data)

self.first_run = True
self.lock = False

return editor

def setEditorData(self, editor, index):
if self.lock:
# Only set editor data once per delegation
return

editor.clear()

# Current value of the index
value = index.data(QtCore.Qt.DisplayRole)
assert isinstance(value, int), "Version is not `int`"

# Add all available versions to the editor
node = index.data(SubsetModel.NodeRole)
parent_id = node['version_document']['parent']
versions = io.find({"type": "version", "parent": parent_id},
sort=[("name", 1)])
index = 0
for i, version in enumerate(versions):
label = self._format_version(version['name'])
editor.addItem(label, userData=version)

if version['name'] == value:
index = i

editor.setCurrentIndex(index) # Will trigger index-change signal
self.first_run = False
self.lock = True

def setModelData(self, editor, model, index):
"""Apply the integer version back in the model"""
version = editor.itemData(editor.currentIndex())
model.setData(index, version['name'])
89 changes: 89 additions & 0 deletions avalon/gui/delegates/lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import time
from datetime import datetime
import logging

log = logging.getLogger(__name__)


def pretty_date(t, now=None, strftime="%b %d %Y %H:%M"):
"""Parse datetime to readable timestamp

Within first ten seconds:
- "just now",
Within first minute ago:
- "%S seconds ago"
Within one hour ago:
- "%M minutes ago".
Within one day ago:
- "%H:%M hours ago"
Else:
"%Y-%m-%d %H:%M:%S"

"""

assert isinstance(t, datetime)
if now is None:
now = datetime.now()
assert isinstance(now, datetime)
diff = now - t

second_diff = diff.seconds
day_diff = diff.days

# future (consider as just now)
if day_diff < 0:
return 'just now'

# history
if day_diff == 0:
if second_diff < 10:
return "just now"
if second_diff < 60:
return str(second_diff) + " seconds ago"
if second_diff < 120:
return "a minute ago"
if second_diff < 3600:
return str(second_diff // 60) + " minutes ago"
if second_diff < 86400:
minutes = (second_diff % 3600) // 60
hours = second_diff // 3600
return "{0}:{1:02d} hours ago".format(hours, minutes)

return t.strftime(strftime)


def pretty_timestamp(t, now=None):
"""Parse timestamp to user readable format

>>> pretty_timestamp("20170614T151122Z", now="20170614T151123Z")
'just now'

>>> pretty_timestamp("20170614T151122Z", now="20170614T171222Z")
'2:01 hours ago'

Args:
t (str): The time string to parse.
now (str, optional)

Returns:
str: human readable "recent" date.

"""

if now is not None:
try:
now = time.strptime(now, "%Y%m%dT%H%M%SZ")
now = datetime.fromtimestamp(time.mktime(now))
except ValueError as e:
log.warning("Can't parse 'now' time format: {0} {1}".format(t, e))
return None

try:
t = time.strptime(t, "%Y%m%dT%H%M%SZ")
except ValueError as e:
log.warning("Can't parse time format: {0} {1}".format(t, e))
return None
dt = datetime.fromtimestamp(time.mktime(t))

# prettify
return pretty_date(dt, now=now)
73 changes: 73 additions & 0 deletions avalon/gui/lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from .. import io, api
from ..vendor import qtawesome

FAMILY_ICON_COLOR = "#0091B2"
FAMILY_CONFIG = {}


def get(config, name):
"""Get value from config with fallback to default"""
# We assume the default fallback key in the config is `__default__`
return config.get(name, config.get("__default__", None))


def refresh_family_config():
"""Get the family configurations from the database

The configuration must be stored on the project under `config`.
For example:

{
"config": {
"families": [
{"name": "avalon.camera", label: "Camera", "icon": "photo"},
{"name": "avalon.anim", label: "Animation", "icon": "male"},
]
}
}

It is possible to override the default behavior and set specific families
checked. For example we only want the families imagesequence and camera
to be visible in the Loader.

# This will turn every item off
api.data["familyStateDefault"] = False

# Only allow the imagesequence and camera
api.data["familyStateToggled"] = ["imagesequence", "camera"]

"""
# Update the icons from the project configuration
project = io.find_one({"type": "project"},
projection={"config.families": True})

assert project, "Project not found!"
families = project['config'].get("families", [])
families = {family['name']: family for family in families}

# Check if any family state are being overwritten by the configuration
default_state = api.data.get("familiesStateDefault", True)
toggled = set(api.data.get("familiesStateToggled", []))

# Replace icons with a Qt icon we can use in the user interfaces
default_icon = qtawesome.icon("fa.folder", color=FAMILY_ICON_COLOR)
for name, family in families.items():
# Set family icon
icon = family.get("icon", None)
if icon:
family['icon'] = qtawesome.icon("fa.{}".format(icon),
color=FAMILY_ICON_COLOR)
else:
family['icon'] = default_icon

# Update state
state = not default_state if name in toggled else default_state
family["state"] = state

# Default configuration
families["__default__"] = {"icon": default_icon}

FAMILY_CONFIG.clear()
FAMILY_CONFIG.update(families)

return families
36 changes: 36 additions & 0 deletions avalon/gui/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from . import lib

from .node import Node

from .model_tree import TreeModel
from .model_task import TaskModel
from .model_asset import AssetModel
from .model_subset import SubsetModel
from .model_inventory import InventoryModel

from .proxy_filter import FilterProxyModel
from .proxy_exact_matches_filter import ExactMatchesFilterProxyModel
from .proxy_recursive_sort_filter import RecursiveSortFilterProxyModel
from .proxy_group_filter import GroupMemberFilterProxyModel
from .proxy_subset_filter import SubsetFilterProxyModel
from .proxy_family_filter import FamilyFilterProxyModel


__all__ = [
"lib",

"Node",

"TreeModel",
"TaskModel",
"AssetModel",
"SubsetModel",
"InventoryModel",

"FilterProxyModel",
"ExactMatchesFilterProxyModel",
"RecursiveSortFilterProxyModel",
"GroupMemberFilterProxyModel",
"SubsetFilterProxyModel",
"FamilyFilterProxyModel"
]
Loading