import logging
import datetime
from PyQt4 import QtGui, QtCore, Qt
from pipeLion.ui.graphs.tableBg import TableBg
from pipeLion.ui.graphs.tableTask import TableTask
from pipeLion.ui.utils.specialTypes import TableTuple
from pipeLion.ui.utils import colorManager
_log = logging.getLogger('pipeLion')
[docs]class TableGraph(QtGui.QGraphicsScene):
""" TableGraph is derived by QGraphicsScene
It defines a standardized way to show any kind of table
rules for the tables can be defined by adding entries to the tables dictionary, which is a property-method of this class
depending on which table-view is set by the currentTableType instance variable the scene paints different kind of tables
and offers different editing modes.
"""
def getAllShots(self, item):
from pipeLion.assets.shot import Shot
project = item.getProject
if not project:
return []
if isinstance(item, Shot):
columns = [
TableTuple(('Name',item.name())),
TableTuple(('Image', QtGui.QIcon(item.image() or colorManager.noImage))),
TableTuple(('First_Frame', item.first_frame())),
TableTuple(('Last_Frame', item.last_frame())),
TableTuple(('Description', item.description()))
]
else:
shot = item.getShot or item
columns = [
TableTuple(('Name', shot.name())),
]
user = None
if item.user():
if isinstance(item.user(), list):
user = [each.name() for each in item.user() if each]
else:
user = item.user().name()
additionalColums = [
TableTuple((item.type(), item.name())),
TableTuple(('state(%s)' % item.type()[:3].lower(), colorManager.getStateIcon(item.state()))),
TableTuple(('user(%s)' % item.type()[:3].lower(), user)),
]
columns.extend(additionalColums)
return columns
def getAllShotAttributes(self, selectedAssets):
columns = ['Name', 'Image', 'First_Frame', 'Last_Frame', 'Description']
from pipeLion.assets.shot import Shot
for eachAsset in selectedAssets:
project = eachAsset.getProject
if not project:
return []
dependentTasks = [each for each in eachAsset.getDependencies(recursive=True)]
dependentTasks.append(eachAsset)
for eachDependency in dependentTasks:
if eachDependency.type() not in columns:
columns.extend([eachDependency.type(), 'state(%s)'%eachDependency.type()[:3].lower(), 'user(%s)'%eachDependency.type()[:3].lower()])
return columns
def sortShotbreakdown(self, selectedAssets):
from pipeLion.assets.shot import Shot
listToBeSorted = \
sorted(selectedAssets,
key=lambda item: \
('%s_%s_%s_%s' % tuple([each[1] for each in self.getAllShots(item)])) \
if not isinstance(item, Shot) else \
'%s_%s' % (item.name(), item.first_frame())
)
return listToBeSorted
[docs] def getTimeline(self, selectedAssets):
""" this method outsources a complex functionality from a lambda-function in the tables-property dictionary
:param selectedAssets: list of the assets, represented by the Nodes, selected in the adminGraph
:type selectedAssets: list of AbstractAsset
:returns: a list of dates including the timespan of all assets in selectedAssets an in addition to a certain handleLength
:rtype: list of datetime.timedelta
"""
allColumns = []
handles = datetime.timedelta(days=30)
for i, eachAsset in enumerate(selectedAssets):
if i:
if eachAsset.end_date() >= last:
last = eachAsset.end_date()
if eachAsset.start_date() <= first:
first = eachAsset.start_date()
else:
first = eachAsset.start_date()
last = eachAsset.end_date()
if selectedAssets:
delta = ((last + datetime.timedelta(days=1)) - first) + (2 * handles)
start = first - handles
for i in xrange(delta.days):
day = start + datetime.timedelta(days=i)
newDate = datetime.date(day.year, day.month, day.day)
allColumns.append(newDate)
return allColumns
def getAllDays(self, asset):
allValues = []
for i in xrange((asset.end_date()-asset.start_date()).days+1):
date = asset.start_date()+datetime.timedelta(days=i)
newDate = datetime.date(date.year, date.month, date.day)
allValues.append(TableTuple((newDate, asset.name())))
return allValues
[docs] def moveUser(self, delta, asset):
""" this method outsources a complex functionality from a lambda-function in the tables-property dictionary
:param delta: amount of colums the mouse was moved in y position -1=one column to the left 1=one column top the right
:type delta: int
:param asset: asset on which the event was performed
:type asset: AbstractAsset
"""
column = self.bg.getCurrentColumn(asset.user())+delta
user = self.bg.getCurrentColumnInfo(column)
if user and not isinstance(asset.user(), list):
asset.assignUser(user)
elif isinstance(asset.user(), list):
_log.info('this task cannot be assigned, as it is a group-task')
elif user == None:
asset.deassignUser()
@property
[docs] def tables(self):
""" tables returns a dictionary which holds lambda rules on how to treat a certain table-type
the dictionary is build up as follows::
{
'X-Axsis/Y-Axis' : #string description eg: time/task
{
'all' : lambda selectedAssets: function to get all values of the x-achsis, #mandatory
'getter' : lambda item: function to get all values of the current item, #mandatory
'sort' : lambda items: function to sort the output of 'all' and 'getter', #optional
'str' : lambda item: function to convert the item to a string(display information), #optional
'rowSorting' : lambda selectedAssets: allows to sort all rows from top to bottom #optional
'changeMin' : lambda delta, item: function to define what should be done with the item in case the in-point of an item was changed in the interface, #optional
'changeMax' : lambda delta, item: function to define what should be done with the item in case the out-point of an item was changed in the interface, #optional
'move' : lambda delta, item: function to define what should be done with the item in case an item was moved in the interface, #optional
'deleteExisting' : lambda column, item: function to define what should be done with the item in case the item was deleted in the interface, #optional
'createNew' : lambda column, item: function to define what should be done with the item in case a new item was created in the interface, #optional
'keepSeparate' : True/False indicates if the entries should be kept separately or if they should be grouped together. default is grouping #optional
}
}
.. note:: all mandatory items have to be defined and all optional items define user-actions. if an action is not defined, the missing interaction is not available in the interface
:returns: the table-definition-dictionary
:rtype: dictionary
"""
return {
'time/task' : {
'all' : lambda selectedAssets: self.getTimeline(selectedAssets),
'getter' : lambda item : self.getAllDays(item),
'sort' : lambda items : sorted(items),
'rowSorting' : lambda selectedAssets: sorted(selectedAssets,
key = lambda asset: asset.getShot.name() if asset.getShot else '',
reverse=True
),
'move' : lambda delta, item : item.assetDict.update(\
{'start_date':item.start_date() + datetime.timedelta(days=delta), \
'end_date':item.end_date() + datetime.timedelta(days=delta)}\
),
'changeMin' : lambda delta, item : item.assetDict.update(\
{'start_date':item.start_date() + datetime.timedelta(days=delta)}\
),
'changeMax' : lambda delta, item : item.assetDict.update(\
{'end_date':item.end_date() + datetime.timedelta(days=delta)}\
),
'str' : lambda item : item.strftime('%y-%m-%d'),
},
'user/task' : {
'all' : lambda selectedAssets: selectedAssets[-1].getProject.user() if selectedAssets else [],
'getter' : lambda item : [TableTuple((each, item.name())) for each in item.user()] \
if isinstance(item.user(), list) \
else [TableTuple((item.user(), item.name()))] \
if item.user() \
else [],
'sort' : lambda items : items,
'move' : lambda delta, item : self.moveUser(delta, item),
'str': lambda item : item.name() if item else 'None',
},
'state/task' : {
'all' : lambda selectedAssets: [0, 1, 2],
'getter' : lambda item : [TableTuple((item.state(), item.name()))],
'sort' : lambda items : items,
'move' : lambda delta, item : item.set_state(item.state()+delta),
},
'task/shot' : {
'all' : lambda selectedAssets: self.getAllShotAttributes(selectedAssets),
'getter': lambda item : self.getAllShots(item),
'keepSeparate': True,
'rowSorting': lambda allAssets: self.sortShotbreakdown(allAssets),
},
}
def __init__(self, selectedAssets=None, editable=True, parent=None):
""" the constructor of TableGraph
no argument needs to be set here, it can be changed anytime.
:param selectedAssets: list of AbstractAsset-objects which should be used to create the table
:type selectedAssets: list of AbstractAsset
:param editable: indicates if the user should be able to change values in the table or not
:type editable: bool
:param parent: QGraphicsScene parent
:type parent: QWidget
"""
if not selectedAssets:
selectedAssets = []
super(TableGraph, self).__init__(parent)
self.editable = editable
self.currentTableType = 'time/task'
self.selectedNodes = self.getSelectedNodes(selectedAssets)
self.sortSelectedNodes()
self.createAllTaskEntries()
def sortSelectedNodes(self):
if self.tables[self.currentTableType].has_key('rowSorting'):
self.selectedNodes = self.tables[self.currentTableType]['rowSorting'](self.selectedNodes)
[docs] def getSelectedNodes(self, selectedAssets):
""" method will generate a list with all dependent assets from all selected assets without double entries
:param selectedAssets: list of selected assets
:type selectedAssets: list of AbstractAsset
:returns: list of assets
:rtype: list of AbstractAsset
"""
allAssets = set(selectedAssets)
for eachAsset in list(allAssets)[:]:
dependentAssets = eachAsset.getDependencies(True)
allAssets = allAssets | set(dependentAssets)
allAssets = list(allAssets)
return allAssets
[docs] def createAllTaskEntries(self):
""" this will create all table-items and the table-background and add them to its scene.
"""
self.allItems = {}
self.bg = TableBg(self.tables[self.currentTableType], self.selectedNodes)
self.addItem(self.bg)
for i, eachAsset in enumerate(self.selectedNodes):
eachItem = TableTask(self.tables[self.currentTableType], eachAsset, i, self.bg, self.editable)
self.allItems[eachAsset] = eachItem
self.addItem(self.allItems[eachAsset])
[docs] def updateTable(self, selectedNodes):
""" method will repaint the entire scene
"""
self.selectedNodes = self.getSelectedNodes(selectedNodes)
self.sortSelectedNodes()
self.clear()
self.bg.updateTable(self.selectedNodes)
self.createAllTaskEntries()
[docs] def updateItems(self):
""" method to update all items in the table
"""
for eachAsset, eachItem in self.allItems.iteritems():
eachItem.updateData()
[docs] def tableTypeChanged(self, tableType):
""" method to define what happens, when the table-type changes
:param tableType: name of the new tableType
:type tableType: string
"""
self.currentTableType = str(tableType)
self.clear()
for eachView in self.views():
eachView.centerOn(0.0,0.0)
self.sortSelectedNodes()
self.bg.updateTable(self.selectedNodes)
self.createAllTaskEntries()