flea-qml 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +478 -2
- package/THIRD-PARTY-NOTICES.md +29 -0
- package/demos/App.java +5 -0
- package/demos/Dockerfile +4 -0
- package/demos/Main.kt +3 -0
- package/demos/Program.cs +1 -0
- package/demos/README.md +12 -0
- package/demos/app.dart +3 -0
- package/demos/app.deb +0 -0
- package/demos/app.js +1 -0
- package/demos/backup.tar +0 -0
- package/demos/budget.xlsx +0 -0
- package/demos/build.sh +2 -0
- package/demos/bundle.zip +0 -0
- package/demos/clip.mp4 +0 -0
- package/demos/data.sqlite +0 -0
- package/demos/disk.iso +0 -0
- package/demos/engine.cpp +5 -0
- package/demos/firmware.bin +0 -0
- package/demos/flake.nix +4 -0
- package/demos/folder/inside.txt +1 -0
- package/demos/font.ttf +0 -0
- package/demos/index.html +5 -0
- package/demos/index.php +2 -0
- package/demos/init.lua +1 -0
- package/demos/letter.docx +0 -0
- package/demos/lib.rs +3 -0
- package/demos/main.c +6 -0
- package/demos/main.h +3 -0
- package/demos/math-and-diagrams.md +200 -0
- package/demos/notes.tar.gz +0 -0
- package/demos/notes.txt +3 -0
- package/demos/photo.png +0 -0
- package/demos/program +2 -0
- package/demos/report.pdf +32 -0
- package/demos/script.py +1 -0
- package/demos/server.go +7 -0
- package/demos/settings.json +4 -0
- package/demos/slides.pptx +0 -0
- package/demos/song.mp3 +0 -0
- package/demos/style.css +4 -0
- package/demos/tool.rb +1 -0
- package/demos/types.ts +2 -0
- package/docs/pele.md +23 -0
- package/docs/protocol.md +3420 -0
- package/docs/temml.md +63 -0
- package/index.js +107 -0
- package/package.json +34 -4
- package/src/archive.js +402 -0
- package/src/archiveops.js +697 -0
- package/src/backend.js +369 -0
- package/src/bulkrename.js +95 -0
- package/src/clip-host.js +76 -0
- package/src/clip.js +462 -0
- package/src/code.js +168 -0
- package/src/compare.js +74 -0
- package/src/create.js +128 -0
- package/src/dirsize.js +147 -0
- package/src/figure-svg.js +530 -0
- package/src/figure-thread.js +23 -0
- package/src/figure.js +132 -0
- package/src/find.js +55 -0
- package/src/flea.js +39 -0
- package/src/fsinfo.js +39 -0
- package/src/highlight.js +141 -0
- package/src/html.js +52 -0
- package/src/kinds.js +89 -0
- package/src/listing.js +130 -0
- package/src/markdown-html.js +317 -0
- package/src/mathml.js +186 -0
- package/src/menu.js +180 -0
- package/src/meta.js +129 -0
- package/src/office.js +810 -0
- package/src/order.js +100 -0
- package/src/peek.js +49 -0
- package/src/pele-wrapper.js +695 -0
- package/src/permissions.js +180 -0
- package/src/places.js +313 -0
- package/src/preview-language.js +10 -0
- package/src/transfer.js +669 -0
- package/src/trash-record.js +41 -0
- package/src/trash.js +210 -0
- package/src/trashbrowse.js +326 -0
- package/src/uistate.js +393 -0
- package/src/uistore.js +168 -0
- package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
- package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
- package/src/vendor/LICENSES/MIT-katex.txt +21 -0
- package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
- package/src/vendor/LICENSES/MIT-pele.txt +40 -0
- package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
- package/src/vendor/LICENSES/MIT-temml.txt +21 -0
- package/src/vendor/pele.js +145 -0
- package/src/vendor/temml.mjs +14708 -0
- package/src/watch.js +98 -0
- package/src/wayland/client.js +109 -0
- package/src/wayland/protocol.js +55 -0
- package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
- package/src/wayland/protocols/wayland.xml +3248 -0
- package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
- package/src/wayland/resource-io.js +6 -0
- package/src/wayland/socket-discovery.js +26 -0
- package/src/wayland/transport-posix.js +120 -0
- package/src/wayland/transport-win32.js +11 -0
- package/src/wayland/transport.js +3 -0
- package/src/wayland/win32-resources.js +80 -0
- package/src/wayland/win32-wire.js +62 -0
- package/test/archive.test.js +80 -0
- package/test/bench-markdown-scroll.mjs +190 -0
- package/test/clip-host.test.js +113 -0
- package/test/code-preview.test.js +35 -0
- package/test/compare-ui-paths.test.js +24 -0
- package/test/compare.test.js +54 -0
- package/test/delete-trash.test.js +67 -0
- package/test/dirsize.test.js +138 -0
- package/test/file-ops.test.js +241 -0
- package/test/find-contents.test.js +161 -0
- package/test/find-input.test.js +66 -0
- package/test/fixtures/pele-parity.json +1006 -0
- package/test/highlight-launcher.test.js +35 -0
- package/test/html-preview.test.js +22 -0
- package/test/input.test.js +15 -0
- package/test/keymap.test.js +26 -0
- package/test/language-icons.test.js +36 -0
- package/test/launcher.test.js +51 -0
- package/test/manual-code-scroll.mjs +28 -0
- package/test/manual-column-scroll.mjs +24 -0
- package/test/manual-compare-ui.mjs +20 -0
- package/test/manual-find-status.mjs +37 -0
- package/test/manual-html-security.mjs +23 -0
- package/test/manual-html-ui.mjs +30 -0
- package/test/manual-syntax-preview.mjs +26 -0
- package/test/manual-trash-ui.mjs +86 -0
- package/test/markdown-diagrams.test.js +93 -0
- package/test/markdown-html.test.js +105 -0
- package/test/mathml.test.js +92 -0
- package/test/menu-transfer.test.js +57 -0
- package/test/office-preview.test.js +154 -0
- package/test/pele-parity.test.js +28 -0
- package/test/places.test.js +134 -0
- package/test/preview-keys.test.js +60 -0
- package/test/preview-language.test.js +22 -0
- package/test/status-filename.test.js +20 -0
- package/test/transfer-replace-merge.test.js +187 -0
- package/test/trash-address.test.js +23 -0
- package/test/trash-view.test.js +37 -0
- package/test/trashbrowse.test.js +135 -0
- package/ui/AboutFacts.qml +78 -0
- package/ui/ArchiveCards.qml +68 -0
- package/ui/ArchiveCompressDialog.qml +378 -0
- package/ui/ArchivePasswordDialog.qml +257 -0
- package/ui/Backend.qml +771 -0
- package/ui/BulkOldNames.qml +47 -0
- package/ui/BulkRenameDialog.qml +380 -0
- package/ui/CardFrame.qml +67 -0
- package/ui/CardScroll.qml +79 -0
- package/ui/CheckBox.qml +73 -0
- package/ui/ChromeBar.qml +360 -0
- package/ui/ChromeButton.qml +131 -0
- package/ui/ChromeModes.qml +43 -0
- package/ui/CodeLines.qml +256 -0
- package/ui/CodeRefusal.qml +52 -0
- package/ui/CollideConfirm.qml +147 -0
- package/ui/CollideHost.qml +167 -0
- package/ui/ColumnPane.qml +523 -0
- package/ui/ColumnRow.qml +380 -0
- package/ui/ColumnsArea.qml +594 -0
- package/ui/ColumnsLanding.qml +147 -0
- package/ui/CompareView.qml +321 -0
- package/ui/ContextMenu.qml +743 -0
- package/ui/ConvertDialog.qml +456 -0
- package/ui/CountedWorker.qml +7 -0
- package/ui/Crumb.qml +64 -0
- package/ui/CrumbDrop.qml +25 -0
- package/ui/CsvTable.qml +394 -0
- package/ui/CursorMark.qml +41 -0
- package/ui/DefaultClaim.qml +130 -0
- package/ui/DeviceLock.qml +47 -0
- package/ui/DeviceMounts.qml +540 -0
- package/ui/DialogButton.qml +150 -0
- package/ui/DialogField.qml +147 -0
- package/ui/DialogHeader.qml +31 -0
- package/ui/DialogScrim.qml +20 -0
- package/ui/DialogTitle.qml +37 -0
- package/ui/Divider.qml +9 -0
- package/ui/DropInto.qml +126 -0
- package/ui/DropTarget.qml +35 -0
- package/ui/DropboxMark.qml +60 -0
- package/ui/EmptyState.qml +150 -0
- package/ui/EscapeHint.qml +11 -0
- package/ui/FactsTable.qml +54 -0
- package/ui/FastScrollHandler.qml +476 -0
- package/ui/FatNamesConfirm.qml +157 -0
- package/ui/Favourites.qml +143 -0
- package/ui/FigureService.qml +120 -0
- package/ui/FileDrag.qml +267 -0
- package/ui/FilterStrip.qml +133 -0
- package/ui/FitMetrics.qml +22 -0
- package/ui/FlashDriveRows.qml +137 -0
- package/ui/FlashHost.qml +72 -0
- package/ui/FlashImageRow.qml +104 -0
- package/ui/FlashRunner.qml +119 -0
- package/ui/FlashStepRow.qml +172 -0
- package/ui/FlashStrip.qml +38 -0
- package/ui/FlashView.qml +397 -0
- package/ui/FleaMark.qml +88 -0
- package/ui/FlyoutFade.qml +23 -0
- package/ui/FontPane.qml +170 -0
- package/ui/Glass.qml +114 -0
- package/ui/GlassTagger.qml +59 -0
- package/ui/Glyph.qml +64 -0
- package/ui/GlyphSolid.qml +22 -0
- package/ui/GridArea.qml +664 -0
- package/ui/GridTile.qml +325 -0
- package/ui/GvfsBridge.qml +164 -0
- package/ui/Header.qml +433 -0
- package/ui/HoverTimers.qml +39 -0
- package/ui/HtmlClip.qml +30 -0
- package/ui/HtmlPane.qml +63 -0
- package/ui/HyprlandMark.qml +45 -0
- package/ui/Ipc.qml +1005 -0
- package/ui/IpcControls.qml +7 -0
- package/ui/IpcMenuState.qml +48 -0
- package/ui/IpcPreviewState.qml +185 -0
- package/ui/JumpPath.qml +51 -0
- package/ui/KeymapSheet.qml +561 -0
- package/ui/KeymapSheetResult.qml +89 -0
- package/ui/KindStack.qml +40 -0
- package/ui/LangMark.qml +51 -0
- package/ui/LangMarkLoader.qml +26 -0
- package/ui/List.qml +480 -0
- package/ui/LoadingState.qml +54 -0
- package/ui/LocalSend.qml +56 -0
- package/ui/LocalSendMark.qml +73 -0
- package/ui/MarkdownBlockView.qml +311 -0
- package/ui/MarkdownBlocks.qml +35 -0
- package/ui/MarkdownFigure.qml +254 -0
- package/ui/MarkdownImages.qml +72 -0
- package/ui/MarkdownList.qml +81 -0
- package/ui/MarkdownMathsText.qml +97 -0
- package/ui/MarkdownPane.qml +83 -0
- package/ui/MarkdownPictures.qml +41 -0
- package/ui/MarkdownQuote.qml +52 -0
- package/ui/MarkdownRemote.qml +82 -0
- package/ui/MarkdownSource.qml +155 -0
- package/ui/MarkdownTable.qml +222 -0
- package/ui/MarkdownTableScroll.qml +64 -0
- package/ui/MarkdownTableWheel.qml +126 -0
- package/ui/MarkdownText.qml +87 -0
- package/ui/MarkdownWorker.js +132 -0
- package/ui/MatchText.qml +99 -0
- package/ui/MediaSession.qml +38 -0
- package/ui/MediaSound.qml +26 -0
- package/ui/MediaStrip.qml +191 -0
- package/ui/MediaTrack.qml +36 -0
- package/ui/MediaVolume.qml +54 -0
- package/ui/MenuActionDialog.qml +396 -0
- package/ui/MenuEdgeFade.qml +24 -0
- package/ui/MenuHost.qml +227 -0
- package/ui/MenuRow.qml +351 -0
- package/ui/MountListing.qml +100 -0
- package/ui/NetworkDialog.qml +435 -0
- package/ui/NetworkForm.qml +426 -0
- package/ui/NetworkMounts.qml +913 -0
- package/ui/NetworkPlaces.qml +127 -0
- package/ui/OpenWithDialog.qml +613 -0
- package/ui/Opener.qml +115 -0
- package/ui/Pane.qml +1296 -0
- package/ui/PaneMenuActions.qml +527 -0
- package/ui/PanePath.qml +90 -0
- package/ui/PaneRail.qml +239 -0
- package/ui/PaneStates.qml +88 -0
- package/ui/PaneSwap.qml +339 -0
- package/ui/PaneWire.qml +605 -0
- package/ui/ParkedWait.qml +9 -0
- package/ui/PathEntry.qml +186 -0
- package/ui/PathJump.qml +400 -0
- package/ui/PdfViewer.qml +297 -0
- package/ui/PermissionsDialog.qml +658 -0
- package/ui/PermissionsHeadings.qml +31 -0
- package/ui/PhoneMounts.qml +67 -0
- package/ui/PickerAnswers.qml +75 -0
- package/ui/PickerBox.qml +26 -0
- package/ui/PickerChrome.qml +295 -0
- package/ui/PickerFilter.qml +97 -0
- package/ui/PickerGrid.qml +308 -0
- package/ui/PickerHeader.qml +28 -0
- package/ui/PickerLifecycle.qml +27 -0
- package/ui/PickerList.qml +218 -0
- package/ui/PickerListing.qml +149 -0
- package/ui/PickerPathBar.qml +127 -0
- package/ui/PickerPlaces.qml +189 -0
- package/ui/PickerRecent.qml +92 -0
- package/ui/PickerSave.qml +101 -0
- package/ui/PickerSelection.qml +155 -0
- package/ui/PickerWindow.qml +846 -0
- package/ui/PowerSectorsReader.qml +10 -0
- package/ui/PrebuildDriver.qml +146 -0
- package/ui/Preview.qml +1004 -0
- package/ui/PreviewAnimation.qml +24 -0
- package/ui/PreviewArchive.qml +66 -0
- package/ui/PreviewColumn.qml +551 -0
- package/ui/PreviewEdge.qml +63 -0
- package/ui/PreviewFacts.qml +30 -0
- package/ui/PreviewFactsLine.qml +34 -0
- package/ui/PreviewHtml.qml +233 -0
- package/ui/PreviewImage.qml +253 -0
- package/ui/PreviewImagePan.qml +13 -0
- package/ui/PreviewImageSharp.qml +32 -0
- package/ui/PreviewLines.qml +106 -0
- package/ui/PreviewMarkdown.qml +706 -0
- package/ui/PreviewMedia.qml +250 -0
- package/ui/PreviewPdf.qml +482 -0
- package/ui/PreviewScroll.qml +201 -0
- package/ui/PreviewSwap.qml +335 -0
- package/ui/PreviewText.qml +149 -0
- package/ui/ProtocolChip.qml +18 -0
- package/ui/QuickLookArchive.qml +38 -0
- package/ui/QuickLookBar.qml +103 -0
- package/ui/QuickLookPrepare.qml +355 -0
- package/ui/QuickLookSwap.qml +10 -0
- package/ui/QuickLookWarm.qml +65 -0
- package/ui/RailDrop.qml +155 -0
- package/ui/RailHeading.qml +19 -0
- package/ui/RailPlaces.qml +38 -0
- package/ui/RailTrashRow.qml +67 -0
- package/ui/RenameField.qml +353 -0
- package/ui/RendererRetry.qml +11 -0
- package/ui/ResizeHandle.qml +52 -0
- package/ui/Reveal.qml +54 -0
- package/ui/Row.qml +476 -0
- package/ui/RowDrag.qml +213 -0
- package/ui/RowDropTarget.qml +27 -0
- package/ui/RunCard.qml +286 -0
- package/ui/Scripts.qml +65 -0
- package/ui/SearchStrip.qml +232 -0
- package/ui/SelectionBand.qml +140 -0
- package/ui/SelectionPreview.qml +370 -0
- package/ui/SettingsFavourite.qml +122 -0
- package/ui/SettingsGroup.qml +96 -0
- package/ui/SettingsHint.qml +12 -0
- package/ui/SettingsMetrics.qml +29 -0
- package/ui/SettingsPane.qml +178 -0
- package/ui/SettingsPanel.qml +547 -0
- package/ui/SettingsRail.qml +128 -0
- package/ui/SettingsRow.qml +409 -0
- package/ui/SettingsRuler.qml +85 -0
- package/ui/SettingsSegment.qml +85 -0
- package/ui/ShareBrowser.qml +113 -0
- package/ui/ShareLink.qml +67 -0
- package/ui/ShelfPins.qml +101 -0
- package/ui/Sidebar.qml +732 -0
- package/ui/SidebarRow.qml +358 -0
- package/ui/Spinner.qml +60 -0
- package/ui/StableModel.qml +89 -0
- package/ui/StateMessage.qml +84 -0
- package/ui/StatusBar.qml +512 -0
- package/ui/StatusFileName.qml +27 -0
- package/ui/TabBar.qml +673 -0
- package/ui/TabDragGeometry.qml +60 -0
- package/ui/TagMenuMark.qml +52 -0
- package/ui/TagRailSection.qml +53 -0
- package/ui/TagSlot.qml +40 -0
- package/ui/TagState.qml +150 -0
- package/ui/TagSwatch.qml +28 -0
- package/ui/Taildrop.qml +86 -0
- package/ui/TailscaleMark.qml +50 -0
- package/ui/Theme.qml +460 -0
- package/ui/ThemeColors.qml +43 -0
- package/ui/TransferCard.qml +239 -0
- package/ui/TrashConfirm.qml +129 -0
- package/ui/TrashDropHandler.qml +11 -0
- package/ui/TrashHost.qml +202 -0
- package/ui/TrashMonitor.qml +30 -0
- package/ui/TrashToolbar.qml +19 -0
- package/ui/TrashView.qml +492 -0
- package/ui/UnlockDialog.qml +450 -0
- package/ui/UnlockHost.qml +206 -0
- package/ui/UpdateCheck.qml +97 -0
- package/ui/UserDirs.qml +25 -0
- package/ui/ViewState.qml +565 -0
- package/ui/ViewportScrollBar.qml +195 -0
- package/ui/ViewportScrollBars.qml +25 -0
- package/ui/WebPreview.qml +33 -0
- package/ui/WebPreviewView.qml +26 -0
- package/ui/WindowBody.qml +827 -0
- package/ui/WindowDrag.qml +35 -0
- package/ui/boot/Commons/AudioNodes.qml +50 -0
- package/ui/boot/Commons/AudioNodesModel.js +96 -0
- package/ui/boot/Commons/Border.qml +243 -0
- package/ui/boot/Commons/BorderGeometry.js +373 -0
- package/ui/boot/Commons/Color.qml +258 -0
- package/ui/boot/Commons/I18n.qml +118 -0
- package/ui/boot/Commons/InputDevices.qml +112 -0
- package/ui/boot/Commons/InputMethodState.qml +38 -0
- package/ui/boot/Commons/IpcRegistry.qml +77 -0
- package/ui/boot/Commons/Numbers.js +19 -0
- package/ui/boot/Commons/Plural.js +91 -0
- package/ui/boot/Commons/ShellIpc.qml +12 -0
- package/ui/boot/Commons/Style.qml +561 -0
- package/ui/boot/Commons/Translations.qml +100 -0
- package/ui/boot/Commons/UntypedInput.qml +152 -0
- package/ui/boot/Commons/Util.qml +188 -0
- package/ui/boot/Commons/qmldir +13 -0
- package/ui/boot/LICENSE-omarchy +20 -0
- package/ui/boot/Ui/BackgroundMedia.qml +49 -0
- package/ui/boot/Ui/BarIconButton.qml +106 -0
- package/ui/boot/Ui/BarIconContent.qml +38 -0
- package/ui/boot/Ui/BarIndicator.qml +53 -0
- package/ui/boot/Ui/BarWidget.qml +50 -0
- package/ui/boot/Ui/BorderOverlay.qml +54 -0
- package/ui/boot/Ui/BorderSurface.qml +40 -0
- package/ui/boot/Ui/Button.qml +224 -0
- package/ui/boot/Ui/ButtonGroup.qml +131 -0
- package/ui/boot/Ui/ConfirmDialog.qml +134 -0
- package/ui/boot/Ui/CursorSurface.qml +42 -0
- package/ui/boot/Ui/Dropdown.qml +245 -0
- package/ui/boot/Ui/FilterInput.qml +26 -0
- package/ui/boot/Ui/IconContentBounds.qml +141 -0
- package/ui/boot/Ui/KeyboardPanel.qml +453 -0
- package/ui/boot/Ui/MultiSelect.qml +624 -0
- package/ui/boot/Ui/NumberField.qml +86 -0
- package/ui/boot/Ui/OpticalGlyph.qml +68 -0
- package/ui/boot/Ui/OverlayWindow.qml +102 -0
- package/ui/boot/Ui/Panel.qml +60 -0
- package/ui/boot/Ui/PanelActionButton.qml +105 -0
- package/ui/boot/Ui/PanelController.qml +16 -0
- package/ui/boot/Ui/PanelHero.qml +112 -0
- package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
- package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
- package/ui/boot/Ui/PanelSeparator.qml +19 -0
- package/ui/boot/Ui/PanelSlider.qml +156 -0
- package/ui/boot/Ui/PanelToolTip.qml +50 -0
- package/ui/boot/Ui/PluginBarApi.qml +89 -0
- package/ui/boot/Ui/PointerMoveGate.qml +54 -0
- package/ui/boot/Ui/PopupCard.qml +181 -0
- package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
- package/ui/boot/Ui/SearchableDropdown.qml +354 -0
- package/ui/boot/Ui/SizedImage.qml +125 -0
- package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
- package/ui/boot/Ui/TextField.qml +58 -0
- package/ui/boot/Ui/Toggle.qml +118 -0
- package/ui/boot/Ui/ToggleSwitch.qml +115 -0
- package/ui/boot/Ui/WidgetButton.qml +172 -0
- package/ui/boot/Ui/qmldir +41 -0
- package/ui/boot/fleatab.qml +50 -0
- package/ui/boot/picker.qml +39 -0
- package/ui/boot/shell.qml +122 -0
- package/ui/boot/tabtearoff.qml +51 -0
- package/ui/js/Anchor.js +379 -0
- package/ui/js/AnchorHold.js +162 -0
- package/ui/js/Archive.js +157 -0
- package/ui/js/ArchiveCard.js +44 -0
- package/ui/js/ArchiveOps.js +44 -0
- package/ui/js/BackReturn.js +118 -0
- package/ui/js/BulkRename.js +100 -0
- package/ui/js/Buttons.js +54 -0
- package/ui/js/ChromeKeys.js +116 -0
- package/ui/js/ClipMarks.js +73 -0
- package/ui/js/ClipSelection.js +67 -0
- package/ui/js/Clipboard.js +179 -0
- package/ui/js/Cloud.js +97 -0
- package/ui/js/CodeRuns.js +72 -0
- package/ui/js/Collide.js +212 -0
- package/ui/js/ColumnFit.js +28 -0
- package/ui/js/ColumnMenu.js +111 -0
- package/ui/js/Columns.js +298 -0
- package/ui/js/ColumnsCarry.js +103 -0
- package/ui/js/Compare.js +74 -0
- package/ui/js/Contrast.js +157 -0
- package/ui/js/ControlReader.js +42 -0
- package/ui/js/Convert.js +52 -0
- package/ui/js/CopyAs.js +52 -0
- package/ui/js/Crumbs.js +178 -0
- package/ui/js/Csv.js +231 -0
- package/ui/js/Density.js +44 -0
- package/ui/js/Devices.js +440 -0
- package/ui/js/DirSizes.js +180 -0
- package/ui/js/Drag.js +364 -0
- package/ui/js/DragOut.js +166 -0
- package/ui/js/DropDev.js +52 -0
- package/ui/js/Dropbox.js +41 -0
- package/ui/js/Eject.js +217 -0
- package/ui/js/Errors.js +188 -0
- package/ui/js/ExtThumbs.js +115 -0
- package/ui/js/Facts.js +402 -0
- package/ui/js/FigureDate.js +14 -0
- package/ui/js/Filter.js +285 -0
- package/ui/js/Find.js +46 -0
- package/ui/js/Flash.js +184 -0
- package/ui/js/FlashCard.js +79 -0
- package/ui/js/FlashEndings.js +125 -0
- package/ui/js/FlashPane.js +116 -0
- package/ui/js/FleaBin.js +12 -0
- package/ui/js/Focus.js +502 -0
- package/ui/js/FolderSorts.js +108 -0
- package/ui/js/FolderViews.js +44 -0
- package/ui/js/Format.js +305 -0
- package/ui/js/Fuzzy.js +141 -0
- package/ui/js/GlassTag.js +72 -0
- package/ui/js/Grid.js +33 -0
- package/ui/js/GridGeometry.js +90 -0
- package/ui/js/GridNames.js +153 -0
- package/ui/js/GvfsBridge.js +200 -0
- package/ui/js/HostClipboard.js +29 -0
- package/ui/js/Icons.js +218 -0
- package/ui/js/Input.js +50 -0
- package/ui/js/Jump.js +200 -0
- package/ui/js/KeyName.js +64 -0
- package/ui/js/Keymap.js +401 -0
- package/ui/js/Kinds.js +164 -0
- package/ui/js/LICENSES/ISC-lucide.txt +43 -0
- package/ui/js/LangMarkPaths.js +47 -0
- package/ui/js/LangMarks.js +120 -0
- package/ui/js/Lists.js +11 -0
- package/ui/js/LocalSend.js +39 -0
- package/ui/js/LockedMenu.js +63 -0
- package/ui/js/MakeDefault.js +186 -0
- package/ui/js/MarkBox.js +7 -0
- package/ui/js/Markdown.js +119 -0
- package/ui/js/MarkdownLists.js +86 -0
- package/ui/js/MarkdownMaths.js +58 -0
- package/ui/js/MarkdownPictures.js +32 -0
- package/ui/js/MarkdownPrepared.js +139 -0
- package/ui/js/MarkdownTableFit.js +34 -0
- package/ui/js/Marks.js +148 -0
- package/ui/js/Marquee.js +96 -0
- package/ui/js/Match.js +93 -0
- package/ui/js/Material.js +109 -0
- package/ui/js/MdBlocks.js +437 -0
- package/ui/js/MdBreak.js +71 -0
- package/ui/js/MdChunks.js +70 -0
- package/ui/js/MdContainer.js +137 -0
- package/ui/js/MdDocument.js +337 -0
- package/ui/js/MdEmph.js +97 -0
- package/ui/js/MdEntity.js +108 -0
- package/ui/js/MdEntityTable.js +223 -0
- package/ui/js/MdEscape.js +59 -0
- package/ui/js/MdFront.js +37 -0
- package/ui/js/MdHold.js +32 -0
- package/ui/js/MdHtml.js +307 -0
- package/ui/js/MdHtmlBlock.js +290 -0
- package/ui/js/MdHtmlHeading.js +53 -0
- package/ui/js/MdHtmlImage.js +179 -0
- package/ui/js/MdInline.js +154 -0
- package/ui/js/MdItems.js +212 -0
- package/ui/js/MdLeaf.js +309 -0
- package/ui/js/MdLink.js +196 -0
- package/ui/js/MdMath.js +67 -0
- package/ui/js/MdParagraphs.js +138 -0
- package/ui/js/MdRefs.js +232 -0
- package/ui/js/MdResolve.js +226 -0
- package/ui/js/MdRun.js +300 -0
- package/ui/js/MdUrl.js +226 -0
- package/ui/js/Menu.js +514 -0
- package/ui/js/MenuContext.js +85 -0
- package/ui/js/MenuFit.js +13 -0
- package/ui/js/MenuRefresh.js +74 -0
- package/ui/js/MenuWheel.js +35 -0
- package/ui/js/Messages.js +186 -0
- package/ui/js/Motion.js +16 -0
- package/ui/js/Mounts.js +320 -0
- package/ui/js/MouseNav.js +13 -0
- package/ui/js/Nav.js +329 -0
- package/ui/js/NetFs.js +26 -0
- package/ui/js/OpenWith.js +83 -0
- package/ui/js/Ops.js +585 -0
- package/ui/js/Palette.js +33 -0
- package/ui/js/PathBar.js +260 -0
- package/ui/js/Permissions.js +179 -0
- package/ui/js/Phones.js +112 -0
- package/ui/js/Picker.js +353 -0
- package/ui/js/PlaceMenu.js +96 -0
- package/ui/js/Places.js +265 -0
- package/ui/js/PreviewAsk.js +21 -0
- package/ui/js/PreviewKeys.js +155 -0
- package/ui/js/PreviewSettle.js +29 -0
- package/ui/js/PreviewSwap.js +101 -0
- package/ui/js/PropertyFacts.js +144 -0
- package/ui/js/Protocols.js +296 -0
- package/ui/js/QuickLookKinds.js +27 -0
- package/ui/js/QuickLookRect.js +24 -0
- package/ui/js/RailDrop.js +51 -0
- package/ui/js/RailKeys.js +87 -0
- package/ui/js/RailMenu.js +133 -0
- package/ui/js/Recent.js +135 -0
- package/ui/js/RecentMode.js +156 -0
- package/ui/js/Reload.js +60 -0
- package/ui/js/Renderer.js +9 -0
- package/ui/js/RunFile.js +143 -0
- package/ui/js/Scripts.js +28 -0
- package/ui/js/Scroll.js +304 -0
- package/ui/js/ScrollOff.js +69 -0
- package/ui/js/Search.js +478 -0
- package/ui/js/Selection.js +97 -0
- package/ui/js/Settings.js +476 -0
- package/ui/js/SettingsAbout.js +97 -0
- package/ui/js/SettingsShelf.js +52 -0
- package/ui/js/SheetKeys.js +42 -0
- package/ui/js/SheetQuery.js +281 -0
- package/ui/js/ShelfPile.js +27 -0
- package/ui/js/SlowClick.js +53 -0
- package/ui/js/SlowOp.js +6 -0
- package/ui/js/Sort.js +134 -0
- package/ui/js/Startup.js +63 -0
- package/ui/js/StateWords.js +17 -0
- package/ui/js/Status.js +238 -0
- package/ui/js/Swap.js +166 -0
- package/ui/js/TabMove.js +39 -0
- package/ui/js/Tabs.js +736 -0
- package/ui/js/Tags.js +134 -0
- package/ui/js/Taildrop.js +128 -0
- package/ui/js/Tap.js +174 -0
- package/ui/js/TextSize.js +107 -0
- package/ui/js/Thumbs.js +158 -0
- package/ui/js/Transfer.js +172 -0
- package/ui/js/Trash.js +81 -0
- package/ui/js/TrashDates.js +47 -0
- package/ui/js/TrashDrop.js +64 -0
- package/ui/js/UiState.js +438 -0
- package/ui/js/Unlock.js +152 -0
- package/ui/js/Update.js +130 -0
- package/ui/js/UserDirs.js +56 -0
- package/ui/qmldir +228 -0
package/ui/Pane.qml
ADDED
|
@@ -0,0 +1,1296 @@
|
|
|
1
|
+
import QtQuick
|
|
2
|
+
import Quickshell
|
|
3
|
+
import "." as Flea
|
|
4
|
+
import "js/DirSizes.js" as DirSizes
|
|
5
|
+
import "js/Dropbox.js" as Dropbox
|
|
6
|
+
import "js/ExtThumbs.js" as ExtThumbs
|
|
7
|
+
import "js/Filter.js" as Filter
|
|
8
|
+
import "js/Format.js" as Format
|
|
9
|
+
import "js/Focus.js" as Focus
|
|
10
|
+
import "js/Marks.js" as Marks
|
|
11
|
+
import "js/Menu.js" as Menu
|
|
12
|
+
import "js/Mounts.js" as Mounts
|
|
13
|
+
import "js/Search.js" as Search
|
|
14
|
+
import "js/Archive.js" as Archive
|
|
15
|
+
import "js/DragOut.js" as DragOut
|
|
16
|
+
import "js/Startup.js" as Startup
|
|
17
|
+
import "js/Nav.js" as Nav
|
|
18
|
+
import "js/RecentMode.js" as RecentMode
|
|
19
|
+
import "js/Ops.js" as Ops
|
|
20
|
+
import "js/Clipboard.js" as Clipboard
|
|
21
|
+
import "js/Permissions.js" as Permissions
|
|
22
|
+
import "js/Selection.js" as Selection
|
|
23
|
+
import "js/Anchor.js" as Anchor
|
|
24
|
+
import "js/SlowClick.js" as SlowClick
|
|
25
|
+
import "js/Sort.js" as Sort
|
|
26
|
+
import "js/Tabs.js" as Tabs
|
|
27
|
+
import "js/Thumbs.js" as Thumbs
|
|
28
|
+
import "js/FolderViews.js" as FolderViews
|
|
29
|
+
|
|
30
|
+
FocusScope {
|
|
31
|
+
id: root
|
|
32
|
+
focus: true
|
|
33
|
+
// Unhandled child keys reach the pane, including keys from an actually focused rail.
|
|
34
|
+
Keys.onPressed: function (event) { DragOut.keys.track(event, true); event.accepted = Focus.handleKey(event, root.sidebar && root.sidebar.activeFocus ? root.railPane : root, root.sidebar) }
|
|
35
|
+
Keys.onReleased: function (event) { DragOut.keys.track(event, false) }
|
|
36
|
+
enabled: !root.settingsPanel || !root.settingsPanel.opened
|
|
37
|
+
property var backend: null
|
|
38
|
+
property string path: ""
|
|
39
|
+
// Set once by shell.qml from FLEA_SELECT; applied to the first `rows` this pane receives, then forgotten.
|
|
40
|
+
property string pendingSelect: ""
|
|
41
|
+
// Set with pendingSelect by a right click on a peeked column row: the menu opens on the row once it is the cursor.
|
|
42
|
+
property bool pendingMenu: false
|
|
43
|
+
// Set by a right click on a neighbour column's empty space: the drawn directory opens, then its background menu opens at the stored scene point once the rows land.
|
|
44
|
+
property string pendingBackground: ""
|
|
45
|
+
property var pendingBackgroundAt: null
|
|
46
|
+
// The directory the listing in flight asked for, which is not pane.path until the reply lands.
|
|
47
|
+
property string listingPath: ""
|
|
48
|
+
property int total: 0
|
|
49
|
+
property int cursorIndex: 0
|
|
50
|
+
// Filter and Marks bump this on every cursor move, ending a shift gesture.
|
|
51
|
+
property int cursorSeq: 0
|
|
52
|
+
// The wheel's last pull of the cursor, which ui/js/Filter.js pulledFrom reads for a Shift range's start.
|
|
53
|
+
property var cursorPulled: null
|
|
54
|
+
property string listingState: "loading"
|
|
55
|
+
property string stateMessage: ""
|
|
56
|
+
property int lockedMode: 0
|
|
57
|
+
// The folder the Locked tile names while one is drawn, "" otherwise; ui/js/Nav.js owns it,
|
|
58
|
+
// and the tile's right click and the m key open that folder's own menu through it.
|
|
59
|
+
readonly property string lockedTarget: Nav.lockedTarget(root)
|
|
60
|
+
// Off by default: dotfiles stay out of every listing until the context menu or "." turns them on.
|
|
61
|
+
property bool showHidden: ViewState.state.hidden === true
|
|
62
|
+
// A zip's one image member, which Flash to drive needs named before it shows on a zip.
|
|
63
|
+
property string flashZipMember: ""
|
|
64
|
+
// The asked zip with its size and mtime, so a late answer for an earlier file never arms a later one.
|
|
65
|
+
property string flashZipAsked: ""
|
|
66
|
+
onCursorIndexChanged: Ops.checkZip(root)
|
|
67
|
+
onSelectionVersionChanged: Ops.checkZip(root)
|
|
68
|
+
onPathChanged: Ops.checkZip(root)
|
|
69
|
+
onBackendChanged: Ops.checkZip(root)
|
|
70
|
+
// Issue 27's state-file key: with it on a cursor step past an end comes round; ui/js/Focus.js step is the only reader.
|
|
71
|
+
readonly property bool wrapAtEnds: ViewState.state.wrapAtEnds === true
|
|
72
|
+
// ClickAndRefresh under Settings, View: Escape climbs, one tap opens, and the slow click renames.
|
|
73
|
+
readonly property bool escapeUp: ViewState.state.escapeUp === true
|
|
74
|
+
readonly property bool singleClick: ViewState.state.openMode === "single"
|
|
75
|
+
readonly property bool clickRename: ViewState.state.clickRename !== false
|
|
76
|
+
// A manual reload's own count, or -1; ui/js/Reload.js sets it and ui/PaneSwap.qml spends it.
|
|
77
|
+
property int reloadFrom: -1
|
|
78
|
+
// Added plus removed rows on the reload's listed line, or -1; ui/PaneSwap.qml sets it from changed.
|
|
79
|
+
property int reloadChanged: -1
|
|
80
|
+
// The slow click's own window: the last tap's row and time, read by ui/js/SlowClick.js alone.
|
|
81
|
+
property double slowClickAt: 0
|
|
82
|
+
property int slowClickIndex: SlowClick.CLEARED
|
|
83
|
+
// One slow-click timer for the whole pane; a tap arms it and a second tap in time opens as a double click does, and its firing renames only while the row still holds the cursor.
|
|
84
|
+
Timer {
|
|
85
|
+
id: slowClickTimer
|
|
86
|
+
interval: Qt.styleHints.mouseDoubleClickInterval
|
|
87
|
+
repeat: false
|
|
88
|
+
onTriggered: SlowClick.fire(root)
|
|
89
|
+
}
|
|
90
|
+
function armSlowClick(index, modifiers, dragging, wasSole) {
|
|
91
|
+
if (SlowClick.arm(root, index, modifiers, Date.now(), Qt.styleHints.mouseDoubleClickInterval, dragging, wasSole))
|
|
92
|
+
slowClickTimer.restart()
|
|
93
|
+
else slowClickTimer.stop()
|
|
94
|
+
}
|
|
95
|
+
function slowClickWasSole(index) { return SlowClick.wasSoleSelection(root, index) }
|
|
96
|
+
// A press stops the timer without clearing the tap record, so a hold past the interval cannot fire while down;
|
|
97
|
+
// the release still arms through armSlowClick, and a drag clears fully at lift.
|
|
98
|
+
function pressSlowClick() { slowClickTimer.stop() }
|
|
99
|
+
function cancelSlowClick() { slowClickTimer.stop(); SlowClick.cancel(root) }
|
|
100
|
+
// ui/js/Tabs.js is a .pragma library and cannot reach a QML singleton, so the state it asks
|
|
101
|
+
// ui/js/Startup.js about rides in through the pane, the way every other setting it reads does.
|
|
102
|
+
readonly property var uiState: ViewState.state
|
|
103
|
+
// When the first d of the dd pair landed; ui/js/Focus.js reads it and Nav's reset clears it.
|
|
104
|
+
property double trashArmedAt: 0
|
|
105
|
+
// The first row a trash request went out with, or -1: what the block left, where the cursor lands.
|
|
106
|
+
property int trashedFirst: -1
|
|
107
|
+
property string keySequence: ""
|
|
108
|
+
property string keySequenceIdentity: ""
|
|
109
|
+
// "" off, "typing" while the query line has the keyboard, "results" once Enter keeps them; ui/js/Search.js owns every transition.
|
|
110
|
+
property string searchMode: ""
|
|
111
|
+
// An exit owed its landing: the backend closed at the request and the drawn state clears with the rows.
|
|
112
|
+
property bool searchLeaving: false
|
|
113
|
+
// Search origin survives a home-wide walk.
|
|
114
|
+
property string searchFrom: ""
|
|
115
|
+
property string searchQuery: ""
|
|
116
|
+
// flea-qml: the query Find last ran, and when Enter last ran one (for the Enter debounce).
|
|
117
|
+
property string searchSent: ""
|
|
118
|
+
property real searchEnterAt: 0
|
|
119
|
+
// Issue 30: which scope the next walk takes, flipped by tab on the query line; see ui/js/Search.js.
|
|
120
|
+
// flea-qml: the toolbar's two toggles. Off, each opens as its key does; on, Find stops its run and
|
|
121
|
+
// leaves, Filter clears and closes. Neither touches the other.
|
|
122
|
+
function toggleFind() {
|
|
123
|
+
if (root.searchMode.length > 0) Search.dismiss(root)
|
|
124
|
+
else root.act("search")
|
|
125
|
+
}
|
|
126
|
+
function toggleFilter() {
|
|
127
|
+
if (root.filterTyping || root.filterQuery.length > 0) Filter.close(root)
|
|
128
|
+
else root.act("filter")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// flea-qml: Find's scope, Search.HERE, HOME or ROOT; it starts in the folder and Tab turns it.
|
|
132
|
+
property int searchStep: 0
|
|
133
|
+
// flea-qml: Ctrl+G's content search: Find runs grep over the scope instead of find.
|
|
134
|
+
property bool searchContents: false
|
|
135
|
+
property bool searchRunning: false
|
|
136
|
+
property bool searchCancelled: false
|
|
137
|
+
// Newest sent query generation, rising across sessions; answers for an older one are dropped.
|
|
138
|
+
property int searchGen: 0
|
|
139
|
+
// The gen a session opened at; a line at or below it is from a closed session.
|
|
140
|
+
property int searchOpenedGen: 0
|
|
141
|
+
// Stopwatch for the newest gen: keyAt at the send, firstRowsAt at its first rows, settledAt at its landed searched; a stale gen sets none.
|
|
142
|
+
property real searchKeyAt: 0
|
|
143
|
+
property real searchFirstRowsAt: 0
|
|
144
|
+
property real searchSettledAt: 0
|
|
145
|
+
// The listing's own base: the scope for a live answer, the folder for an empty one.
|
|
146
|
+
property string searchBase: ""
|
|
147
|
+
// Recent place: "" off, "results" once answered; RecentMode.js owns transitions; pane path is history base.
|
|
148
|
+
property string recentMode: ""
|
|
149
|
+
// Where Recent was opened over, and the listing it hands back on leaving.
|
|
150
|
+
property string recentFrom: ""
|
|
151
|
+
// The history paths the listing stands on, so a refresh with the rail hidden re-asks them.
|
|
152
|
+
property var recentPaths: []
|
|
153
|
+
property var recentVisits: ({})
|
|
154
|
+
// The sort Recent replaced, handed back on leaving; ui/js/RecentMode.js run and restoreSort write it.
|
|
155
|
+
property string recentSortBy: ""
|
|
156
|
+
property bool recentSortDesc: false
|
|
157
|
+
// Filter.js owns the standing query and its typing state.
|
|
158
|
+
property string filterQuery: ""
|
|
159
|
+
property bool filterTyping: false
|
|
160
|
+
// The filter stopwatch Ipc's filterStamps reads: the newest keystroke and when it narrowed the shown model.
|
|
161
|
+
property real filterKeyAt: 0
|
|
162
|
+
property real filterNarrowedAt: 0
|
|
163
|
+
property int searchScanned: 0
|
|
164
|
+
property real searchMs: 0
|
|
165
|
+
property bool listInFlight: false
|
|
166
|
+
property bool listedSeen: false
|
|
167
|
+
readonly property bool menuVisible: menu.opened
|
|
168
|
+
// The keyboard-highlighted menu row, so a test can drive the menu without OCR.
|
|
169
|
+
readonly property int menuCursor: menu.cursor
|
|
170
|
+
// What the header case reads over IPC, the same alias idiom Row.qml uses for its icon.
|
|
171
|
+
readonly property alias header: header
|
|
172
|
+
property var sharedSidebar: null
|
|
173
|
+
property var railPane: root
|
|
174
|
+
// The window's chrome bar and tab strip, the two Tab stops after the rail; ui/js/ChromeKeys.js reads them.
|
|
175
|
+
property var chromeBar: null
|
|
176
|
+
property var tabStrip: null
|
|
177
|
+
// The window-long network host, injected by ui/WindowBody.qml (the primary pane's own,
|
|
178
|
+
// shared by the second pane): mounts, bridge waits and dialog answers outlive the rail.
|
|
179
|
+
property var sharedNetworkService: null
|
|
180
|
+
property var networkService: null
|
|
181
|
+
readonly property var pathAlias: root.networkService ? root.networkService.pathAlias(root.path) : null
|
|
182
|
+
// Answers the one network host ui/WindowBody.qml builds with the window.
|
|
183
|
+
function ensureNetworkService() {
|
|
184
|
+
if (root.networkService) return root.networkService
|
|
185
|
+
if (root.sharedNetworkService) { root.networkService = root.sharedNetworkService; return root.networkService }
|
|
186
|
+
if (root.overlayParent) root.networkService = root.overlayParent.ensureNetworkService()
|
|
187
|
+
return root.networkService
|
|
188
|
+
}
|
|
189
|
+
readonly property var sidebar: root.sharedSidebar || railHost.item
|
|
190
|
+
readonly property real sidebarWidth: railHost.railWidth
|
|
191
|
+
// RailAdditions rule 4 and directive 77, both answered in ui/PaneRail.qml.
|
|
192
|
+
readonly property bool railHidden: railHost.hidden
|
|
193
|
+
// What the rail takes from the pane, which an overlay never does; the sidebar case reads it.
|
|
194
|
+
readonly property real railInset: railHost.inset
|
|
195
|
+
readonly property bool railRevealed: railHost.revealed
|
|
196
|
+
readonly property real railX: railHost.railX
|
|
197
|
+
// Directive 77: a withdrawn rail is still a place Tab can go, because arriving there reveals it.
|
|
198
|
+
readonly property bool railAvailable: root.sidebar !== null || railHost.overlay
|
|
199
|
+
function toggleRail() { ViewState.toggleRail() }
|
|
200
|
+
// Ctrl+E with the rail hidden: the Sidebar is unloaded, so the key cannot read entries or
|
|
201
|
+
// release through it; PaneRail spins a transient DeviceMounts for one listing instead.
|
|
202
|
+
function ejectHidden() { railHost.ejectHidden() }
|
|
203
|
+
// Hiding a focused rail strands the keyboard on a view that no longer exists, so the listing takes it.
|
|
204
|
+
onRailHiddenChanged: if (root.railHidden) Focus.railHidden(root)
|
|
205
|
+
property bool paneFocused: true
|
|
206
|
+
property bool listOnly: false
|
|
207
|
+
signal focusRequested()
|
|
208
|
+
// A list-only pane draws no view of its own, so a view chosen on it goes to the window, which routes it as its chrome does.
|
|
209
|
+
signal viewRequested(string mode)
|
|
210
|
+
// Which view Tab last handed the keyboard to; the rail's own cursor is read the same way.
|
|
211
|
+
property string focusView: Focus.LIST
|
|
212
|
+
readonly property int railCursor: root.sidebar ? root.sidebar.cursorIndex : 0
|
|
213
|
+
// So a test can wait for the rail's async FileViews instead of sleeping and guessing.
|
|
214
|
+
readonly property int railCount: root.sidebar ? root.sidebar.entries.length : 0
|
|
215
|
+
property var preview: null
|
|
216
|
+
// WindowBody's Quick Look loader; quickLook() builds the overlay on the first open and answers it.
|
|
217
|
+
property var previewLoader: null
|
|
218
|
+
function quickLook() {
|
|
219
|
+
if (root.previewLoader)
|
|
220
|
+
root.previewLoader.active = true
|
|
221
|
+
return root.preview
|
|
222
|
+
}
|
|
223
|
+
property var statusBar: null
|
|
224
|
+
// shell.qml's ui/ShareBrowser.qml overlay, wired the same way as preview above.
|
|
225
|
+
property var shareBrowser: null
|
|
226
|
+
// shell.qml's ui/KeymapSheet.qml, which ? opens from either the list or the rail.
|
|
227
|
+
property var keymapSheet: null
|
|
228
|
+
// shell.qml's ui/SettingsPanel.qml, which the comma key opens from the list and the rail alike, see act() below.
|
|
229
|
+
property var settingsPanel: null
|
|
230
|
+
property Item overlayParent: null
|
|
231
|
+
readonly property alias trash: trashHost
|
|
232
|
+
// Set by the window's flash host while it covers this pane; every gate below reads it.
|
|
233
|
+
property bool flashActive: false
|
|
234
|
+
readonly property alias menuActions: menuActions
|
|
235
|
+
readonly property alias menuHost: menu
|
|
236
|
+
readonly property var emptyState: paneStates.emptyItem
|
|
237
|
+
readonly property var stateMessageItem: paneStates.messageItem
|
|
238
|
+
readonly property alias retrySelectionText: wire.retrySelectionText
|
|
239
|
+
// The listing swap, ui/PaneSwap.qml: ui/js/Nav.js starts a hold through it and the views read holding.
|
|
240
|
+
readonly property alias swap: wire.swap
|
|
241
|
+
readonly property alias wire: wire
|
|
242
|
+
// The rows serialize on the first read after they change, never inside the landing turn; rows are only replaced
|
|
243
|
+
// whole, so the array the text came from tells a stale text whatever order the handlers run in.
|
|
244
|
+
readonly property var rowsIdentity: ({ rows: null, text: "" })
|
|
245
|
+
onRowsChanged: { root.rowsIdentity.rows = null; root.rowsIdentity.text = ""; Ops.checkZip(root) }
|
|
246
|
+
// Notifies whenever an input of menuSelectionIdentity() does, and serializes nothing.
|
|
247
|
+
readonly property var menuSelectionInputs: [root.path, root.held, root.selectionVersion, root.cursorIndex,
|
|
248
|
+
root.total, root.listInFlight, root.rows]
|
|
249
|
+
function menuSelectionIdentity() {
|
|
250
|
+
if (root.rowsIdentity.rows !== root.rows) {
|
|
251
|
+
root.rowsIdentity.rows = root.rows
|
|
252
|
+
// A tag write changes only a row's tg, which names no file, so a menu opened before it still names the same rows.
|
|
253
|
+
root.rowsIdentity.text = JSON.stringify(root.rows, function (key, value) { return key === "tg" ? undefined : value })
|
|
254
|
+
}
|
|
255
|
+
return JSON.stringify([root.path, root.held, root.selectionVersion, root.cursorIndex, root.total,
|
|
256
|
+
root.listInFlight]) + "\n" + root.rowsIdentity.text
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
signal opened(string path)
|
|
260
|
+
signal message(string text, bool isError)
|
|
261
|
+
// The dd prompt of an arm, said apart from message so the status bar never reads a sentence to find it.
|
|
262
|
+
signal armPrompted(string text)
|
|
263
|
+
signal forgetMessage(string text)
|
|
264
|
+
signal operationResult(string headline, string detail, bool isError)
|
|
265
|
+
// The status bar's sticky slot, which unlike message does not time out; empty clears it.
|
|
266
|
+
signal sticky(string text)
|
|
267
|
+
// The same slot for the GVFS bridge's Starting line, so the status bar lights its busy mark from the call and not from the sentence.
|
|
268
|
+
signal stickyStarting(string text)
|
|
269
|
+
// The one popup, hosted in shell.qml beside the network dialog rather than inside the pane.
|
|
270
|
+
signal convertRequested(string name)
|
|
271
|
+
// Archives040: the password compress resolves through the cards host, which Focus reaches
|
|
272
|
+
// through this signal so Focus takes no new first-frame import.
|
|
273
|
+
signal compressPasswordRequested(var paths, int menuId)
|
|
274
|
+
signal extractPasswordRequested(string path, string dest, string err)
|
|
275
|
+
signal permissionsRequested(string path)
|
|
276
|
+
// MenuAdditions040: the whole selection's paths for the multi-row card.
|
|
277
|
+
signal permissionsBatchRequested(var paths)
|
|
278
|
+
// Bulk rename takes the whole selection's paths for its card.
|
|
279
|
+
signal bulkRenameRequested(var paths)
|
|
280
|
+
signal pathBarRequested() // ":" and Ctrl+L; the bar is chrome, so shell.qml opens it as it does the popup above
|
|
281
|
+
signal textSizeRequested(int direction) // issue 9's zoom pair, +1, -1 or 0 to follow Omarchy again; the size is the window's
|
|
282
|
+
|
|
283
|
+
// The window covers [held, held + rows.length) and nothing outside it is in memory.
|
|
284
|
+
property int held: 0
|
|
285
|
+
property var rows: []
|
|
286
|
+
// The current response's Kind dictionary; every row's own "k" is an index into this one array.
|
|
287
|
+
property var kindNames: []
|
|
288
|
+
// The listing rows the filter leaves standing, null when none is up: a view position goes in and
|
|
289
|
+
// a listing row comes out, which is what every row-indexed cache and the selection still hold.
|
|
290
|
+
readonly property var shown: Filter.shown(root.rows, root.held, root.filterQuery)
|
|
291
|
+
readonly property int shownTotal: root.shown === null ? root.total : root.shown.length
|
|
292
|
+
|
|
293
|
+
readonly property int minBuffer: 50
|
|
294
|
+
readonly property int maxBuffer: 1000
|
|
295
|
+
readonly property int defaultBuffer: 150
|
|
296
|
+
property int bufferRows: defaultBuffer
|
|
297
|
+
readonly property int buffer: Math.max(minBuffer, Math.min(maxBuffer, bufferRows))
|
|
298
|
+
readonly property int visibleRows: Math.max(1, Math.ceil(list.height / Theme.fileRowHeight))
|
|
299
|
+
readonly property int windowSize: visibleRows + 2 * buffer
|
|
300
|
+
// The leading result rows a live answer may carry folder sizes for: none when the shown view would ask for none.
|
|
301
|
+
readonly property int sizeWish: DirSizes.wantsSizes(root.viewMode, ViewState.hiddenCols, root.storageClass, root.storageKnown) ? root.visibleRows : 0
|
|
302
|
+
readonly property int refetchMargin: 25
|
|
303
|
+
readonly property int cacheRows: 4
|
|
304
|
+
readonly property int coalesceMs: 16
|
|
305
|
+
// A settle, not a stream: a fling must issue no request at all, see AGENTS.md "Thumbnail requests in the GUI".
|
|
306
|
+
readonly property int settleMs: 120
|
|
307
|
+
// The first screen's settle only has to outlast the compositor's resize, see AGENTS.md "Thumbnail requests in the GUI".
|
|
308
|
+
readonly property int firstSettleMs: 70
|
|
309
|
+
// Seven screens of history at this row height, so a policy bug costs memory slowly, not without limit.
|
|
310
|
+
readonly property int thumbCap: 240
|
|
311
|
+
// Sizes are only ever what the user asked for, so the map holds a whole listing's folders.
|
|
312
|
+
readonly property int dirSizeCap: 1000000
|
|
313
|
+
property var thumbState: Thumbs.empty()
|
|
314
|
+
// The class verdict the last refresh spent, so a preview change that moves nothing for
|
|
315
|
+
// this directory (zoom step, other classes) refreshes nothing; see ui/js/ExtThumbs.js.
|
|
316
|
+
property string extVerdict: ExtThumbs.verdict("", ViewState.preview)
|
|
317
|
+
// Same cap as thumbState, same reason; a directory row's size is only ever asked for by a viewport.
|
|
318
|
+
property var dirSizeState: DirSizes.empty()
|
|
319
|
+
// The filesystem answers user extended attributes here, probed once per directory beside the fsinfo figures.
|
|
320
|
+
property bool canTag: true
|
|
321
|
+
// File tags (ui/TagState.qml) are built when a listing first carries one or a tag key or menu first asks.
|
|
322
|
+
property var tagState: null
|
|
323
|
+
function tags() {
|
|
324
|
+
if (root.tagState === null) root.tagState = Qt.createComponent("TagState.qml").createObject(root, { pane: root })
|
|
325
|
+
return root.tagState
|
|
326
|
+
}
|
|
327
|
+
// Input to rows is stamped inside the UI, because a harness that polls IPC across the interval times itself; see AGENTS.md "Testing".
|
|
328
|
+
property real inputAt: 0
|
|
329
|
+
property real rowsAt: 0
|
|
330
|
+
|
|
331
|
+
// The cut or copied paths, absolute because a paste lands in a different directory; see ui/js/Ops.js.
|
|
332
|
+
property var clipboard: Ops.emptyClipboard()
|
|
333
|
+
// Shared system clipboard session, including outstanding backend replies and owner tokens.
|
|
334
|
+
property var clipboardState: Clipboard.state()
|
|
335
|
+
// True when this pane's clipboard watcher could not start; Paste then reads on demand.
|
|
336
|
+
property bool clipboardWatchFailed: false
|
|
337
|
+
// The outstanding ordered clipboard request, or null; late replies cannot publish an older choice.
|
|
338
|
+
property var clipPending: null
|
|
339
|
+
// Ordered choices waiting for the untagged paths reply or already resolved by a menu snapshot.
|
|
340
|
+
property var clipQueue: []
|
|
341
|
+
// Monotonic choice number; only the newest request can publish to the system clipboard.
|
|
342
|
+
property int clipSequence: 0
|
|
343
|
+
// Which asker a pending paths reply belongs to, null meaning the clipboard, which is what every
|
|
344
|
+
// reply meant before compress also had to resolve a selection wider than this pane holds.
|
|
345
|
+
property var pathsPending: null
|
|
346
|
+
// A file drag in progress holds the watched re-read, so rows never shift under a live pointer.
|
|
347
|
+
property bool dragHolding: false
|
|
348
|
+
property bool awaitingPaths: false
|
|
349
|
+
readonly property int anchorRowHeight: Theme.fileRowHeight
|
|
350
|
+
// What the status bar's sticky slot is reporting, or an idle transfer; see ui/js/Ops.js.
|
|
351
|
+
property var transfer: Ops.emptyTransfer()
|
|
352
|
+
// The row that is its own editor right now, or -1; ui/List.qml's delegate reads it per row.
|
|
353
|
+
property int renamingIndex: -1
|
|
354
|
+
// Set by a pointer-committed rename so the reply reveals nothing; ui/js/Nav.js clears it.
|
|
355
|
+
property bool renameKeepsPointerRow: false
|
|
356
|
+
|
|
357
|
+
// Read once for the window: the chrome's path and the search strip's scope both shorten with it.
|
|
358
|
+
readonly property string home: Quickshell.env("HOME") || ""
|
|
359
|
+
|
|
360
|
+
// "list", "columns" or "grid"; the chrome's own buttons write it and the views read it.
|
|
361
|
+
property string viewMode: "list"
|
|
362
|
+
Component.onCompleted: root.viewMode = root.desiredView(root.path)
|
|
363
|
+
// A view switch takes the filter with it; only chooseView() and adoptView() persist, so a restored view writes nothing.
|
|
364
|
+
onViewModeChanged: Filter.close(root)
|
|
365
|
+
// The folder a view belongs to; a search, Recent or Trash standing over it is not that folder.
|
|
366
|
+
readonly property string viewFolder: root.searchMode.length === 0 && root.recentMode.length === 0 && !trashHost.opened ? root.path : ""
|
|
367
|
+
// Dual draws lists in both panes, and a folder's own view applies only while it is remembered.
|
|
368
|
+
function desiredView(path) {
|
|
369
|
+
return root.listOnly || root.dualMode ? "list" : FolderViews.viewFor(ViewState.folderViews, path, ViewState.view,
|
|
370
|
+
ViewState.rememberViewPerFolder)
|
|
371
|
+
}
|
|
372
|
+
function restoreView(path) {
|
|
373
|
+
var desired = root.desiredView(path)
|
|
374
|
+
if (root.viewMode !== desired)
|
|
375
|
+
root.viewMode = desired
|
|
376
|
+
}
|
|
377
|
+
// A tab brings back the view it held: a remembered folder keeps its own, any other view becomes the standing one.
|
|
378
|
+
function adoptView(path, mode) {
|
|
379
|
+
var own = ViewState.rememberViewPerFolder && FolderViews.has(ViewState.folderViews, path)
|
|
380
|
+
if (!own && !root.dualMode && mode !== ViewState.view)
|
|
381
|
+
ViewState.changeKey("view", mode)
|
|
382
|
+
root.restoreView(path)
|
|
383
|
+
}
|
|
384
|
+
// Effective values, so a settled ui.json that only spells out the defaults re-lists nothing and leaves the view where it is.
|
|
385
|
+
readonly property string listingPreferences: Sort.preferences(ViewState.state)
|
|
386
|
+
property string appliedListingPreferences: ""
|
|
387
|
+
// A preference re-list keeps selection and cursor by name, Finder's rule; the watcher defers
|
|
388
|
+
// while a selection stands, this one preserves it instead. Cleared when its rows land.
|
|
389
|
+
property var preferenceAnchor: null
|
|
390
|
+
onListingPreferencesChanged: {
|
|
391
|
+
// A hidden dual pane retains its session sort when the single pane changes the saved default.
|
|
392
|
+
if (root.backend && root.backend.preserveSort && !root.visible && root.appliedListingPreferences.length > 0) {
|
|
393
|
+
var applied = JSON.parse(root.appliedListingPreferences)
|
|
394
|
+
applied[1] = ViewState.state.sort
|
|
395
|
+
root.appliedListingPreferences = JSON.stringify(applied)
|
|
396
|
+
}
|
|
397
|
+
preferences.restart()
|
|
398
|
+
}
|
|
399
|
+
onVisibleChanged: if (root.visible) preferences.restart()
|
|
400
|
+
// A restored folder that never answers leaves "loading" for the waiting state, with Home
|
|
401
|
+
// reachable through it; the late rows, if they ever land, are dropped by the swap as stale.
|
|
402
|
+
readonly property int listingWaitMs: Startup.LISTING_WAIT_MS
|
|
403
|
+
Timer {
|
|
404
|
+
id: listingWait
|
|
405
|
+
interval: root.listingWaitMs
|
|
406
|
+
onTriggered: {
|
|
407
|
+
if (root.listInFlight && root.listingState === "loading") {
|
|
408
|
+
root.listingState = "waiting"
|
|
409
|
+
root.stateMessage = "That folder is not responding."
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
onListInFlightChanged: if (!root.listInFlight) { preferences.restart(); Sort.applyPending(root); listingWait.stop() } else listingWait.restart()
|
|
414
|
+
onSearchModeChanged: { preferences.restart(); root.moveSearchFocus() }
|
|
415
|
+
// flea-qml: typing gives the Find field the keyboard; leaving it hands the keyboard back to the list.
|
|
416
|
+
function moveSearchFocus() {
|
|
417
|
+
if (root.searchMode === Search.TYPING) Qt.callLater(header.focusSearchField)
|
|
418
|
+
else if (header.searchFieldFocused && root.listArea) root.listArea.forceActiveFocus()
|
|
419
|
+
}
|
|
420
|
+
onRecentModeChanged: if (root.recentMode.length === 0) preferences.restart()
|
|
421
|
+
Timer {
|
|
422
|
+
id: preferences
|
|
423
|
+
interval: 0
|
|
424
|
+
onTriggered: {
|
|
425
|
+
root.restoreView(root.viewFolder)
|
|
426
|
+
if (!root.visible || !root.path || root.listInFlight || root.searchMode.length > 0
|
|
427
|
+
|| root.recentMode.length > 0
|
|
428
|
+
|| root.appliedListingPreferences === root.listingPreferences) return
|
|
429
|
+
root.preferenceAnchor = Anchor.preference(root)
|
|
430
|
+
root.openWithoutHistory(root.path, { inPlace: true })
|
|
431
|
+
if (root.preferenceAnchor && root.preferenceAnchor.start > 0)
|
|
432
|
+
root.backend.window(root.preferenceAnchor.start, root.windowSize)
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
Connections {
|
|
436
|
+
target: ViewState
|
|
437
|
+
function onStateChanged() { preferences.restart() }
|
|
438
|
+
// A class switched on in Settings decodes again the same way the menu row does.
|
|
439
|
+
function onPreviewChanged() { root.refreshExtThumbs() }
|
|
440
|
+
}
|
|
441
|
+
// The zip probe lands here; the open menu is never rebuilt for it, the next opening draws it.
|
|
442
|
+
Connections {
|
|
443
|
+
target: root.backend
|
|
444
|
+
function onFlashImageResult(message) { Ops.noteZip(root, message) }
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
// Each tab retains its own back and forward navigation.
|
|
449
|
+
property var history: []
|
|
450
|
+
property var forwardHistory: []
|
|
451
|
+
property var tabs: null
|
|
452
|
+
property int tabCountNotice: -1
|
|
453
|
+
readonly property bool canGoBack: trashHost.opened || root.recentMode.length > 0 || root.history.length > 0
|
|
454
|
+
readonly property bool canGoUp: root.path.length > 1 || root.recentMode.length > 0 || Nav.climbsFromListing(root)
|
|
455
|
+
|
|
456
|
+
// The filesystem line the status bar draws, refreshed once per directory rather than per row.
|
|
457
|
+
property string fsName: ""
|
|
458
|
+
property real fsFree: 0
|
|
459
|
+
// False from a request for another folder until the backend's fsinfo line for it lands, so the strip says nothing, not unknown, before then.
|
|
460
|
+
property bool fsAnswered: false
|
|
461
|
+
// ExtThumbs: src/backend/extclass.rs's own word for the directory being shown, network,
|
|
462
|
+
// phone, usb or "", carried beside the fsinfo line once per directory change, never per row.
|
|
463
|
+
// storageKnown is false until that line lands, so the first settle never spends unknown as local.
|
|
464
|
+
property string storageClass: ""
|
|
465
|
+
property bool storageKnown: false
|
|
466
|
+
// The backend's fact that a system mountpoint holds this directory, found by its device number, so a link or bind mount counts as its volume.
|
|
467
|
+
property bool systemVolume: false
|
|
468
|
+
|
|
469
|
+
// Back and up close Recent rather than travelling; forward stays put outside the history.
|
|
470
|
+
function goBack() { if (root.flashActive) root.overlayParent.closeFlash(); else if (trashHost.opened) trashHost.close(); else if (root.recentMode.length > 0) RecentMode.close(root); else Nav.back(root) }
|
|
471
|
+
function goForward() { if (!trashHost.opened && !root.flashActive && root.recentMode.length === 0) Nav.forward(root) }
|
|
472
|
+
|
|
473
|
+
// Rename lives in ui/js/Ops.js with the other write operations; ui/List.qml's editor commits through this.
|
|
474
|
+
function commitRename(newName) { Ops.commitRename(root, newName) }
|
|
475
|
+
property int renameMenuId: 0
|
|
476
|
+
property string renameSource: ""
|
|
477
|
+
property string renameError: ""
|
|
478
|
+
property var renameRequest: null
|
|
479
|
+
readonly property bool renamePending: root.renameRequest !== null
|
|
480
|
+
// A sort asked for while an edit was open or a rename pending, held by
|
|
481
|
+
// ui/js/Sort.js and applied when the edit ends; never dropped, see applyPending.
|
|
482
|
+
property var pendingSort: null
|
|
483
|
+
property var convertSource: null
|
|
484
|
+
onRenamingIndexChanged: if (root.renamingIndex < 0) {
|
|
485
|
+
root.renameMenuId = 0
|
|
486
|
+
root.renameSource = ""
|
|
487
|
+
root.renameError = ""
|
|
488
|
+
// A rename that settled and an Escape that abandoned both end here, so a
|
|
489
|
+
// held sort applies on either; a navigation holds listInFlight and drops it.
|
|
490
|
+
Sort.applyPending(root)
|
|
491
|
+
}
|
|
492
|
+
// A navigation during a slow rename leaves renamingIndex at -1 with the
|
|
493
|
+
// request still pending, so no index change fires when it settles.
|
|
494
|
+
onRenamePendingChanged: if (!root.renamePending) Sort.applyPending(root)
|
|
495
|
+
|
|
496
|
+
// A set of row indices over the current listing, mutated in place; selectionVersion tells a reactive binding (List.qml's delegate, StatusBar's count) to re-read it. Task 8 declined ScriptModel plus ItemSelectionModel on measured memory, see AGENTS.md "The list model".
|
|
497
|
+
property var selection: Selection.create()
|
|
498
|
+
property int selectionVersion: 0
|
|
499
|
+
property int selectionAnchor: 0
|
|
500
|
+
property var selectionBand: null
|
|
501
|
+
// A button is down over the header or the view; ui/PaneStates.qml writes it, and ui/js/Anchor.js busy holds a watched re-read while it is true.
|
|
502
|
+
property bool pointerHeld: false
|
|
503
|
+
function isSelected(index) { return root.selectionVersion >= 0 && root.selection.has(index) }
|
|
504
|
+
function selectionCount() { return root.selectionVersion >= 0 ? root.selection.count() : 0 }
|
|
505
|
+
function selectedIndices() { return root.selectionVersion >= 0 ? root.selection.indices() : [] }
|
|
506
|
+
function toggleSelect() { Marks.toggleSelect(root) }
|
|
507
|
+
function selectAll() { Marks.selectAll(root); root.selectionVersion++ }
|
|
508
|
+
function clearSelection() { root.selection.clear(); root.selectionVersion++ }
|
|
509
|
+
function selectOnly(index, context) {
|
|
510
|
+
root.setCursor(index, context)
|
|
511
|
+
root.selection.only(root.cursorIndex)
|
|
512
|
+
root.selectionAnchor = root.cursorIndex
|
|
513
|
+
root.selectionVersion++
|
|
514
|
+
}
|
|
515
|
+
function extendSelection(delta) { Marks.extend(root, delta) }
|
|
516
|
+
// Ctrl+click and shift+click, the mouse's twins of v and shift+j/k; see keys.toml's [[pointer]].
|
|
517
|
+
function toggleSelectAt(index) { Marks.toggleRow(root, index) }
|
|
518
|
+
function extendSelectionTo(index) { Marks.extendToRow(root, index) }
|
|
519
|
+
// Named escapePressed, not escape, which collides with the JS global URI function; clears an active selection first, see keys.toml.
|
|
520
|
+
function escapePressed() { if (root.selection.count() > 0) { root.clearSelection(); return }; root.message("", false) }
|
|
521
|
+
|
|
522
|
+
function applyPendingSelect() { Nav.applyPendingSelect(root); Nav.applyPendingBackground(root) }
|
|
523
|
+
function openBackgroundMenu(at) { menu.openBackground(at) }
|
|
524
|
+
// flea-qml: measure folders only when the user asks; see src/dirsize.js and ui/js/DirSizes.js.
|
|
525
|
+
function measureDirs(rows) {
|
|
526
|
+
var dirs = rows.filter(function (i) { var r = root.rowFor(i); return r && r.d })
|
|
527
|
+
if (dirs.length === 0) return
|
|
528
|
+
root.dirSizeState = DirSizes.asking(root.dirSizeState, dirs)
|
|
529
|
+
root.backend.dirsize(dirs)
|
|
530
|
+
}
|
|
531
|
+
function measureAllDirs() {
|
|
532
|
+
root.dirSizeState = DirSizes.askingAll(root.dirSizeState)
|
|
533
|
+
root.backend.dirsizeAll()
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function refresh(selectPath, keepSelection) {
|
|
537
|
+
// Recent re-reads the history rather than re-listing its base, which is the root: see
|
|
538
|
+
// ui/js/RecentMode.js refresh, and Nav.refresh for every other listing.
|
|
539
|
+
if (root.recentMode === "trash" || (root.recentMode.length > 0 && keepSelection !== true)) RecentMode.refresh(root, selectPath)
|
|
540
|
+
else Nav.refresh(root, selectPath, keepSelection)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function open(newPath) {
|
|
544
|
+
if (trashHost.confirming) return
|
|
545
|
+
if (root.flashActive) root.overlayParent.closeFlash()
|
|
546
|
+
Nav.open(root, newPath)
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// An eject releases the mount under path: queued reads stop first, the preview lets go,
|
|
550
|
+
// and a pane showing that volume moves Home rather than standing on a vanished folder.
|
|
551
|
+
function quiesceVolume(path) {
|
|
552
|
+
if (root.backend)
|
|
553
|
+
root.backend.quiesce()
|
|
554
|
+
if (root.preview)
|
|
555
|
+
root.preview.close()
|
|
556
|
+
var home = root.home
|
|
557
|
+
if (home.length > 0 && String(path).length > 0
|
|
558
|
+
&& (root.path === path || root.path.indexOf(path + "/") === 0))
|
|
559
|
+
Nav.openPlace(root, home)
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// A refused hop clears nothing: only a navigation takes the place of a listing out, and Recent's rows must survive a re-list that does not.
|
|
563
|
+
function openWithoutHistory(newPath, options) {
|
|
564
|
+
// Only a navigation lets a waiting listing go; an in-place re-list of the folder being left keeps it.
|
|
565
|
+
if (!options || options.inPlace !== true) Anchor.clearWaiting(root)
|
|
566
|
+
// Only a navigation takes the place of a listing out, and the folder asked for last has its own wait, not the first one's clock.
|
|
567
|
+
if (root.listInFlight && (!options || options.supersede !== true)) {
|
|
568
|
+
root.message("A directory is already loading.", false)
|
|
569
|
+
return
|
|
570
|
+
}
|
|
571
|
+
if (root.listInFlight) listingWait.restart()
|
|
572
|
+
// Every real navigation leaves Recent, Trash and Flash; options.inPlace, a re-list of this folder, leaves Trash up.
|
|
573
|
+
if (!options || options.inPlace !== true) { trashHost.close(); if (root.flashActive) root.overlayParent.closeFlash() }
|
|
574
|
+
RecentMode.leave(root)
|
|
575
|
+
var applied = root.appliedListingPreferences ? JSON.parse(root.appliedListingPreferences) : []
|
|
576
|
+
// Search exit can enter here before the preferences timer consumes a deferred Settings change.
|
|
577
|
+
if (JSON.stringify(applied[1]) !== JSON.stringify(ViewState.state.sort)) root.backend.resetSort(newPath)
|
|
578
|
+
// options.keepHidden is the tab restore's alone: it just put back this tab's own dotfile answer, which the standing preference would overwrite.
|
|
579
|
+
if (!options || options.keepHidden !== true) root.showHidden = ViewState.state.hidden === true
|
|
580
|
+
Nav.openWithoutHistory(root, newPath, options)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// The toggle re-lists rather than filtering client-side: the model is a row count over the
|
|
584
|
+
// backend's own listing, which never held the dotfiles to begin with when they were off.
|
|
585
|
+
// Re-listing also clears the cursor and selection, the same as opening any other directory.
|
|
586
|
+
// Recent lists what the history names, dotfiles among them, so the toggle has nothing to hide there.
|
|
587
|
+
function toggleHidden() {
|
|
588
|
+
if (root.recentMode.length > 0) { root.message("This collection includes hidden items", false); return }
|
|
589
|
+
root.showHidden = !root.showHidden
|
|
590
|
+
ViewState.changeKey("hidden", root.showHidden)
|
|
591
|
+
// flea-qml: a standing Find takes the new setting and stays, rather than leaving for the folder.
|
|
592
|
+
if (root.searchMode.length > 0) { Search.rehide(root); return }
|
|
593
|
+
root.open(root.path)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Where a drop on this pane lands. While a listing is out the pane's own path is still the
|
|
597
|
+
// directory it is leaving, so a drop taken in that window landed in the wrong one: measured by
|
|
598
|
+
// tests/drag.sh R7, where a drop on a tab whose listing was still out copied into the source.
|
|
599
|
+
readonly property string dropPath: root.listInFlight && root.listingPath.length > 0
|
|
600
|
+
? root.listingPath : root.path
|
|
601
|
+
|
|
602
|
+
function rowFor(index) {
|
|
603
|
+
var offset = index - root.held
|
|
604
|
+
if (offset < 0 || offset >= root.rows.length)
|
|
605
|
+
return null
|
|
606
|
+
return root.rows[offset]
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// The list's own delegate, which is what every reader of a rendered row cell needs; only the
|
|
610
|
+
// list draws those cells at all.
|
|
611
|
+
function itemFor(index) {
|
|
612
|
+
return list.itemAtIndex(index)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// The delegate actually on screen, whichever view is showing. A coordinate taken off a hidden
|
|
616
|
+
// view is a coordinate nothing can be clicked at, which is what the columns view used to return.
|
|
617
|
+
// index is a listing row and every view's itemAtIndex wants a view position, which is a different
|
|
618
|
+
// number under a filter: passing the listing row answered null for a row plainly on screen, and
|
|
619
|
+
// once the match count passed it, the delegate at that position under an unrelated row.
|
|
620
|
+
function visibleItemFor(index) {
|
|
621
|
+
return root.listArea.itemAtIndex(Filter.viewOf(root.shown, index))
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// shell.qml's IPC thumbFile reader calls this; the lookup lives with the thumbnail machinery in ui/List.qml.
|
|
625
|
+
function thumbFor(index) { return list.thumbFor(index) }
|
|
626
|
+
|
|
627
|
+
// Lifted to Focus.act, see ui/js/Focus.js, which routes "settings" here from the list and the rail alike.
|
|
628
|
+
function act(action, menuId, paths, context) {
|
|
629
|
+
// Every dispatched action ends a pending slow click; its own rename runs after the timer cleared the record.
|
|
630
|
+
cancelSlowClick()
|
|
631
|
+
if (trashHost.confirming) return
|
|
632
|
+
if (action === "openTrash" || action === "emptyTrash" || action === "restoreAll") { trashHost.action(action); return }
|
|
633
|
+
if (trashHost.browsing && ["restoreTrashSelection", "deletePermanently", "trash"].indexOf(action) >= 0) { trashHost.action(action, menuId); return }
|
|
634
|
+
if (action === "settings") { root.settingsPanel.open(root); return }
|
|
635
|
+
if (action === "toggleLangMarks") { ViewState.toggleLangMarks(); return }
|
|
636
|
+
if (action === "permissions") { root.openPermissions(); return }
|
|
637
|
+
if (action === "rename" && root.openBulkRename(paths)) return // several rows open the bulk card
|
|
638
|
+
// Recent is a history, not a directory: a new file there would land in the root it stands on.
|
|
639
|
+
if (action === "newFile" && root.recentMode.length > 0) { root.message((root.recentMode === "trash" ? "Trash" : "Recent") + " is a collection. Open a folder first", false); return }
|
|
640
|
+
if (["newFile", "rename", "openWith", "moveTo", "copyTo", "properties", "deletePermanently"].indexOf(action) >= 0) {
|
|
641
|
+
menuActions.open(action, menuId || 0, context)
|
|
642
|
+
return
|
|
643
|
+
}
|
|
644
|
+
Focus.act(action, root, menuId, paths)
|
|
645
|
+
}
|
|
646
|
+
// The menu's own dispatch lives with the rest of the menu machinery; this is the one seam the
|
|
647
|
+
// rail's place menu and the dialogs still call through.
|
|
648
|
+
function performMenu(action, menuId, paths) { menuActions.perform(action, menuId, paths) }
|
|
649
|
+
// The sheet runs a row with no menu opened, so stand the menu on the cursor row as openAt does, or validateChoice finds nothing it opened.
|
|
650
|
+
function sheetMenuAction(action) {
|
|
651
|
+
menu.clearRail()
|
|
652
|
+
menu.forHeader = false
|
|
653
|
+
menu.hasRow = true
|
|
654
|
+
menu.openedIdentity = root.menuSelectionIdentity()
|
|
655
|
+
menuActions.snapshot()
|
|
656
|
+
menuActions.sheetRequest = menuActions.requestId
|
|
657
|
+
menuActions.activate(action, true)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function permissionSelection() {
|
|
661
|
+
var indices = Ops.targetIndices(root)
|
|
662
|
+
return indices.length === 1 ? root.rowFor(indices[0]) : null
|
|
663
|
+
}
|
|
664
|
+
// MenuAdditions040 callout 10: Make executable acts on exactly one target and that target is
|
|
665
|
+
// the cursor row; anything else hides the row and the action refuses.
|
|
666
|
+
function isSingleCursorTarget() {
|
|
667
|
+
var indices = Ops.targetIndices(root)
|
|
668
|
+
return indices.length === 1 && indices[0] === root.cursorIndex
|
|
669
|
+
}
|
|
670
|
+
// The cursor row's two-byte shebang probe, asked of the backend once per menu open for that one file.
|
|
671
|
+
property bool rowHasShebang: false
|
|
672
|
+
property string shebangAsked: ""
|
|
673
|
+
property int shebangId: 0
|
|
674
|
+
property int makeExecPendingId: 0
|
|
675
|
+
// Show original's own pending id, dropped by any navigation before its reply lands.
|
|
676
|
+
property int linkTargetPendingId: 0
|
|
677
|
+
// The one path a shebang answer may land for, "" unless the cursor row is a regular file without any execute bit.
|
|
678
|
+
function shebangTarget() {
|
|
679
|
+
var row = root.cursorRow
|
|
680
|
+
if (!row || row.d) return ""
|
|
681
|
+
var bits = Number(row.p) || 0
|
|
682
|
+
if ((bits & Format.S_IFMT) !== Format.S_IFREG || (bits & Format.ANY_EXECUTE_BIT) !== 0) return ""
|
|
683
|
+
return root.join(Search.baseOf(root), row.n)
|
|
684
|
+
}
|
|
685
|
+
function checkShebang() {
|
|
686
|
+
var path = root.shebangTarget()
|
|
687
|
+
if (path.length === 0) {
|
|
688
|
+
root.shebangAsked = ""
|
|
689
|
+
root.rowHasShebang = false
|
|
690
|
+
return
|
|
691
|
+
}
|
|
692
|
+
root.shebangId += 1
|
|
693
|
+
root.shebangAsked = path
|
|
694
|
+
root.rowHasShebang = false
|
|
695
|
+
root.backend.send({ c: "shebang", path: path, id: root.shebangId })
|
|
696
|
+
}
|
|
697
|
+
// Only the newest id on the asked path lands, so a late answer for an earlier file never arms a later one.
|
|
698
|
+
function noteShebang(path, hasShebang, id) {
|
|
699
|
+
if (!Permissions.landsShebang(path, id, root.shebangAsked, root.shebangId)) return
|
|
700
|
+
root.rowHasShebang = hasShebang
|
|
701
|
+
if (menu.opened && menu.hasRow) menu.refreshProviderRows()
|
|
702
|
+
}
|
|
703
|
+
// MenuAdditions040 callout 10: adds the owner execute bit through the permissions batch, so one undo restores it.
|
|
704
|
+
// The context menu is its one caller, and it acts on the single cursor target.
|
|
705
|
+
function makeExecutable() {
|
|
706
|
+
if (!root.isSingleCursorTarget()) {
|
|
707
|
+
root.message("Make executable works on a script that cannot run yet", false)
|
|
708
|
+
return
|
|
709
|
+
}
|
|
710
|
+
var row = root.rowFor(root.cursorIndex)
|
|
711
|
+
if (!row || !Menu.canMakeExecutable(row.p, 1, root.rowHasShebang, true)) {
|
|
712
|
+
root.message("Make executable works on a script that cannot run yet", false)
|
|
713
|
+
return
|
|
714
|
+
}
|
|
715
|
+
var taken = [root.join(Search.baseOf(root), row.n)]
|
|
716
|
+
var oldBits = (Number(row.p) || 0) & 0o7777
|
|
717
|
+
var octal = Permissions.octal((oldBits | Format.S_IXUSR) & 0o7777)
|
|
718
|
+
root.makeExecPendingId += 1
|
|
719
|
+
root.backend.send({ c: "permissionsBatch", paths: taken, modes: [octal], id: Permissions.MAKE_EXEC_ID + root.makeExecPendingId })
|
|
720
|
+
}
|
|
721
|
+
// MenuAdditions040: Permissions takes the whole selection, so the menu
|
|
722
|
+
// row carries every target's mode and the dialog inspects each in turn.
|
|
723
|
+
function permissionModes() {
|
|
724
|
+
var out = []
|
|
725
|
+
var indices = Ops.targetIndices(root)
|
|
726
|
+
for (var i = 0; i < indices.length; i++) {
|
|
727
|
+
var row = root.rowFor(indices[i])
|
|
728
|
+
if (row) out.push(row.p)
|
|
729
|
+
}
|
|
730
|
+
return out
|
|
731
|
+
}
|
|
732
|
+
function openPermissions() {
|
|
733
|
+
var idx = Ops.targetIndices(root)
|
|
734
|
+
if (idx.length === 0) { Ops.sayNoTarget(root); return }
|
|
735
|
+
if (root.pathsPending || root.clipPending !== null) { Ops.pathsBusy(root); return }
|
|
736
|
+
// The selection can reach past the held window, so the backend
|
|
737
|
+
// resolves the indices while it still can, the same rule clip follows.
|
|
738
|
+
root.pathsPending = { kind: "permissions" }
|
|
739
|
+
root.backend.askPaths(idx)
|
|
740
|
+
}
|
|
741
|
+
function openPermissionsWith(paths) {
|
|
742
|
+
if (paths.length === 0) { Ops.sayNoTarget(root); return }
|
|
743
|
+
if (paths.length === 1) {
|
|
744
|
+
var target = root.permissionSelection()
|
|
745
|
+
var single = target ? target.p : 0
|
|
746
|
+
if (Menu.permissionsEntry(single, 1).disabled) {
|
|
747
|
+
root.message("Permissions takes one item, not a link.", true)
|
|
748
|
+
return
|
|
749
|
+
}
|
|
750
|
+
root.permissionsRequested(paths[0])
|
|
751
|
+
return
|
|
752
|
+
}
|
|
753
|
+
root.permissionsBatchRequested(paths)
|
|
754
|
+
}
|
|
755
|
+
// Several rows open the bulk card; one row answers false so the caller keeps its single-row route.
|
|
756
|
+
function openBulkRename(paths) {
|
|
757
|
+
if (paths && paths.length > 1) { root.bulkRenameRequested(paths); return true }
|
|
758
|
+
var idx = paths && paths.length ? [] : Ops.targetIndices(root)
|
|
759
|
+
if (idx.length <= 1) return false
|
|
760
|
+
if (root.pathsPending || root.clipPending !== null) { Ops.pathsBusy(root); return true }
|
|
761
|
+
root.pathsPending = { kind: "bulkrename" }
|
|
762
|
+
root.backend.askPaths(idx)
|
|
763
|
+
return true
|
|
764
|
+
}
|
|
765
|
+
// The backend's answer to openBulkRename's askPaths.
|
|
766
|
+
function openBulkRenameWith(paths) {
|
|
767
|
+
if (paths.length <= 1) { Ops.sayNoTarget(root); return }
|
|
768
|
+
root.bulkRenameRequested(paths)
|
|
769
|
+
}
|
|
770
|
+
// One line for the whole batch, and one undo reverses every rename it made.
|
|
771
|
+
function bulkRenamed(ok) {
|
|
772
|
+
root.message(Ops.bulkDoneLine(ok), false)
|
|
773
|
+
root.clearSelection()
|
|
774
|
+
root.refresh("")
|
|
775
|
+
}
|
|
776
|
+
// c and P open Copy as and Paste as on their first flyout row.
|
|
777
|
+
function openCopyAs() {
|
|
778
|
+
if (!root.openCursorMenu()) {
|
|
779
|
+
root.message("No item under the cursor to open a menu on", false)
|
|
780
|
+
return
|
|
781
|
+
}
|
|
782
|
+
menu.openSubmenuFor("copyAs")
|
|
783
|
+
}
|
|
784
|
+
function openPasteAs() {
|
|
785
|
+
if (!root.openCursorMenu())
|
|
786
|
+
menu[root.cursorRow ? "openAt" : "openBackground"](root.listSlot.mapToItem(null, root.listSlot.width / 2, root.listSlot.height / 2))
|
|
787
|
+
if (!menu.clipboardAvailable) {
|
|
788
|
+
menu.close()
|
|
789
|
+
root.message(Menu.EMPTY_CLIPBOARD, false)
|
|
790
|
+
return
|
|
791
|
+
}
|
|
792
|
+
menu.openSubmenuFor("pasteAs")
|
|
793
|
+
}
|
|
794
|
+
// MenuAdditions040: V flips the marks over the rows the listing draws;
|
|
795
|
+
// the filter applies, and a close match is never selected.
|
|
796
|
+
function invertSelection() { Marks.invert(root) }
|
|
797
|
+
// MenuAdditions040: Show original reveals a symlink's target in its own
|
|
798
|
+
// folder, the same path Show in folder uses.
|
|
799
|
+
function showOriginal() {
|
|
800
|
+
var row = root.rowFor(root.cursorIndex)
|
|
801
|
+
if (!row || !Format.isSymlink(row.p)) {
|
|
802
|
+
root.message("Show original needs the cursor on a link", false)
|
|
803
|
+
return
|
|
804
|
+
}
|
|
805
|
+
root.requestLinkTarget(root.join(Search.baseOf(root), row.n))
|
|
806
|
+
}
|
|
807
|
+
// Both entrances send through here, so one pending id covers the cursor and the menu.
|
|
808
|
+
function requestLinkTarget(path) {
|
|
809
|
+
root.linkTargetPendingId = root.backend.nextLinkTargetId()
|
|
810
|
+
root.backend.send({ c: "linktarget", path: path, id: root.linkTargetPendingId })
|
|
811
|
+
}
|
|
812
|
+
// Paste as links reads the file clipboard and asks through the collision card.
|
|
813
|
+
function pasteLink(kind) {
|
|
814
|
+
if (RecentMode.refusePaste(root)) return
|
|
815
|
+
Ops.pasteLink(root, kind)
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// index is a listing row, which is what every caller outside ui/js/Filter.js holds; the clamp
|
|
819
|
+
// and the scroll both happen in view space, because a filter can be narrowing what is drawn.
|
|
820
|
+
// A click carries context 0; a keyboard move keeps the default 3.
|
|
821
|
+
function setCursor(index, context) { Filter.setCursor(root, index, context) }
|
|
822
|
+
// The cursor keeps three rows of context: the list and the columns view hold it through their own showCursor, the grid keeps Contain after its landing window.
|
|
823
|
+
function showRow(view, context) {
|
|
824
|
+
if (root.viewMode === "columns" && root.columnsArea) root.columnsArea.activeColumn().showCursor(view, context)
|
|
825
|
+
else if (root.viewMode === "list") list.showCursor(view, context)
|
|
826
|
+
else root.listArea.showCursor(view, context)
|
|
827
|
+
root.listArea.restartCoalesce()
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// A successful pointer commit preserves the newly selected row; a refusal returns to its editor.
|
|
831
|
+
function commitOpenRename() {
|
|
832
|
+
var item = root.renameEditor()
|
|
833
|
+
if (!item || root.renamePending) return
|
|
834
|
+
root.renameKeepsPointerRow = true
|
|
835
|
+
if (!item.commitEditor() || root.renameError.length > 0) root.renameKeepsPointerRow = false
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// The live editor or null: a set renamingIndex is not evidence one exists, see ui/RenameField.qml.
|
|
839
|
+
function renameEditor() {
|
|
840
|
+
if (root.renamingIndex < 0) return null
|
|
841
|
+
// The columns view draws one editor over its active column rather than one inside each row.
|
|
842
|
+
if (root.viewMode === "columns")
|
|
843
|
+
return root.columnsArea && root.columnsArea.activeColumn().renaming ? root.columnsArea.activeColumn() : null
|
|
844
|
+
var item = root.viewMode === "grid" ? (root.listArea ? root.listArea.currentRenameTile(root) : null) : root.visibleItemFor(root.renamingIndex)
|
|
845
|
+
return item && item.renaming ? item : null
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function openCursor() { Nav.openCursor(root, wire.opener) }
|
|
849
|
+
|
|
850
|
+
// A path the caller already resolved, for the columns view's neighbour rows, which have no cursor.
|
|
851
|
+
function openFile(path) { wire.opener.open(path) }
|
|
852
|
+
|
|
853
|
+
// A terminal in the caller's place or the directory being shown, through flea --terminal.
|
|
854
|
+
function openTerminal(path) { wire.opener.openTerminal(path || root.path) }
|
|
855
|
+
|
|
856
|
+
function newWindow() { Quickshell.execDetached(Tabs.freshLaunch([Quickshell.env("FLEA_BIN") || "flea", root.path])) }
|
|
857
|
+
|
|
858
|
+
// Quoted when it holds whitespace, because this one is pasted into a shell: see ui/js/Format.js.
|
|
859
|
+
function copyDirPath() { wire.opener.copyText(Format.shellQuoted(root.path)) }
|
|
860
|
+
|
|
861
|
+
function openParent() { if (root.flashActive) root.overlayParent.closeFlash(); else if (trashHost.opened) trashHost.close(); else if (root.recentMode.length > 0) RecentMode.close(root); else Nav.parent(root) }
|
|
862
|
+
|
|
863
|
+
// The sort Recent replaced, handed back on leaving; ui/js/Search.js reaches it through here.
|
|
864
|
+
function restoreRecentSort() { RecentMode.restoreSort(root) }
|
|
865
|
+
// The rail's Recent row answers with the history's paths, read bounded the way the
|
|
866
|
+
// path jump reads them; a second open while one lands replaces it, the way a navigation does.
|
|
867
|
+
function openRecent(paths, visits) { RecentMode.run(root, paths, visits) }
|
|
868
|
+
function closeRecent() { RecentMode.close(root) }
|
|
869
|
+
|
|
870
|
+
function join(base, name) {
|
|
871
|
+
if (base === "/" && (/^[A-Za-z]:[\\/]/.test(name) || name.indexOf("//") === 0)) return name
|
|
872
|
+
return base === "/" ? "/" + name : base + "/" + name
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
Flea.PaneWire {
|
|
876
|
+
id: wire
|
|
877
|
+
pane: root
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
Flea.PaneRail {
|
|
881
|
+
id: railHost
|
|
882
|
+
anchors.left: parent.left
|
|
883
|
+
// Over the listing, because with auto-hide on the rail is an overlay and not a column.
|
|
884
|
+
z: 3
|
|
885
|
+
pane: root
|
|
886
|
+
service: root.networkService
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// On glass the listing's ground, beside the rail and below the path and search strips, never under them; see ui/Glass.qml.
|
|
890
|
+
Rectangle {
|
|
891
|
+
anchors { top: root.searchMode.length > 0 ? header.bottom : panePath.bottom; bottom: parent.bottom; left: railHost.right; right: parent.right }
|
|
892
|
+
visible: Glass.on
|
|
893
|
+
color: Glass.listBackground
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
Flea.PanePath {
|
|
897
|
+
id: panePath
|
|
898
|
+
anchors { left: railHost.right; right: parent.right; top: parent.top }
|
|
899
|
+
height: root.dualMode && !trashHost.opened ? Theme.chromeHeight : 0
|
|
900
|
+
visible: height > 0
|
|
901
|
+
// A history is a location and not a directory, so the strip says its own name.
|
|
902
|
+
path: root.recentMode === "trash" ? "trash:///" : root.recentMode.length > 0 ? "Recent" : root.path
|
|
903
|
+
home: root.home
|
|
904
|
+
pathAlias: root.pathAlias
|
|
905
|
+
focused: root.paneFocused
|
|
906
|
+
inputLive: !(root.preview && root.preview.active)
|
|
907
|
+
pane: root
|
|
908
|
+
onChosen: function (path) { if (path === "trash:///") root.trash.open(); else root.open(path) }
|
|
909
|
+
onEditRequested: root.pathBarRequested()
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
PointHandler {
|
|
913
|
+
id: focusPointer
|
|
914
|
+
acceptedButtons: Qt.AllButtons
|
|
915
|
+
onActiveChanged: if (active) railHost.focusPress(focusPointer.point.position)
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
Loader {
|
|
919
|
+
id: trashStrip
|
|
920
|
+
anchors { top: panePath.bottom; left: railHost.right; right: parent.right }
|
|
921
|
+
active: trashHost.browsing
|
|
922
|
+
height: active ? Theme.chromeHeight : 0
|
|
923
|
+
visible: active
|
|
924
|
+
source: "TrashToolbar.qml"
|
|
925
|
+
onLoaded: item.trash = trashHost
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
Flea.Header {
|
|
929
|
+
id: header
|
|
930
|
+
// Only the list view has columns to head, and neither the grid board nor the columns board
|
|
931
|
+
// draws one; the strip collapses rather than hiding, so the view below starts at the top of
|
|
932
|
+
// the pane instead of a gap. A search takes the strip whole, in any view.
|
|
933
|
+
visible: !trashHost.opened && !root.flashActive && (root.viewMode === "list" || root.searchMode.length > 0)
|
|
934
|
+
height: visible ? implicitHeight : 0
|
|
935
|
+
anchors.top: trashStrip.bottom
|
|
936
|
+
anchors.left: railHost.right
|
|
937
|
+
anchors.right: parent.right
|
|
938
|
+
// The held rows a double click fits and F4 fits whole; ListColumns040 never scans the directory.
|
|
939
|
+
pane: root
|
|
940
|
+
viewMode: root.viewMode
|
|
941
|
+
sortBy: root.backend.sortBy
|
|
942
|
+
sortDesc: root.backend.sortDesc
|
|
943
|
+
dualMode: root.dualMode
|
|
944
|
+
recent: root.recentMode.length > 0
|
|
945
|
+
hiddenCols: root.dualMode ? ["mode", "kind"].concat(ViewState.hiddenCols) : ViewState.hiddenCols
|
|
946
|
+
onSortRequested: function (key) { Sort.column(root, key) }
|
|
947
|
+
onMenuRequested: function (pos) { menu.openForHeader(pos) }
|
|
948
|
+
searchMode: root.searchMode
|
|
949
|
+
searchQuery: root.searchQuery
|
|
950
|
+
searchScope: Search.scope(Search.scopeRoot(root.path, root.home, root.searchStep), root.home)
|
|
951
|
+
// flea-qml: the count is the last run's; an edited line waiting for Enter says so instead.
|
|
952
|
+
searchNote: Search.note(root.total, root.searchRunning, root.searchSent.length === 0)
|
|
953
|
+
// flea-qml: the keys the strip answers, named plainly.
|
|
954
|
+
searchWayOut: "Enter/Tab/Esc/Ctrl+G"
|
|
955
|
+
searchPlaceholder: Search.placeholder(root.path, root.home, root.searchStep, root.searchContents)
|
|
956
|
+
searchTabHint: ""
|
|
957
|
+
searchRunning: root.searchRunning
|
|
958
|
+
onSearchCloseRequested: Search.dismiss(root)
|
|
959
|
+
// flea-qml: the field edits the query; Enter, Esc, Tab and the arrows take the pane's own route.
|
|
960
|
+
onSearchEdited: function (text) { root.searchQuery = text }
|
|
961
|
+
onSearchKeyOut: function (event) { Focus.handleKey(event, root, root.sidebar, "find") }
|
|
962
|
+
onSearchFieldEntered: if (root.searchMode === Search.RESULTS) root.searchMode = Search.TYPING
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// The two views share the same slot, the same rows and the same cursor; only one is ever up, and
|
|
966
|
+
// listArea points at whichever it is, so every caller of restartSettle stays view-agnostic and
|
|
967
|
+
// shell.qml lays the empty-state overlay over the right one.
|
|
968
|
+
// The list stands in for a view still being built, so no binding on listArea ever reads null.
|
|
969
|
+
readonly property var listArea: root.viewMode === "grid" && gridLoader.item ? gridLoader.item
|
|
970
|
+
: root.viewMode === "columns" && columnsLoader.item ? columnsLoader.item : list
|
|
971
|
+
// How far a cursor step down moves: one row in the list, one row of tiles in the grid.
|
|
972
|
+
// The columns view's own preview, exposed so a test can assert its facts without OCR; null until built.
|
|
973
|
+
readonly property var columnsArea: columnsLoader.item
|
|
974
|
+
readonly property bool dragActive: root.dragHolding || !!(root.listArea && root.listArea.fileDragActive === true)
|
|
975
|
+
// Where the view sits, for an anchor: a Loader's item is no sibling of anything here, its Loader is.
|
|
976
|
+
readonly property Item listSlot: root.viewMode === "grid" ? gridLoader : root.viewMode === "columns" ? columnsLoader : list
|
|
977
|
+
readonly property int cursorStride: root.viewMode === "grid" && gridLoader.item ? gridLoader.item.columns : 1
|
|
978
|
+
|
|
979
|
+
Flea.FilterStrip {
|
|
980
|
+
id: filterStrip
|
|
981
|
+
anchors.top: header.bottom
|
|
982
|
+
anchors.left: railHost.right
|
|
983
|
+
anchors.right: parent.right
|
|
984
|
+
pane: root
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// A hidden view is not a free view, see AGENTS.md rule 6: each of these two is built by its first
|
|
988
|
+
// switch and kept, so a launch in the list view pays for one view's rows and marks, not three.
|
|
989
|
+
property bool columnsBuilt: false
|
|
990
|
+
property bool gridBuilt: false
|
|
991
|
+
// setSource, not source: the view reads pane in its own bindings, so it has to hold one from birth.
|
|
992
|
+
Loader {
|
|
993
|
+
id: columnsLoader
|
|
994
|
+
active: root.viewMode === "columns" || root.columnsBuilt
|
|
995
|
+
visible: !trashHost.opened && !root.flashActive
|
|
996
|
+
focus: visible && root.viewMode === "columns"
|
|
997
|
+
anchors { top: filterStrip.bottom; left: railHost.right; right: parent.right; bottom: parent.bottom }
|
|
998
|
+
Component.onCompleted: setSource("ColumnsArea.qml", { pane: root, menu: menu, focus: true })
|
|
999
|
+
onLoaded: { root.columnsBuilt = true; item.visible = Qt.binding(function () { return root.viewMode === "columns" }) }
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
Loader {
|
|
1003
|
+
id: gridLoader
|
|
1004
|
+
active: root.viewMode === "grid" || root.gridBuilt
|
|
1005
|
+
visible: !trashHost.opened && !root.flashActive
|
|
1006
|
+
focus: visible && root.viewMode === "grid"
|
|
1007
|
+
anchors { top: filterStrip.bottom; left: railHost.right; right: parent.right; bottom: parent.bottom }
|
|
1008
|
+
Component.onCompleted: setSource("GridArea.qml", { pane: root, menu: menu })
|
|
1009
|
+
onLoaded: { root.gridBuilt = true; item.visible = Qt.binding(function () { return root.viewMode === "grid" }) }
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// The grid's plans land the way the list's do below: it computes, and only the pane writes the two states.
|
|
1013
|
+
Connections {
|
|
1014
|
+
target: columnsLoader.item
|
|
1015
|
+
function onThumbsApplied(work) { root.thumbState = Thumbs.applied(root.thumbState, work) }
|
|
1016
|
+
function onDirSizesApplied(ask) { root.dirSizeState = DirSizes.applied(root.dirSizeState, ask) }
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
Connections {
|
|
1020
|
+
target: gridLoader.item
|
|
1021
|
+
function onThumbsApplied(work) { root.thumbState = Thumbs.applied(root.thumbState, work) }
|
|
1022
|
+
function onDirSizesApplied(ask) { root.dirSizeState = DirSizes.applied(root.dirSizeState, ask) }
|
|
1023
|
+
function onDirSizesCancelled() { root.dirSizeState = DirSizes.cancelled(root.dirSizeState) }
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
readonly property int previewIndex: root.viewMode === "columns" && columnsLoader.item ? columnsLoader.item.previewIndex : -1
|
|
1027
|
+
readonly property var previewColumnItem: root.viewMode === "columns" && columnsLoader.item ? columnsLoader.item.previewColumn : null
|
|
1028
|
+
function loadSelectionPreview() {
|
|
1029
|
+
if (!ViewState.previewColumn || root.dualMode) return
|
|
1030
|
+
if (root.viewMode === "columns" && columnsLoader.item) columnsLoader.item.loadSelection()
|
|
1031
|
+
}
|
|
1032
|
+
function togglePreviewColumn() { ViewState.changeLeaf("preview", { column: !ViewState.previewColumn }) }
|
|
1033
|
+
// ExtThumbs: the background menu's class row writes the class setting, never "this drive".
|
|
1034
|
+
// A class switched on decodes again: its cache-only misses leave and the viewport re-asks.
|
|
1035
|
+
function toggleExtThumbs() {
|
|
1036
|
+
var key = ExtThumbs.keyForClass(root.storageClass)
|
|
1037
|
+
if (key === "") return
|
|
1038
|
+
var on = !ExtThumbs.classOn(root.storageClass, ViewState.preview)
|
|
1039
|
+
// No direct refresh: the setting change below routes through onPreviewChanged,
|
|
1040
|
+
// which refreshes once on the verdict edge instead of twice.
|
|
1041
|
+
ViewState.changeSetting("preview." + key, on)
|
|
1042
|
+
root.message(ExtThumbs.statusLine(root.storageClass, on), false)
|
|
1043
|
+
}
|
|
1044
|
+
function refreshExtThumbs() {
|
|
1045
|
+
var now = ExtThumbs.verdict(root.storageClass, ViewState.preview)
|
|
1046
|
+
if (now === root.extVerdict) return
|
|
1047
|
+
root.extVerdict = now
|
|
1048
|
+
root.thumbState = ExtThumbs.forgetMisses(root.thumbState)
|
|
1049
|
+
if (root.listArea) root.listArea.restartSettle()
|
|
1050
|
+
// The preview follows rather than loads, so a class just switched off holds instead of decoding.
|
|
1051
|
+
if (root.viewMode === "columns" && columnsLoader.item) columnsLoader.item.askMeta()
|
|
1052
|
+
}
|
|
1053
|
+
function chooseView(mode) {
|
|
1054
|
+
if (FolderViews.MODES.indexOf(mode) < 0 && mode !== "dual")
|
|
1055
|
+
return
|
|
1056
|
+
if (root.listOnly) {
|
|
1057
|
+
root.viewRequested(mode)
|
|
1058
|
+
return
|
|
1059
|
+
}
|
|
1060
|
+
// Dual is a window layout and no folder's view: choosing it, or any view while in it, sets the standing view first.
|
|
1061
|
+
if (mode === "dual" || root.dualMode)
|
|
1062
|
+
ViewState.changeKey("view", mode)
|
|
1063
|
+
if (mode === "dual")
|
|
1064
|
+
return
|
|
1065
|
+
root.viewMode = mode
|
|
1066
|
+
if (FolderViews.shouldRemember(ViewState.rememberViewPerFolder, root.viewFolder)) {
|
|
1067
|
+
var leaf = {}
|
|
1068
|
+
leaf[root.viewFolder] = mode
|
|
1069
|
+
ViewState.changeMapEntries("folderViews", leaf, FolderViews.set(ViewState.folderViews, root.viewFolder, mode))
|
|
1070
|
+
} else if (ViewState.state.view !== mode) {
|
|
1071
|
+
ViewState.changeKey("view", mode)
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
function focusPreviewColumn() {
|
|
1075
|
+
if (!ViewState.previewColumn || root.dualMode) return
|
|
1076
|
+
if (root.viewMode === "columns" && columnsLoader.item) columnsLoader.item.focusPreview()
|
|
1077
|
+
}
|
|
1078
|
+
property bool dualMode: false
|
|
1079
|
+
readonly property alias pathCrumbs: panePath.crumbItems
|
|
1080
|
+
readonly property alias pathSlot: panePath.crumbSlot
|
|
1081
|
+
readonly property alias pathStrip: panePath
|
|
1082
|
+
signal switchPane()
|
|
1083
|
+
signal switchToRail()
|
|
1084
|
+
|
|
1085
|
+
Flea.List {
|
|
1086
|
+
id: list
|
|
1087
|
+
visible: !trashHost.opened && !root.flashActive && root.viewMode === "list"
|
|
1088
|
+
focus: visible
|
|
1089
|
+
anchors.top: filterStrip.bottom
|
|
1090
|
+
anchors.left: railHost.right
|
|
1091
|
+
anchors.right: parent.right
|
|
1092
|
+
anchors.bottom: parent.bottom
|
|
1093
|
+
pane: root
|
|
1094
|
+
menu: menu
|
|
1095
|
+
|
|
1096
|
+
// List only computes the clamp and the thumbnail plan; cursorIndex and thumbState are Pane's own to write.
|
|
1097
|
+
onCursorClamped: function (first, last) { Filter.clampCursor(root, first, last) }
|
|
1098
|
+
onThumbsApplied: function (work) { root.thumbState = Thumbs.applied(root.thumbState, work) }
|
|
1099
|
+
onDirSizesApplied: function (ask) { root.dirSizeState = DirSizes.applied(root.dirSizeState, ask) }
|
|
1100
|
+
onDirSizesCancelled: root.dirSizeState = DirSizes.cancelled(root.dirSizeState)
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
Rectangle {
|
|
1104
|
+
anchors { top: panePath.bottom; bottom: parent.bottom; left: railHost.right }
|
|
1105
|
+
visible: root.dualMode && root.paneFocused
|
|
1106
|
+
width: Theme.spacing.hairline * 2
|
|
1107
|
+
color: Theme.color.accent
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
Flea.TrashHost {
|
|
1111
|
+
id: trashHost
|
|
1112
|
+
pane: root
|
|
1113
|
+
overlayParent: root.overlayParent
|
|
1114
|
+
anchors { top: parent.top; left: railHost.right; right: parent.right; bottom: parent.bottom }
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// The two menu rows open the pane through the window's host with the half they came from chosen.
|
|
1118
|
+
function openFlashImage(path) { root.overlayParent.openFlash(root, path, "") }
|
|
1119
|
+
function openFlashDrive(device) { root.overlayParent.openFlash(root, "", device) }
|
|
1120
|
+
|
|
1121
|
+
// Directory cursors retain the installed provider with its explicit file-only reason.
|
|
1122
|
+
readonly property var cursorRow: root.rowFor(root.cursorIndex)
|
|
1123
|
+
readonly property alias taildropService: wire.taildrop
|
|
1124
|
+
readonly property alias opener: wire.opener
|
|
1125
|
+
readonly property var dropboxService: root.networkService
|
|
1126
|
+
// The keymap sheet's query asks the menu's own rows, so the values stay live for as long as the sheet is open.
|
|
1127
|
+
readonly property bool sheetReadsMenu: root.keymapSheet !== null && root.keymapSheet.opened === true
|
|
1128
|
+
readonly property bool menuValuesLive: menu.preparing || menu.opened || root.sheetReadsMenu
|
|
1129
|
+
readonly property var menuPermissionRow: root.menuValuesLive ? root.permissionSelection() : null
|
|
1130
|
+
|
|
1131
|
+
Flea.MenuHost {
|
|
1132
|
+
id: menu
|
|
1133
|
+
pane: root
|
|
1134
|
+
parent: root.overlayParent || root
|
|
1135
|
+
focusOwner: root.listArea
|
|
1136
|
+
showHidden: root.showHidden
|
|
1137
|
+
providersRefreshing: menuActions.providersRefreshing
|
|
1138
|
+
taildropPeers: wire.taildrop.peers
|
|
1139
|
+
rowIsFile: root.cursorRow !== null && !root.cursorRow.d
|
|
1140
|
+
taildropInstalled: !root.backend.providers.taildrop || root.backend.providers.taildrop.installed !== false
|
|
1141
|
+
taildropReason: root.cursorRow && root.cursorRow.d ? "Taildrop sends files only" : wire.taildrop.reason
|
|
1142
|
+
taildropCode: wire.taildrop.code
|
|
1143
|
+
archiveFormats: root.backend.archiveFormats
|
|
1144
|
+
canConvert: root.backend.canConvert
|
|
1145
|
+
canExtract: root.menuValuesLive && root.cursorRow !== null
|
|
1146
|
+
&& Archive.canExtract(root.cursorRow.n, root.backend.extraction)
|
|
1147
|
+
archiveName: root.cursorRow ? root.cursorRow.n : ""
|
|
1148
|
+
rowMode: root.menuPermissionRow ? root.menuPermissionRow.p : 0
|
|
1149
|
+
selectionCount: root.menuValuesLive ? Ops.targetIndices(root).length : 0
|
|
1150
|
+
// Held kinds answer at once; the menu snapshot carries kinds for marks outside the window.
|
|
1151
|
+
compareKinds: root.menuValuesLive ? Ops.menuKinds(root) : []
|
|
1152
|
+
cursorIsTarget: root.menuValuesLive && root.isSingleCursorTarget()
|
|
1153
|
+
openWithApps: menuActions.openWithApps
|
|
1154
|
+
openWithLoaded: menuActions.openWithLoaded
|
|
1155
|
+
identitySource: root
|
|
1156
|
+
clipboardAvailable: root.clipboard.paths.length > 0
|
|
1157
|
+
clipboardWatchFailed: root.clipboardWatchFailed
|
|
1158
|
+
// Scripts, send peers and the shebang are requested only when a row menu opens.
|
|
1159
|
+
onSnapshotRequested: { menuActions.snapshot(); Flea.Scripts.refresh(); menuActions.localSend.refresh(menu.localSend.installed); root.checkShebang(); if (root.canTag) root.tags().ask() }
|
|
1160
|
+
onRefused: function(reason) { root.message(reason, true) }
|
|
1161
|
+
rowIsArchive: root.menuValuesLive && root.cursorRow !== null && !root.cursorRow.d && Archive.isArchive(root.cursorRow.n)
|
|
1162
|
+
rowIsImage: root.cursorRow !== null && root.cursorRow.i === "image-x-generic"
|
|
1163
|
+
// Flash to drive reads the name, not the icon: only the name carries the ledger's suffixes.
|
|
1164
|
+
rowName: root.cursorRow !== null && !root.cursorRow.d ? root.cursorRow.n : ""
|
|
1165
|
+
flashZipMember: root.flashZipMember
|
|
1166
|
+
// MenuAdditions040: Show original is visible but only on a symlink.
|
|
1167
|
+
rowIsSymlink: root.cursorRow !== null && Format.isSymlink(root.cursorRow.p)
|
|
1168
|
+
rowHasShebang: root.rowHasShebang
|
|
1169
|
+
// Permissions carries every target's mode only while the menu needs it.
|
|
1170
|
+
selectionModes: root.menuValuesLive ? root.permissionModes() : []
|
|
1171
|
+
dropboxInstalled: !root.backend.providers.dropbox || root.backend.providers.dropbox.installed !== false
|
|
1172
|
+
localSend: ({ installed: (root.backend.providers.localsend || {}).installed === true, checking: menuActions.localSend.checking,
|
|
1173
|
+
peers: (root.cursorRow && !root.cursorRow.d) ? menuActions.localSend.peers : [], answeredOnce: menuActions.localSend.answeredOnce })
|
|
1174
|
+
dropboxPath: root.dropboxService && root.dropboxService.dropboxReady ? root.dropboxService.dropboxPath : ""
|
|
1175
|
+
dropboxReason: root.dropboxService ? root.dropboxService.dropboxReason : "Dropbox is not available"
|
|
1176
|
+
dropboxCode: root.dropboxService ? root.dropboxService.dropboxCode : ""
|
|
1177
|
+
rowInDropbox: root.menuValuesLive && root.dropboxService && root.cursorRow
|
|
1178
|
+
&& Dropbox.contains(root.dropboxService.dropboxPath, root.join(Search.baseOf(root), root.cursorRow.n))
|
|
1179
|
+
// Issue 133: no GVFS mount, share or phone, has a trash of its own, so the row is not offered there.
|
|
1180
|
+
// A read-only folder has none either: the listed line's own writability rides along.
|
|
1181
|
+
canTrash: Mounts.trashable(root.path, root.backend ? root.backend.dirWritable !== false : true)
|
|
1182
|
+
// The listing's own w flag turns the menu's write rows off in a read-only folder.
|
|
1183
|
+
dirWritable: root.backend ? root.backend.dirWritable !== false : true
|
|
1184
|
+
// vfat and exfat hold no links, so the Paste as rows are not offered there either.
|
|
1185
|
+
canLink: root.fsName !== "vfat" && root.fsName !== "exfat"
|
|
1186
|
+
// The Tags flyout stands only while a menu is live; null drops the row where tags cannot be stored.
|
|
1187
|
+
tagSubmenu: root.menuValuesLive && root.canTag && root.tagState !== null ? root.tagState.flyout() : null
|
|
1188
|
+
// ExtThumbs: the class the background menu's thumbnail row is present for, "" locally.
|
|
1189
|
+
storageClass: root.storageClass
|
|
1190
|
+
// Issue 179: the Sort by flyout offers its forget row only where this folder has its own sort.
|
|
1191
|
+
hasFolderSort: root.menuValuesLive && root.searchMode.length === 0 && root.backend ? root.backend.folderHasSort(root.path) : false
|
|
1192
|
+
// The Locked tile's folder and mode while one is drawn; ui/ContextMenu.qml routes a
|
|
1193
|
+
// background right click to that folder's own menu through them.
|
|
1194
|
+
tileTarget: root.lockedTarget
|
|
1195
|
+
tileMode: root.lockedMode
|
|
1196
|
+
onChosen: function (action) {
|
|
1197
|
+
// Issue #170: Rename opens the editor at once by the route F2 takes
|
|
1198
|
+
// rather than queueing an activate behind the cold Open-with catalogue.
|
|
1199
|
+
if (action === "rename") { if (!root.openBulkRename(null)) menuActions.openRenameFromMenu(); return }
|
|
1200
|
+
menuActions.activate(action, menu.hasRow && !menu.forHeader)
|
|
1201
|
+
}
|
|
1202
|
+
// The Locked tile's folder arrives with the row: close() cleared the menu's own copy
|
|
1203
|
+
// before this runs, so reading menu.lockedPath here would always see "" and every row
|
|
1204
|
+
// would fall into the background dispatch above.
|
|
1205
|
+
onLockedChosen: function (action, path) { root.performLocked(path, action) }
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
Flea.PaneMenuActions {
|
|
1209
|
+
id: menuActions
|
|
1210
|
+
parent: root.overlayParent || root
|
|
1211
|
+
pane: root
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// Every paste and drop asks through here first, see ui/js/Collide.js; built by the first ask and owned by the pane, so a closed tab takes its card.
|
|
1215
|
+
property var collide: null
|
|
1216
|
+
function asks() {
|
|
1217
|
+
var made = root.collide ? null : Qt.createComponent("CollideHost.qml")
|
|
1218
|
+
if (made && made.status !== Component.Ready) throw new Error("CollideHost.qml did not load: " + made.errorString())
|
|
1219
|
+
return root.collide || (root.collide = made.createObject(root, { pane: root, parent: root.overlayParent || root }))
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// Opening an executable file asks once, in the window's one card; see ui/WindowBody.qml.
|
|
1223
|
+
// A null host is a loud refusal, never a call into nothing; every product pane names one.
|
|
1224
|
+
readonly property string noWindowSentence: "This pane has no window, so nothing was done."
|
|
1225
|
+
function askRunCard(info) {
|
|
1226
|
+
if (!root.overlayParent) { root.message(root.noWindowSentence, true); return }
|
|
1227
|
+
root.overlayParent.askRunCard(root, info)
|
|
1228
|
+
}
|
|
1229
|
+
function runText(path) {
|
|
1230
|
+
if (!root.overlayParent) { root.message(root.noWindowSentence, true); return }
|
|
1231
|
+
root.overlayParent.runLane().openText(path, root)
|
|
1232
|
+
}
|
|
1233
|
+
// Without a path Open with opens over the cursor; with one the card snapshots the row that path still names.
|
|
1234
|
+
function showOpenWith(path) {
|
|
1235
|
+
if (path === undefined) { menuActions.open("openWith", 0); return }
|
|
1236
|
+
var at = Anchor.matchListed(root, path)
|
|
1237
|
+
if (at < 0) { root.message("That file is no longer listed.", true); return }
|
|
1238
|
+
menuActions.snapshot([at])
|
|
1239
|
+
menuActions.open("openWith", menuActions.requestId)
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// The one ui/ContextMenu.qml this pane owns, for ui/Ipc.qml: entries, flyout and row geometry
|
|
1243
|
+
// are read off it directly, so a new reader costs the seam a line and this file none.
|
|
1244
|
+
function contextMenu() { return menu.item || menu }
|
|
1245
|
+
|
|
1246
|
+
Flea.PaneStates {
|
|
1247
|
+
id: paneStates
|
|
1248
|
+
pane: root
|
|
1249
|
+
trashOpen: trashHost.opened
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function openConvert(menuId) { Ops.openConvert(root, menuId) }
|
|
1253
|
+
function moveToDropbox(menuId) { Ops.moveToDropbox(root, root.dropboxService && root.dropboxService.dropboxReady ? root.dropboxService.dropboxPath : "", menuId) }
|
|
1254
|
+
// The three foreign programs live in ui/PaneWire.qml with the backend's replies; these only name the row.
|
|
1255
|
+
function copyShareLink(path) {
|
|
1256
|
+
if (!path) { root.message("That menu is out of date. Reopen it.", true); return }
|
|
1257
|
+
wire.shareLink.copy(path)
|
|
1258
|
+
}
|
|
1259
|
+
function sendTaildrop(peerId, path) { Ops.sendTaildrop(root, wire.taildrop, peerId, path) }
|
|
1260
|
+
|
|
1261
|
+
// The Locked tile's own dispatch: every row its menu offers acts on the locked folder
|
|
1262
|
+
// alone, by path, never on the cursor, the selection or the parent standing behind it.
|
|
1263
|
+
// The menu row is already disabled where it cannot act, so no row here can fail refused.
|
|
1264
|
+
function performLocked(path, action) {
|
|
1265
|
+
if (path.length === 0) return
|
|
1266
|
+
if (action === "openTerminal") { wire.opener.openTerminal(path); return }
|
|
1267
|
+
if (action === "copypath") { wire.opener.copyText(path); return }
|
|
1268
|
+
// MenuAdditions040: the Locked tile offers Copy as over the one folder
|
|
1269
|
+
// it names, each leaf copying that path in its own form.
|
|
1270
|
+
if (action.indexOf("copyAs:") === 0) {
|
|
1271
|
+
var leaf = action.substring("copyAs:".length)
|
|
1272
|
+
var kinds = { copyPath: "path", copyName: "name", copyStem: "stem",
|
|
1273
|
+
copydirpath: "dirpath", copyUri: "uri", copyQuoted: "quoted" }
|
|
1274
|
+
if (kinds[leaf] !== undefined) { Ops.copyAs(root, kinds[leaf], [path]); return }
|
|
1275
|
+
}
|
|
1276
|
+
if (action === "copyAs") { Ops.copyAs(root, "path", [path]); return }
|
|
1277
|
+
if (action === "permissions") { root.permissionsRequested(path); return }
|
|
1278
|
+
root.message("That action is not available yet", false)
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// The keyboard's own entrance to the row menu; the placement itself is ui/js/Menu.js's.
|
|
1282
|
+
function openCursorMenu() {
|
|
1283
|
+
if (root.lockedTarget.length > 0) {
|
|
1284
|
+
var at = root.listSlot.mapToItem(null, root.listSlot.width / 2, root.listSlot.height / 2)
|
|
1285
|
+
menu.openLocked(root.lockedTarget, root.lockedMode, at)
|
|
1286
|
+
return true
|
|
1287
|
+
}
|
|
1288
|
+
return Menu.openAtCursor(root, menu, Theme.spacing.rowPaddingX)
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// The query lines' paste source (issue 287), read only when Ctrl+V or Shift+Insert reaches one.
|
|
1292
|
+
// flea-qml: buninu-browser reads its clipboard asynchronously and offers readClipboardText's
|
|
1293
|
+
// Promise for it; Quickshell itself answers the text at once.
|
|
1294
|
+
function clipboardText() { return Quickshell.readClipboardText ? Quickshell.readClipboardText() : Quickshell.clipboardText }
|
|
1295
|
+
|
|
1296
|
+
}
|