visualdynamics.gui.project_tree¶
project_tree
¶
The project tree.
Dropping files on the tree works, and it takes some doing.
The tree used to import what landed on it through QAbstractItemView's
own drop machinery, which decides what a drop means from the item under
the cursor and the drag-drop mode — so the same file dragged onto the
same tree would import on the third go. That was replaced by having the
window answer drags for every part of itself at once, and the tree
refuse them, on the understanding that Qt walks up from a widget that
refuses to one that accepts.
It does walk up. But the tree lives in a dock widget inside a scroll area, the walk starts at whichever child is under the cursor, and whether the window is reached depends on where in that stack the drag was resolved. Dropping on the tree still missed sometimes, while the plot pane on the other side of the window never did.
So the tree accepts drags again — but it does not handle them. It
takes a drag carrying files, refuses everything else outright so
nothing else is swallowed, and hands the paths straight to the window's
own importer. setDragDropMode(NoDragDrop) keeps the view's item
machinery out of it entirely: these three overrides never call up to
it. One importer, reached two ways, neither of which depends on Qt
resolving a target through a dock.
Objects drag within the tree too, to move one between link groups, and that rides on the same hand-rolled handling rather than on the item machinery: a drag of our own carries a mime type of our own, and a drop is read as the object row it lands on.
Every one of our drags is accepted, including ones the project will go
on to refuse, and this is the part that was wrong first. Refusing the
drag reads well — the move is simply not offered — but Qt takes an
ignored dragMoveEvent to mean the widget is not a drop target there,
so releasing the mouse produces no drop at all: no move, no message,
nothing. A geometry dragged onto a side that already had one behaved
exactly like a broken feature, because from the outside there is no
difference. So the drop always lands and the window always answers it,
and landing_for decides only whether the landing line is drawn —
the part that is genuinely about showing what applies.
Classes:
| Name | Description |
|---|---|
ProjectTree |
Tree of imported objects. |
Functions:
| Name | Description |
|---|---|
trace_drag |
One line per drag event, into a small log truncated at launch. |
start_trace |
Truncate the log; called once, when the window comes up. |
dropped_files |
The local paths a drag is carrying, if it carries any. |
dragged_objects |
The object names a drag of our own is carrying. |
Classes¶
ProjectTree
¶
Bases: QTreeWidget
Tree of imported objects.
Linked objects are joined by a bracket painted down the left edge —
link_spans is [(color, [items], bold)], maintained by the
window; the bold flag marks the Basis group's bracket.
Methods:
| Name | Description |
|---|---|
eventFilter |
Focus entering a record grid must not move the tree's current. |
draggable |
Whether an item is one of the project's objects. Sub-items — |
startDrag |
One drag, two readers. |
copy_selected |
Copy (Cmd/Ctrl+C): the selection onto the clipboard, as the |
paste |
Paste (Cmd/Ctrl+V): our own object names go to the window as |
span_at |
Index of the link bracket under a viewport position, or |
Source code in src/visualdynamics/gui/project_tree.py
Methods:¶
eventFilter
¶
Focus entering a record grid must not move the tree's current.
setItemWidget makes this view an event filter on the widget,
and QAbstractItemView's filter answers the widget's FocusIn by
moving the current index to the widget's own row — the grid's
holder, which is deliberately unselectable, so the move is a
ClearAndSelect that selects nothing and clears everything.
Any focus into a grid that a cell pick did not follow — a
click on its header, its margins, the space right of its last
column — silently dropped every other object's picks: a record
chosen in one FRF's grid vanished when a click aimed at
another grid's first row landed a few pixels high (Brandon,
2026-08-30; reproduced with setFocus() alone). The grid keeps
the focus; the tree keeps the selection.
Source code in src/visualdynamics/gui/project_tree.py
draggable
¶
Whether an item is one of the project's objects. Sub-items — a geometry's Nodes, a record inside a data array — are parts of an object, and an object is what a link group holds.
Source code in src/visualdynamics/gui/project_tree.py
startDrag
¶
One drag, two readers.
Inside the tree it carries OBJECT_MIME and moves objects
between link groups, exactly as before. Outside — Finder,
Explorer, a file manager — it carries text/uri-list and
becomes a .vdyn saved wherever it lands. _offer and
dropEvent both ask for the object names first, so an internal
drop never sees the file half.
Copy and Move are offered, defaulting to Move: Finder refuses a move-only drag outright, and Move is what a drop inside the tree means.
Source code in src/visualdynamics/gui/project_tree.py
copy_selected
¶
Copy (Cmd/Ctrl+C): the selection onto the clipboard, as the
same two-faced payload a drag carries — object names for a
paste back into a tree, and .vdyn files for a paste into
Finder or Explorer, written only when a file manager actually
asks (Brandon, 2026-09-03). Returns the names copied — or
[PROJECT_ROW] for the project row, which copies the whole
project as one file — and an empty list when nothing was
selected, in which case the clipboard is left as it was.
Source code in src/visualdynamics/gui/project_tree.py
paste
¶
Paste (Cmd/Ctrl+V): our own object names go to the window as
objects_pasted; files from anywhere — a file manager, another
window — arrive the way a drop of files does. Returns whether
the clipboard held anything we read.
Source code in src/visualdynamics/gui/project_tree.py
span_at
¶
Index of the link bracket under a viewport position, or None — how a right-click lands on a group rather than on the object beside it.
Source code in src/visualdynamics/gui/project_tree.py
Functions:¶
trace_drag
¶
One line per drag event, into a small log truncated at launch.
Here because a drop that dies silently is undiagnosable from a description: the badge shows, nothing lands, and every stage of the chain — routing, accepting, delivering, importing — looks the same from the outside. A drop on a real window from a real Finder cannot be reproduced by tests, so when one misbehaves this log is the only witness. The cost is a few lines per drag, in a file that never outlives the session.
Source code in src/visualdynamics/gui/project_tree.py
start_trace
¶
Truncate the log; called once, when the window comes up.
Deliberately not an application-wide event filter, though one would also catch a drop delivered to a widget nobody instrumented: a Python filter running for every event in a process that hosts QtWebEngine segfaulted inside Chromium's own machinery. The per-surface lines are enough — an accepted enter and accepted moves followed by no drop is a platform non-delivery, whoever it was not delivered to.
Source code in src/visualdynamics/gui/project_tree.py
dropped_files
¶
The local paths a drag is carrying, if it carries any.
Our own drags carry none, however real the file they are offering. This is the one place that can say so: the tree ignoring the drop was not enough, because an ignored drop propagates to the window, which takes a file dropped anywhere on it — deliberately, since a fresh window's tree is a 90 px strip and missing it reads as the drop being ignored. Both ask here, so both get the same answer.
Source code in src/visualdynamics/gui/project_tree.py
dragged_objects
¶
The object names a drag of our own is carrying.