Repository navigation
Open folders in place in the list view #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,13 @@ import QtQuick.Controls | |
| ListView { | ||
| id: list | ||
|
|
||
| // Whether folders open in place (the window's tree, T). Off, the | ||
| // list is a plain list: → and ← go into and out of folders, as in the | ||
| // columns. Turned off, the open folders close, and the rows shifting | ||
| // put the cursor on the folder at the top its item was in. | ||
| property bool tree: false | ||
| onTreeChanged: if (!tree) backend.folder.collapseAll() | ||
|
|
||
| function select(row, keep) { selecting.select(row, keep) } | ||
| // A click on empty space lets go of the cursor; the pane then keeps it | ||
| // let go of through a re-read or a switch of view. | ||
|
|
@@ -17,22 +24,97 @@ ListView { | |
|
|
||
| Selecting { id: selecting; view: list } | ||
| onCurrentIndexChanged: selecting.cursorMoved() | ||
|
|
||
| // Rows coming and going in place (a folder opened or closed above or | ||
| // around the cursor, its items arriving, a file appearing, a new order) | ||
| // would move the cursor off its item, and the cursor moving selects | ||
| // what it lands on. So the rows are laid out at once, quietly, and the | ||
| // cursor put back on its item; when that went out of sight with a | ||
| // folder closed, on the folder, which is then selected if nothing is. | ||
| property string shiftingPath: "" | ||
| property string shiftingAnchor: "" | ||
| Connections { | ||
| target: backend.folder | ||
| function onRowsShifting() { | ||
| list.shiftingPath = backend.folder.get(list.currentIndex).path || "" | ||
| list.shiftingAnchor = backend.folder.get(selecting.anchor).path || "" | ||
| } | ||
| function onRowsShifted() { | ||
| const folder = backend.folder | ||
| selecting.quiet = true | ||
| list.forceLayout() | ||
| selecting.quiet = false | ||
| if (!list.shiftingPath) | ||
| return | ||
| const shown = folder.path.endsWith("/") ? folder.path : folder.path + "/" | ||
| let row = folder.indexOfPath(list.shiftingPath) | ||
| const hidden = row < 0 | ||
| for (let up = list.shiftingPath; row < 0 && up.startsWith(shown);) { | ||
| up = up.substring(0, up.lastIndexOf("/")) | ||
| row = folder.indexOfPath(up) | ||
| } | ||
| // Gone from the folder itself (trashed, moved away): the cursor | ||
| // stays on its row, and the folder's re-read places it as before. | ||
| if (row < 0) | ||
| return | ||
| if (row !== list.currentIndex) | ||
| selecting.moveTo(row) | ||
| if (hidden) { | ||
| selecting.anchor = row | ||
| if (folder.selectedCount === 0) | ||
| folder.selectOnly(row) | ||
| } else { | ||
| selecting.anchor = folder.indexOfPath(list.shiftingAnchor) | ||
| } | ||
| } | ||
| } | ||
| Keys.onPressed: (event) => selecting.extendBy(event, event.key === Qt.Key_Up ? -1 : event.key === Qt.Key_Down ? 1 : 0) | ||
|
|
||
| Band { | ||
| view: list | ||
| rectOf: (i) => Qt.rect(0, i * 32, list.width, 32) | ||
| } | ||
|
|
||
| // → goes into the selected folder and ← back up, as in the columns; | ||
| // mirrored, the other way round, with the reading direction. | ||
| function enter() { | ||
| const item = backend.folder.get(currentIndex) | ||
| if (item.isDir) | ||
| // With the tree on, → opens the cursor's folder in place, below it, and on an open one | ||
| // steps to its first item; ← closes it, or steps out to the folder it's | ||
| // in, and at the top goes back up as before. Mirrored, the other way | ||
| // round, with the reading direction. Where nothing nests (a search, | ||
| // Recents, a plugin's view) → goes into the folder, as in the columns. | ||
| function inward() { | ||
| const folder = backend.folder | ||
| const item = folder.get(currentIndex) | ||
| if (!item.isDir) | ||
| return | ||
| if (!tree) { | ||
| backend.navigate(item.path) | ||
| } else if (item.open) { | ||
| // Its items may still be on their way; until they're there, the | ||
| // cursor stays put. | ||
| if (currentIndex + 1 < count && folder.get(currentIndex + 1).depth > item.depth) | ||
| moveCursor(currentIndex + 1) | ||
| } else if (!folder.expand(currentIndex)) { | ||
| backend.navigate(item.path) | ||
| } | ||
| } | ||
| function outward() { | ||
| const folder = backend.folder | ||
| const item = folder.get(currentIndex) | ||
| if (!tree) | ||
| backend.goUp() | ||
| else if (item.open) | ||
| folder.collapse(currentIndex) | ||
| else if (folder.parentRow(currentIndex) >= 0) | ||
| moveCursor(folder.parentRow(currentIndex)) | ||
| else | ||
| backend.goUp() | ||
| } | ||
| // Moved like the arrows move it, so what it lands on is selected. | ||
| function moveCursor(row) { | ||
| currentIndex = row | ||
| positionViewAtIndex(row, ListView.Contain) | ||
| } | ||
| Keys.onRightPressed: LayoutMirroring.enabled ? backend.goUp() : enter() | ||
| Keys.onLeftPressed: LayoutMirroring.enabled ? enter() : backend.goUp() | ||
| Keys.onRightPressed: LayoutMirroring.enabled ? outward() : inward() | ||
| Keys.onLeftPressed: LayoutMirroring.enabled ? inward() : outward() | ||
|
|
||
| // Right-click on an item (selecting it) or on empty space. | ||
| signal contextMenu(var item, string folder, point at) | ||
|
|
@@ -56,8 +138,16 @@ ListView { | |
| delegate: FileRow { | ||
| width: list.width | ||
| detailed: true | ||
| tree: list.tree | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Folder arrows remain visible in search results and Recents when the tree is on, but clicking them does nothing. The delegate receives |
||
| current: selected | ||
| onClicked: (modifiers) => { selecting.click(index, modifiers); list.forceActiveFocus() } | ||
| // The arrow opens or closes the folder in place, leaving the folder | ||
| // shown and the selection as they are. | ||
| onToggled: { | ||
| if (list.tree) | ||
| open ? backend.folder.collapse(index) : backend.folder.expand(index) | ||
| list.forceActiveFocus() | ||
| } | ||
| onActivated: backend.open(path) | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renaming a nested file moves the cursor to its parent instead of keeping the renamed file selected.
FileOps::rename()reports the actual parent folder, butFolderPane.onDoneignores that result because it differs frombackend.path. When the child listing arrives, this fallback treats the old name as a removed file and selects its parent. Follow completed changes inside open folders by their full destination path before using the parent fallback.Knowledge Base Used: Folder view layouts