def testSelectionExpansion(self): d = {} for i in range(0, 10): dd = {} for j in range(0, 10): dd[str(j)] = j d[str(i)] = dd p = Gaffer.DictPath(d, "/") w = GafferUI.PathListingWidget( p, allowMultipleSelection=True, displayMode=GafferUI.PathListingWidget.DisplayMode.Tree) _GafferUI._pathListingWidgetAttachTester( GafferUI._qtAddress(w._qtWidget())) self.assertTrue(w.getSelection().isEmpty()) self.assertTrue(w.getExpansion().isEmpty()) s = IECore.PathMatcher(["/1", "/2", "/9", "/2/5"]) w.setSelection(s, expandNonLeaf=True, scrollToFirst=False) self.assertEqual(w.getSelection(), s) _GafferUI._pathModelWaitForPendingUpdates( GafferUI._qtAddress(w._qtWidget().model())) self.assertEqual(w.getExpansion(), IECore.PathMatcher(["/1", "/2", "/9"]))
def testModelDoesntUpdateUnexpandedPaths(self): class InfinitePath(Gaffer.Path): # `self.visitedPaths` is a PathMatcher that will be filled with all the # children visited by the PathModel. def __init__(self, path, root="/", filter=None, visitedPaths=None): Gaffer.Path.__init__(self, path, root, filter=filter) self.visitedPaths = visitedPaths if visitedPaths is not None else IECore.PathMatcher( ) self.visitedPaths.addPath(str(self)) def isValid(self, canceller=None): return True def isLeaf(self, canceller=None): return False def copy(self): return InfinitePath(self[:], self.root(), self.getFilter(), self.visitedPaths) def _children(self, canceller=None): return [ InfinitePath(self[:] + [x], self.root(), self.getFilter(), self.visitedPaths) for x in ["a", "b"] ] # Create an infinite model and expand it up to a fixed depth. path1 = InfinitePath("/") widget = GafferUI.PathListingWidget( path=path1, displayMode=GafferUI.PathListingWidget.DisplayMode.Tree) _GafferUI._pathListingWidgetAttachTester( GafferUI._qtAddress(widget._qtWidget())) self.__expandModel(widget._qtWidget().model(), depth=4) # Replace with a new path, to force the PathModel into evaluating # it to see if there are any changes. The PathModel should only # visit paths that have been visited before, because there is no # need to notify Qt of layout or data changes for items that haven't # been visited yet. path2 = InfinitePath("/") widget.setPath(path2) _GafferUI._pathModelWaitForPendingUpdates( GafferUI._qtAddress(widget._qtWidget().model())) self.assertEqual(path2.visitedPaths, path1.visitedPaths)
def testModelChangingData(self): root = { "a": 10, } widget = GafferUI.PathListingWidget( path=Gaffer.DictPath(root, "/"), columns=[ GafferUI.PathListingWidget.defaultNameColumn, GafferUI.PathListingWidget.StandardColumn( "Value", "dict:value") ], ) _GafferUI._pathListingWidgetAttachTester( GafferUI._qtAddress(widget._qtWidget())) # Do initial query and wait for async update. model = widget._qtWidget().model() self.assertEqual(model.rowCount(), 0) _GafferUI._pathModelWaitForPendingUpdates(GafferUI._qtAddress(model)) self.assertEqual(model.rowCount(), 1) self.assertEqual(model.data(model.index(0, 1, QtCore.QModelIndex())), 10) # Set up change tracking. changes = [] def dataChanged(*args): changes.append(args) model.dataChanged.connect(dataChanged) # Change value in column 1. Check that `dataChanged` is emitted # and we can query the new value. root["a"] = 20 self.__emitPathChanged(widget) self.assertEqual(len(changes), 1) self.assertEqual(model.data(model.index(0, 1, QtCore.QModelIndex())), 20) # Trigger an artificial update and assert that the data has not changed, # and `dataChanged` has not been emitted. self.__emitPathChanged(widget) self.assertEqual(len(changes), 1) self.assertEqual(model.data(model.index(0, 1, QtCore.QModelIndex())), 20)
def __expandModel(cls, model, index=None, queryData=False, depth=10): if depth <= 0: return index = index if index is not None else QtCore.QModelIndex() model.rowCount(index) if queryData: model.data(index) _GafferUI._pathModelWaitForPendingUpdates(GafferUI._qtAddress(model)) for row in range(0, model.rowCount(index)): cls.__expandModel(model, model.index(row, 0, index), queryData, depth - 1)
def testDeeperExpandedPaths(self): p = Gaffer.DictPath({"a": {"b": {"c": {"d": 10}}}}, "/") w = GafferUI.PathListingWidget(p) _GafferUI._pathListingWidgetAttachTester( GafferUI._qtAddress(w._qtWidget())) w.setPathExpanded(p.copy().setFromString("/a/b/c"), True) self.assertTrue(w.getPathExpanded(p.copy().setFromString("/a/b/c"))) _GafferUI._pathModelWaitForPendingUpdates( GafferUI._qtAddress(w._qtWidget().model())) self.assertEqual(self.__expansionFromQt(w), IECore.PathMatcher(["/a/b/c"]))
def testSetExpansionClearsExpansionByUser(self): w = GafferUI.PathListingWidget( Gaffer.DictPath({ "a": 1, "b": 2, "c": 3 }, "/"), displayMode=GafferUI.PathListingWidget.DisplayMode.Tree) model = w._qtWidget().model() model.rowCount() # Trigger population of top level of the model _GafferUI._pathModelWaitForPendingUpdates(GafferUI._qtAddress(model)) self.assertEqual(w.getExpansion(), IECore.PathMatcher()) w._qtWidget().expand(model.index( 0, 0)) # Equivalent to a click by the user self.assertEqual(w.getExpansion(), IECore.PathMatcher(["/a"])) w.setExpansion(IECore.PathMatcher(["/b"])) _GafferUI._pathModelWaitForPendingUpdates(GafferUI._qtAddress(model)) self.assertEqual(self.__expansionFromQt(w), IECore.PathMatcher(["/b"])) w._qtWidget().collapse(model.index( 1, 0)) # Equivalent to a click by the user self.assertEqual(w.getExpansion(), IECore.PathMatcher()) w.setExpansion(IECore.PathMatcher(["/b"])) _GafferUI._pathModelWaitForPendingUpdates(GafferUI._qtAddress(model)) self.assertEqual(self.__expansionFromQt(w), IECore.PathMatcher(["/b"]))
def testExpansionByUser(self): w = GafferUI.PathListingWidget( Gaffer.DictPath({"a": { "b": { "c": 10 } }}, "/"), displayMode=GafferUI.PathListingWidget.DisplayMode.Tree) model = w._qtWidget().model() model.rowCount() # Trigger population of top level of the model _GafferUI._pathModelWaitForPendingUpdates(GafferUI._qtAddress(model)) self.assertEqual(w.getExpansion(), IECore.PathMatcher()) cs = GafferTest.CapturingSlot(w.expansionChangedSignal()) w._qtWidget().setExpanded(model.index(0, 0), True) # Equivalent to a click by the user self.assertEqual(len(cs), 1) self.assertEqual(w.getExpansion(), IECore.PathMatcher(["/a"])) w._qtWidget().setExpanded(model.index(0, 0), False) # Equivalent to a click by the user self.assertEqual(len(cs), 2) self.assertEqual(w.getExpansion(), IECore.PathMatcher())
def __emitPathChanged(widget): widget.getPath().pathChangedSignal()(widget.getPath()) _GafferUI._pathModelWaitForPendingUpdates( GafferUI._qtAddress(widget._qtWidget().model()))
def testExpansion(self): d = {} for i in range(0, 10): dd = {} for j in range(0, 10): dd[str(j)] = j d[str(i)] = dd p = Gaffer.DictPath(d, "/") w = GafferUI.PathListingWidget( p, displayMode=GafferUI.PathListingWidget.DisplayMode.Tree) _GafferUI._pathListingWidgetAttachTester( GafferUI._qtAddress(w._qtWidget())) self.assertTrue(w.getExpansion().isEmpty()) cs = GafferTest.CapturingSlot(w.expansionChangedSignal()) e = IECore.PathMatcher(["/1", "/2", "/2/4", "/1/5", "/3"]) w.setExpansion(e) self.assertEqual(w.getExpansion(), e) self.assertEqual(len(cs), 1) # Wait for asynchronous update to expand model. Then check # that the expected indices are expanded in the QTreeView. _GafferUI._pathModelWaitForPendingUpdates( GafferUI._qtAddress(w._qtWidget().model())) self.assertEqual(self.__expansionFromQt(w), e) # Delete a path that was expanded. d2 = d["2"] del d["2"] self.__emitPathChanged(w) # We don't expect this to affect the result of `getExpansion()` because # the expansion state is independent of the model contents. self.assertEqual(w.getExpansion(), e) # But it should affect what is mirrored in Qt. e2 = IECore.PathMatcher(e) e2.removePath("/2") e2.removePath("/2/4") self.assertEqual(self.__expansionFromQt(w), e2) # If we reintroduce the deleted path, it should be expanded again in Qt. # This behaviour is particularly convenient when switching between # different scenes in the HierarchyView, and matches the behaviour of # the SceneGadget. d["2"] = d2 self.__emitPathChanged(w) self.assertEqual(self.__expansionFromQt(w), e) # Now try to set expansion twice in succession, so the model doesn't have # chance to finish one update before starting the next. e1 = IECore.PathMatcher(["/9", "/9/10", "/8/6"]) e2 = IECore.PathMatcher(["/9", "/9/9", "/5/6", "3"]) w.setExpansion(e1) w.setExpansion(e2) _GafferUI._pathModelWaitForPendingUpdates( GafferUI._qtAddress(w._qtWidget().model())) self.assertEqual(self.__expansionFromQt(w), e2)
def testModelFirstQueryDoesntEmitDataChanged(self): for sortable in (False, True): # Not making widget visible, so it doesn't make any # queries to the model (leaving just our queries and # those made by the attached tester). widget = GafferUI.PathListingWidget( path=Gaffer.DictPath({"v": 10}, "/"), columns=[ GafferUI.PathListingWidget.defaultNameColumn, GafferUI.PathListingWidget.StandardColumn( "Value", "dict:value") ], sortable=sortable, displayMode=GafferUI.PathListingWidget.DisplayMode.Tree) _GafferUI._pathListingWidgetAttachTester( GafferUI._qtAddress(widget._qtWidget())) # Make initial child queries to populate the model with # items, but without evaluating data. We should start # without any rows on the root item. model = widget._qtWidget().model() self.assertEqual(model.rowCount(), 0) # Meaning that we can't even get an index to the first row. self.assertFalse(model.index(0, 0).isValid()) # But if we wait for the update we've just triggered then # we should see a row appear. _GafferUI._pathModelWaitForPendingUpdates( GafferUI._qtAddress(model)) self.assertEqual(model.rowCount(), 1) self.assertTrue(model.index(0, 0).isValid()) # Set up recording for data changes. changes = [] def dataChanged(*args): changes.append(args) model.dataChanged.connect(dataChanged) # We are testing the columns containing "dict:value". valueIndex = model.index(0, 1) if sortable: # Data will have been generated during sorting, so # we can query it immediately. self.assertEqual(model.data(valueIndex), 10) self.assertEqual(len(changes), 0) else: # Data not generated yet. The initial query will receive empty # data and should not trigger `dataChanged`. self.assertIsNone(model.data(valueIndex)) self.assertEqual(len(changes), 0) # But the act of querying will have launched an async # update that should update the value and signal the # the change. _GafferUI._pathModelWaitForPendingUpdates( GafferUI._qtAddress(model)) self.assertEqual(len(changes), 1) self.assertEqual(model.data(valueIndex), 10)
def testModelPathSwap(self): # Make a model for a small hierarchy, and expand the # model fully. root1 = Gaffer.GraphComponent() root1["c"] = Gaffer.GraphComponent() root1["c"]["d"] = Gaffer.GraphComponent() widget = GafferUI.PathListingWidget( path=Gaffer.GraphComponentPath(root1, "/"), columns=[GafferUI.PathListingWidget.defaultNameColumn], displayMode=GafferUI.PathListingWidget.DisplayMode.Tree, ) _GafferUI._pathListingWidgetAttachTester( GafferUI._qtAddress(widget._qtWidget())) path = Gaffer.GraphComponentPath(root1, "/") self.assertEqual(path.property("graphComponent:graphComponent"), root1) path.append("c") self.assertEqual(path.property("graphComponent:graphComponent"), root1["c"]) path.append("d") self.assertEqual(path.property("graphComponent:graphComponent"), root1["c"]["d"]) model = widget._qtWidget().model() self.__expandModel(model) # Get an index for `/c/d` and check that we can retrieve # the right path for it. def assertIndexRefersTo(index, graphComponent): if isinstance(index, QtCore.QPersistentModelIndex): index = QtCore.QModelIndex(index) path = widget._PathListingWidget__pathForIndex(index) self.assertEqual(path.property("graphComponent:graphComponent"), graphComponent) dIndex = model.index(0, 0, model.index(0, 0)) assertIndexRefersTo(dIndex, root1["c"]["d"]) persistentDIndex = QtCore.QPersistentModelIndex(dIndex) assertIndexRefersTo(persistentDIndex, root1["c"]["d"]) # Replace the path with another one referencing an identical hierarchy. root2 = Gaffer.GraphComponent() root2["c"] = Gaffer.GraphComponent() root2["c"]["d"] = Gaffer.GraphComponent() widget.setPath(Gaffer.GraphComponentPath(root2, "/")) _GafferUI._pathModelWaitForPendingUpdates(GafferUI._qtAddress(model)) # Check that the model now references the new path and the # new hierarchy. dIndex = model.index(0, 0, model.index(0, 0)) assertIndexRefersTo(dIndex, root2["c"]["d"]) assertIndexRefersTo(persistentDIndex, root2["c"]["d"])
def testModelSorting(self): # Make a widget with sorting enabled. root = Gaffer.GraphComponent() root["c"] = Gaffer.GraphComponent() root["b"] = Gaffer.GraphComponent() path = Gaffer.GraphComponentPath(root, "/") widget = GafferUI.PathListingWidget( path, columns=[GafferUI.PathListingWidget.defaultNameColumn], sortable=True) _GafferUI._pathListingWidgetAttachTester( GafferUI._qtAddress(widget._qtWidget())) model = widget._qtWidget().model() self.__expandModel(model) self.assertEqual(model.rowCount(), 2) self.assertEqual(model.columnCount(), 1) # Check that the paths appear in sorted order. self.assertEqual(model.data(model.index(0, 0)), "b") self.assertEqual(model.data(model.index(1, 0)), "c") bIndex = QtCore.QPersistentModelIndex(model.index(0, 0)) cIndex = QtCore.QPersistentModelIndex(model.index(1, 0)) # And sorting is maintained when adding another path. root["a"] = Gaffer.GraphComponent() self.__emitPathChanged(widget) self.assertEqual(model.rowCount(), 3) self.assertEqual(model.columnCount(), 1) self.assertEqual(model.data(model.index(0, 0)), "a") self.assertEqual(model.data(model.index(1, 0)), "b") self.assertEqual(model.data(model.index(2, 0)), "c") self.assertTrue(bIndex.isValid()) self.assertTrue(cIndex.isValid()) self.assertEqual(model.data(bIndex), "b") self.assertEqual(model.data(cIndex), "c") # Turning sorting off should revert to the order in # the path itself. aIndex = QtCore.QPersistentModelIndex(model.index(0, 0)) widget.setSortable(False) _GafferUI._pathModelWaitForPendingUpdates(GafferUI._qtAddress(model)) self.assertEqual(model.rowCount(), 3) self.assertEqual(model.columnCount(), 1) self.assertTrue(aIndex.isValid()) self.assertTrue(bIndex.isValid()) self.assertTrue(cIndex.isValid()) self.assertEqual(model.data(aIndex), "a") self.assertEqual(model.data(bIndex), "b") self.assertEqual(model.data(cIndex), "c") self.assertEqual(model.data(model.index(0, 0)), "c") self.assertEqual(model.data(model.index(1, 0)), "b") self.assertEqual(model.data(model.index(2, 0)), "a")