Skip to content

Commit 4b84a4e

Browse files
authored
Merge branch 'AcademySoftwareFoundation:master' into master
2 parents 8da8ba1 + 400b353 commit 4b84a4e

17 files changed

+584
-54
lines changed

cuegui/cuegui/Constants.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,17 @@ def __get_version_from_cmd(command):
185185
COLOR_USER_2 = QtGui.QColor(*__bg_colors[1])
186186
COLOR_USER_3 = QtGui.QColor(*__bg_colors[2])
187187
COLOR_USER_4 = QtGui.QColor(*__bg_colors[3])
188+
COLOR_USER_5 = QtGui.QColor(*__bg_colors[4])
189+
COLOR_USER_6 = QtGui.QColor(*__bg_colors[5])
190+
COLOR_USER_7 = QtGui.QColor(*__bg_colors[6])
191+
COLOR_USER_8 = QtGui.QColor(*__bg_colors[7])
192+
COLOR_USER_9 = QtGui.QColor(*__bg_colors[8])
193+
COLOR_USER_10 = QtGui.QColor(*__bg_colors[9])
194+
COLOR_USER_11 = QtGui.QColor(*__bg_colors[10])
195+
COLOR_USER_12 = QtGui.QColor(*__bg_colors[11])
196+
COLOR_USER_13 = QtGui.QColor(*__bg_colors[12])
197+
COLOR_USER_14 = QtGui.QColor(*__bg_colors[13])
198+
COLOR_USER_15 = QtGui.QColor(*__bg_colors[14])
188199

189200
__frame_colors = __config.get('style.colors.frame_state')
190201
RGB_FRAME_STATE = {

cuegui/cuegui/DependDialog.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def __init__(self, rpcOject, parent=None):
3939
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
4040
self.setSizeGripEnabled(True)
4141

42-
self.resize(1000, 600)
42+
self.resize(1200, 800)
4343

4444
name = "Dependencies for "
4545
if cuegui.Utils.isJob(rpcOject):

cuegui/cuegui/DependMonitorTree.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ def __init__(self, parent, rpcObject):
6060

6161
cuegui.AbstractTreeWidget.AbstractTreeWidget.__init__(self, parent)
6262

63+
# Set columns to auto-resize to content
64+
header = self.header()
65+
for col in range(self.columnCount()):
66+
header.setSectionResizeMode(col, QtWidgets.QHeaderView.ResizeToContents)
67+
6368
self.__menuActions = cuegui.MenuActions.MenuActions(
6469
self, self.updateSoon, self.selectedObjects)
6570

cuegui/cuegui/DependWizard.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ def initializePage(self):
388388
self.setSubTitle("What type of dependency would you like %s to have?" % self.__msg())
389389

390390
# it is not respecting or providing my size hints otherwise
391-
self.wizard().setMinimumSize(500, 500)
391+
self.wizard().setMinimumSize(1200, 800)
392392

393393
# pylint: disable=missing-function-docstring
394394
def validatePage(self):

cuegui/cuegui/FrameMonitorTree.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,9 @@ def tick(self):
259259

260260
# Redrawing every even number of seconds to see the current frame
261261
# runtime, LLU and last log line changes. Every second was excessive.
262-
if not self.ticksWithoutUpdate % 2:
263-
self.redraw()
262+
# Always redraw running frames regardless of update status
263+
if self.__job and not self.ticksWithoutUpdate % 2:
264+
self.redrawRunning()
264265

265266
@staticmethod
266267
def getCores(frame, format_as_string=False):

cuegui/cuegui/JobMonitorTree.py

Lines changed: 226 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class JobMonitorTree(cuegui.AbstractTreeWidget.AbstractTreeWidget):
9090
"""Tree widget to display a list of monitored jobs."""
9191

9292
__loadMine = True
93-
__groupDependent = True
93+
__groupByMode = "Clear" # Options: "Clear", "Dependent", "Show-Shot", "Show-Shot-Username"
9494
view_object = QtCore.Signal(object)
9595

9696
def __init__(self, parent):
@@ -182,6 +182,8 @@ def __init__(self, parent):
182182
self.__dependentJobs = {}
183183
self._dependent_items = {}
184184
self.__reverseDependents = {}
185+
self.__groupItems = {} # For Show-Shot and Show-Shot-Username grouping
186+
self.__groupExpansionState = {} # Track expansion state of group items
185187
self.local_plugin_saved_values = {}
186188
# Used to build right click context menus
187189
self.__menuActions = cuegui.MenuActions.MenuActions(
@@ -274,12 +276,25 @@ def setLoadMine(self, value):
274276
@type value: boolean or QtCore.Qt.Checked or QtCore.Qt.Unchecked"""
275277
self.__loadMine = (value is True or value == QtCore.Qt.Checked)
276278

277-
def setGroupDependent(self, value):
278-
"""Enables or disables the auto grouping of the dependent jobs
279-
@param value: New groupDependent state
280-
@type value: boolean or QtCore.Qt.Checked or QtCore.Qt.Unchecked"""
281-
self.__groupDependent = (value is True or value == QtCore.Qt.Checked)
282-
self.updateRequest()
279+
def setGroupBy(self, mode):
280+
"""Sets the grouping mode for jobs
281+
@param mode: Grouping mode ("Clear", "Dependent", "Show-Shot", "Show-Shot-Username")
282+
@type mode: str"""
283+
if mode in ["Clear", "Dependent", "Show-Shot", "Show-Shot-Username"]:
284+
old_mode = self.__groupByMode
285+
self.__groupByMode = mode
286+
287+
# If we have existing jobs, regroup them
288+
if self._items and old_mode != mode:
289+
current_jobs = {}
290+
for proxy, item in list(self._items.items()):
291+
current_jobs[proxy] = item.rpcObject
292+
293+
# Process update with new grouping
294+
if current_jobs:
295+
self._processUpdate(None, current_jobs)
296+
297+
self.updateRequest()
283298

284299
def addJob(self, job, timestamp=None, loading_from_config=False):
285300
"""Adds a job to the list. With locking"
@@ -294,7 +309,7 @@ def addJob(self, job, timestamp=None, loading_from_config=False):
294309
try:
295310
if newJobObj:
296311
jobKey = cuegui.Utils.getObjectKey(newJobObj)
297-
if not self.__groupDependent:
312+
if self.__groupByMode == "Clear":
298313
self.__load[jobKey] = newJobObj
299314
self.__jobTimeLoaded[jobKey] = timestamp if timestamp else time.time()
300315
else:
@@ -397,6 +412,7 @@ def removeAllItems(self):
397412
del self.__jobTimeLoaded[proxy]
398413
self.__dependentJobs.clear()
399414
self.__reverseDependents.clear()
415+
self.__groupItems.clear()
400416
cuegui.AbstractTreeWidget.AbstractTreeWidget.removeAllItems(self)
401417

402418
def removeFinishedItems(self):
@@ -497,6 +513,20 @@ def contextMenuEvent(self, e):
497513
self.__menuActions.jobs().addAction(color_menu, "setUserColor2")
498514
self.__menuActions.jobs().addAction(color_menu, "setUserColor3")
499515
self.__menuActions.jobs().addAction(color_menu, "setUserColor4")
516+
self.__menuActions.jobs().addAction(color_menu, "setUserColor5")
517+
self.__menuActions.jobs().addAction(color_menu, "setUserColor6")
518+
self.__menuActions.jobs().addAction(color_menu, "setUserColor7")
519+
self.__menuActions.jobs().addAction(color_menu, "setUserColor8")
520+
self.__menuActions.jobs().addAction(color_menu, "setUserColor9")
521+
self.__menuActions.jobs().addAction(color_menu, "setUserColor10")
522+
self.__menuActions.jobs().addAction(color_menu, "setUserColor11")
523+
self.__menuActions.jobs().addAction(color_menu, "setUserColor12")
524+
self.__menuActions.jobs().addAction(color_menu, "setUserColor13")
525+
self.__menuActions.jobs().addAction(color_menu, "setUserColor14")
526+
self.__menuActions.jobs().addAction(color_menu, "setUserColor15")
527+
color_menu.addSeparator()
528+
self.__menuActions.jobs().addAction(color_menu, "setUserCustomColor")
529+
color_menu.addSeparator()
500530
self.__menuActions.jobs().addAction(color_menu, "clearUserColor")
501531
menu.addMenu(color_menu)
502532

@@ -570,6 +600,62 @@ def actionSetUserColor(self, color):
570600
self.__userColors[objectKey] = color
571601
item.setUserColor(color)
572602

603+
def actionSetUserCustomColor(self):
604+
"""Opens a dialog to set a custom RGB color for selected items"""
605+
dialog = QtWidgets.QDialog(self)
606+
dialog.setWindowTitle("Set Custom Color (RGB)")
607+
dialog.setModal(True)
608+
609+
layout = QtWidgets.QFormLayout()
610+
611+
# Create spinboxes for RGB values
612+
r_spinbox = QtWidgets.QSpinBox()
613+
r_spinbox.setRange(0, 255)
614+
r_spinbox.setValue(100)
615+
616+
g_spinbox = QtWidgets.QSpinBox()
617+
g_spinbox.setRange(0, 255)
618+
g_spinbox.setValue(100)
619+
620+
b_spinbox = QtWidgets.QSpinBox()
621+
b_spinbox.setRange(0, 255)
622+
b_spinbox.setValue(100)
623+
624+
# Color preview label
625+
preview_label = QtWidgets.QLabel()
626+
preview_label.setMinimumSize(200, 50)
627+
preview_label.setFrameStyle(QtWidgets.QFrame.Box)
628+
preview_label.setStyleSheet("background-color: rgb(100, 100, 100);")
629+
630+
def update_preview():
631+
r = r_spinbox.value()
632+
g = g_spinbox.value()
633+
b = b_spinbox.value()
634+
preview_label.setStyleSheet(f"background-color: rgb({r}, {g}, {b});")
635+
636+
r_spinbox.valueChanged.connect(update_preview)
637+
g_spinbox.valueChanged.connect(update_preview)
638+
b_spinbox.valueChanged.connect(update_preview)
639+
640+
layout.addRow("Red (0-255):", r_spinbox)
641+
layout.addRow("Green (0-255):", g_spinbox)
642+
layout.addRow("Blue (0-255):", b_spinbox)
643+
layout.addRow("Preview:", preview_label)
644+
645+
# Buttons
646+
button_box = QtWidgets.QDialogButtonBox(
647+
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
648+
button_box.accepted.connect(dialog.accept)
649+
button_box.rejected.connect(dialog.reject)
650+
layout.addRow(button_box)
651+
652+
dialog.setLayout(layout)
653+
654+
if dialog.exec_() == QtWidgets.QDialog.Accepted:
655+
# Create custom color from RGB values
656+
custom_color = QtGui.QColor(r_spinbox.value(), g_spinbox.value(), b_spinbox.value())
657+
self.actionSetUserColor(custom_color)
658+
573659
def actionEatSelectedItems(self):
574660
"""Eats all dead frames for selected jobs"""
575661
self.__menuActions.jobs().eatDead()
@@ -635,11 +721,15 @@ def _getUpdate(self):
635721
monitored_proxies.remove(proxy)
636722

637723
if monitored_proxies:
638-
for job in opencue.api.getJobs(
639-
id=[proxyId.split('.')[-1] for proxyId in monitored_proxies],
640-
include_finished=True):
641-
objectKey = cuegui.Utils.getObjectKey(job)
642-
jobs[objectKey] = job
724+
# Batch fetch jobs to improve performance
725+
batch_size = 50 # Fetch in smaller batches to avoid timeouts
726+
for i in range(0, len(monitored_proxies), batch_size):
727+
batch = monitored_proxies[i:i + batch_size]
728+
for job in opencue.api.getJobs(
729+
id=[proxyId.split('.')[-1] for proxyId in batch],
730+
include_finished=True):
731+
objectKey = cuegui.Utils.getObjectKey(job)
732+
jobs[objectKey] = job
643733

644734
except opencue.exception.CueException as e:
645735
list(map(logger.warning, cuegui.Utils.exceptionOutput(e)))
@@ -672,16 +762,92 @@ def _processUpdate(self, work, rpcObjects):
672762
for item in self._dependent_items.values():
673763
self.__jobTimeLoaded[cuegui.Utils.getObjectKey(item.rpcObject)] = item.created
674764

765+
# Save expansion state of current group items
766+
for group_key, group_item in self.__groupItems.items():
767+
self.__groupExpansionState[group_key] = group_item.isExpanded()
768+
675769
self._items = {}
770+
self.__groupItems = {}
676771
self.clear()
677772

678773
for proxy, job in iteritems(rpcObjects):
679-
self._items[proxy] = JobWidgetItem(job,
680-
self.invisibleRootItem(),
681-
self.__jobTimeLoaded.get(proxy, None))
682-
if proxy in self.__userColors:
683-
self._items[proxy].setUserColor(self.__userColors[proxy])
684-
if self.__groupDependent:
774+
# Handle different grouping modes
775+
if self.__groupByMode == "Clear":
776+
# No grouping - flat list
777+
self._items[proxy] = JobWidgetItem(job,
778+
self.invisibleRootItem(),
779+
self.__jobTimeLoaded.get(proxy, None))
780+
781+
elif self.__groupByMode == "Show-Shot":
782+
# Group by show-shot
783+
job_name = job.data.name
784+
parts = job_name.split("-")
785+
if len(parts) >= 2:
786+
show = parts[0]
787+
shot = parts[1]
788+
group_key = f"{show}-{shot}"
789+
790+
# Create or get group parent item
791+
if group_key not in self.__groupItems:
792+
self.__groupItems[group_key] = GroupWidgetItem(
793+
group_key, self.invisibleRootItem(), "show-shot")
794+
# Restore expansion state or default to expanded
795+
is_expanded = self.__groupExpansionState.get(group_key, True)
796+
self.__groupItems[group_key].setExpanded(is_expanded)
797+
798+
# Add job as child of group
799+
self._items[proxy] = JobWidgetItem(job,
800+
self.__groupItems[group_key],
801+
self.__jobTimeLoaded.get(proxy, None))
802+
else:
803+
# Can't parse show-shot, add to root
804+
self._items[proxy] = JobWidgetItem(job,
805+
self.invisibleRootItem(),
806+
self.__jobTimeLoaded.get(proxy, None))
807+
808+
elif self.__groupByMode == "Show-Shot-Username":
809+
# Group by show-shot-username
810+
job_name = job.data.name
811+
parts = job_name.split("-")
812+
if len(parts) >= 2:
813+
show = parts[0]
814+
shot = parts[1]
815+
# Extract username from the rest
816+
if len(parts) >= 3:
817+
rest = "-".join(parts[2:])
818+
username_parts = rest.split("_")
819+
if username_parts:
820+
username = username_parts[0]
821+
else:
822+
username = "unknown"
823+
else:
824+
username = "unknown"
825+
826+
group_key = f"{show}-{shot}-{username}"
827+
828+
# Create or get group parent item
829+
if group_key not in self.__groupItems:
830+
self.__groupItems[group_key] = GroupWidgetItem(
831+
group_key, self.invisibleRootItem(), "show-shot-username")
832+
# Restore expansion state or default to expanded
833+
is_expanded = self.__groupExpansionState.get(group_key, True)
834+
self.__groupItems[group_key].setExpanded(is_expanded)
835+
836+
# Add job as child of group
837+
self._items[proxy] = JobWidgetItem(job,
838+
self.__groupItems[group_key],
839+
self.__jobTimeLoaded.get(proxy, None))
840+
else:
841+
# Can't parse show-shot-username, add to root
842+
self._items[proxy] = JobWidgetItem(job,
843+
self.invisibleRootItem(),
844+
self.__jobTimeLoaded.get(proxy, None))
845+
846+
elif self.__groupByMode == "Dependent":
847+
# Dependent mode - group by job dependencies
848+
self._items[proxy] = JobWidgetItem(job,
849+
self.invisibleRootItem(),
850+
self.__jobTimeLoaded.get(proxy, None))
685851
dependent_jobs = self.__dependentJobs.get(proxy, [])
686852
for djob in dependent_jobs:
687853
item = JobWidgetItem(djob,
@@ -693,6 +859,9 @@ def _processUpdate(self, work, rpcObjects):
693859
self._dependent_items[dkey].setUserColor(
694860
self.__userColors[dkey])
695861

862+
if proxy in self.__userColors:
863+
self._items[proxy].setUserColor(self.__userColors[proxy])
864+
696865
self.verticalScrollBar().setRange(scrolled, len(rpcObjects.keys()) - scrolled)
697866
list(map(lambda key: self._items[key].setSelected(True),
698867
[key for key in selectedKeys if key in self._items]))
@@ -797,3 +966,41 @@ def data(self, col, role):
797966
return self.rpcObject.isPaused()
798967

799968
return cuegui.Constants.QVARIANT_NULL
969+
970+
971+
class GroupWidgetItem(QtWidgets.QTreeWidgetItem):
972+
"""Represents a group parent item in the JobMonitorTree."""
973+
974+
def __init__(self, group_name, parent, group_type):
975+
"""Initialize a group widget item.
976+
@param group_name: The name of the group (e.g., "show-shot" or "show-shot-username")
977+
@param parent: The parent item
978+
@param group_type: Type of grouping ("show-shot" or "show-shot-username")
979+
"""
980+
QtWidgets.QTreeWidgetItem.__init__(self, parent)
981+
self.group_name = group_name
982+
self.group_type = group_type
983+
self.setText(0, group_name)
984+
985+
# Set bold font for group headers
986+
font = QtGui.QFont()
987+
font.setBold(True)
988+
self.setFont(0, font)
989+
990+
# Make group headers non-selectable
991+
self.setFlags(self.flags() & ~QtCore.Qt.ItemIsSelectable)
992+
993+
def data(self, col, role):
994+
"""Return data for the given column and role."""
995+
if role == QtCore.Qt.DisplayRole:
996+
if col == 0:
997+
return self.group_name
998+
return ""
999+
1000+
if role == QtCore.Qt.FontRole and col == 0:
1001+
font = QtGui.QFont()
1002+
font.setBold(True)
1003+
return font
1004+
1005+
# Let the parent handle all other roles (including selection colors)
1006+
return QtWidgets.QTreeWidgetItem.data(self, col, role)

0 commit comments

Comments
 (0)