pyapp-kit / superqt

Missing widgets and components for Qt-python
https://pyapp-kit.github.io/superqt/
BSD 3-Clause "New" or "Revised" License
189 stars 33 forks source link

TreeView/TableView/ListView in conjuction with psygnal EventedDataclass/EventedModel #255

Open ndxmrb opened 3 weeks ago

ndxmrb commented 3 weeks ago

Hi all, I wasn't sure where to place this question (which might lead to a feature request?), but because of #77 I now decided to go ahead here:

If I had a psygnal EventedContainer holding EventedModel instances, I'd like those to be shown in a QTable[...] or even a QTree[...]. Two approaches are feasible:

  1. Create a ModelMapper that maps the EventedContainer to a QAbstractItemModel. This could utilize the .internalPointer() function to return data directly from the EventedModel. Then you could use a QTreeView or QTableView to show the data from the Qt model.
  2. Create a QTreeWidget or QTableWidget, in which the respective Items are connected to the EventedModel signals.

I see the following issues with these approaches:

  1. Maybe you could move the entire EventedModel to a separate thread, such that data operations always run there and never in the GUI thread. Maybe the Qt model approach prohibits that because of the pointer to the data? It's stated multiple times in Qt forums that you should never ever touch model data from outside the GUI thread.
  2. Probably this needs reimplementation of all the neat things Qt already provides in the model/view classes. But as it copies data rather than referencing it, data exchange via signals between threads is possible as it is the recommended approach.

Which implementation should be chosen here? Regarding the threading, I fear approach 2. could still run into race conditions on read/write enabled items (but isn't that the case for magicgui widgets, too?)...

Again, #77 leaves this intentionally vague, so this might be the starting point.

I tried to provide some (ever so small) value in addition to my question, but if it still is too forumy-questiony, feel free to close this 😋

Thanks for having a look though!

tlambert03 commented 3 weeks ago

hey @ndxmrb, #77 was essentially a pointer to a large amount of prior work and discussion that happened over in napari.

In napari, I did create QTreeView and QListView subclasses that were designed to do exactly this. Those were based on napari's evented model, before i extracted it into psygnal's variant. and #77 was just an acknowledgement that it would be nice to pull all of that work into superqt, and base it off of psygnal instead of the napari EventedModel.

I'm not going to lie, it gets rather hairy... but the end goal is indeed quite nice; python objects that automagically update themselves as you work with them. I would encourage you to start by looking at the napri code for QtListView, which would be a view onto the equivalent of a psygnal SelectableEventedList, and

@alisterburt took a stab at porting all of that over here in https://github.com/pyapp-kit/superqt/pull/105 ... so you could also start there and see what state it's in.

QTable and QTree would be a bit harder, since there's no direct model backing either of those quite like the SelectableEventedList is to a ListView, so additional decisions would need to be made.

I'm afraid i can't personally offer to work on this at the moment, but if you do dig deeper and have any questions as you go, feel free to keep asking them here

ndxmrb commented 3 weeks ago

Hey @tlambert03, thanks for the quick answer and the pointers.

I had a look at napari prior to my post here. I tried to use it as a role model, but got stuck (for now)... I also looked at the Issues over there, because I was hoping to find a road map for migrating to psygnal SelectableEventedList and friends. 😋 Is that the plan?

It's nice that the ground work for a list model has already been done. Do you remember why you didn't pull after the test was added? @alisterburt mentions a yet to be developed superqt QtListView in the docstrings. Why do we need this/what does it do? I thought by defining the model we achieve easy use of preexisting Qt views.

As for the additional decisions you mentioned: For tables and trees the main difference is, that conceptionally the ListModel is just a 1-to-1 mapper of the existing structure, whereas for tables and trees you essentially define a view model instead. E.g. you provide the columns you want to see [and the nested container path]. Other frameworks do this too and have different approaches. For instance, the close-to-pydantic FastUI does this with an extra DisplayLookup class. But it's difficult for me to gather all pros and cons for all these solutions.

Coming from EventedModels, a decision on what to show could be made in the ListModel as well though. Or would you rather do this by using delegates on the view?