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.
Files changed (625) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +478 -2
  3. package/THIRD-PARTY-NOTICES.md +29 -0
  4. package/demos/App.java +5 -0
  5. package/demos/Dockerfile +4 -0
  6. package/demos/Main.kt +3 -0
  7. package/demos/Program.cs +1 -0
  8. package/demos/README.md +12 -0
  9. package/demos/app.dart +3 -0
  10. package/demos/app.deb +0 -0
  11. package/demos/app.js +1 -0
  12. package/demos/backup.tar +0 -0
  13. package/demos/budget.xlsx +0 -0
  14. package/demos/build.sh +2 -0
  15. package/demos/bundle.zip +0 -0
  16. package/demos/clip.mp4 +0 -0
  17. package/demos/data.sqlite +0 -0
  18. package/demos/disk.iso +0 -0
  19. package/demos/engine.cpp +5 -0
  20. package/demos/firmware.bin +0 -0
  21. package/demos/flake.nix +4 -0
  22. package/demos/folder/inside.txt +1 -0
  23. package/demos/font.ttf +0 -0
  24. package/demos/index.html +5 -0
  25. package/demos/index.php +2 -0
  26. package/demos/init.lua +1 -0
  27. package/demos/letter.docx +0 -0
  28. package/demos/lib.rs +3 -0
  29. package/demos/main.c +6 -0
  30. package/demos/main.h +3 -0
  31. package/demos/math-and-diagrams.md +200 -0
  32. package/demos/notes.tar.gz +0 -0
  33. package/demos/notes.txt +3 -0
  34. package/demos/photo.png +0 -0
  35. package/demos/program +2 -0
  36. package/demos/report.pdf +32 -0
  37. package/demos/script.py +1 -0
  38. package/demos/server.go +7 -0
  39. package/demos/settings.json +4 -0
  40. package/demos/slides.pptx +0 -0
  41. package/demos/song.mp3 +0 -0
  42. package/demos/style.css +4 -0
  43. package/demos/tool.rb +1 -0
  44. package/demos/types.ts +2 -0
  45. package/docs/pele.md +23 -0
  46. package/docs/protocol.md +3420 -0
  47. package/docs/temml.md +63 -0
  48. package/index.js +107 -0
  49. package/package.json +34 -4
  50. package/src/archive.js +402 -0
  51. package/src/archiveops.js +697 -0
  52. package/src/backend.js +369 -0
  53. package/src/bulkrename.js +95 -0
  54. package/src/clip-host.js +76 -0
  55. package/src/clip.js +462 -0
  56. package/src/code.js +168 -0
  57. package/src/compare.js +74 -0
  58. package/src/create.js +128 -0
  59. package/src/dirsize.js +147 -0
  60. package/src/figure-svg.js +530 -0
  61. package/src/figure-thread.js +23 -0
  62. package/src/figure.js +132 -0
  63. package/src/find.js +55 -0
  64. package/src/flea.js +39 -0
  65. package/src/fsinfo.js +39 -0
  66. package/src/highlight.js +141 -0
  67. package/src/html.js +52 -0
  68. package/src/kinds.js +89 -0
  69. package/src/listing.js +130 -0
  70. package/src/markdown-html.js +317 -0
  71. package/src/mathml.js +186 -0
  72. package/src/menu.js +180 -0
  73. package/src/meta.js +129 -0
  74. package/src/office.js +810 -0
  75. package/src/order.js +100 -0
  76. package/src/peek.js +49 -0
  77. package/src/pele-wrapper.js +695 -0
  78. package/src/permissions.js +180 -0
  79. package/src/places.js +313 -0
  80. package/src/preview-language.js +10 -0
  81. package/src/transfer.js +669 -0
  82. package/src/trash-record.js +41 -0
  83. package/src/trash.js +210 -0
  84. package/src/trashbrowse.js +326 -0
  85. package/src/uistate.js +393 -0
  86. package/src/uistore.js +168 -0
  87. package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
  88. package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
  89. package/src/vendor/LICENSES/MIT-katex.txt +21 -0
  90. package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
  91. package/src/vendor/LICENSES/MIT-pele.txt +40 -0
  92. package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
  93. package/src/vendor/LICENSES/MIT-temml.txt +21 -0
  94. package/src/vendor/pele.js +145 -0
  95. package/src/vendor/temml.mjs +14708 -0
  96. package/src/watch.js +98 -0
  97. package/src/wayland/client.js +109 -0
  98. package/src/wayland/protocol.js +55 -0
  99. package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
  100. package/src/wayland/protocols/wayland.xml +3248 -0
  101. package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
  102. package/src/wayland/resource-io.js +6 -0
  103. package/src/wayland/socket-discovery.js +26 -0
  104. package/src/wayland/transport-posix.js +120 -0
  105. package/src/wayland/transport-win32.js +11 -0
  106. package/src/wayland/transport.js +3 -0
  107. package/src/wayland/win32-resources.js +80 -0
  108. package/src/wayland/win32-wire.js +62 -0
  109. package/test/archive.test.js +80 -0
  110. package/test/bench-markdown-scroll.mjs +190 -0
  111. package/test/clip-host.test.js +113 -0
  112. package/test/code-preview.test.js +35 -0
  113. package/test/compare-ui-paths.test.js +24 -0
  114. package/test/compare.test.js +54 -0
  115. package/test/delete-trash.test.js +67 -0
  116. package/test/dirsize.test.js +138 -0
  117. package/test/file-ops.test.js +241 -0
  118. package/test/find-contents.test.js +161 -0
  119. package/test/find-input.test.js +66 -0
  120. package/test/fixtures/pele-parity.json +1006 -0
  121. package/test/highlight-launcher.test.js +35 -0
  122. package/test/html-preview.test.js +22 -0
  123. package/test/input.test.js +15 -0
  124. package/test/keymap.test.js +26 -0
  125. package/test/language-icons.test.js +36 -0
  126. package/test/launcher.test.js +51 -0
  127. package/test/manual-code-scroll.mjs +28 -0
  128. package/test/manual-column-scroll.mjs +24 -0
  129. package/test/manual-compare-ui.mjs +20 -0
  130. package/test/manual-find-status.mjs +37 -0
  131. package/test/manual-html-security.mjs +23 -0
  132. package/test/manual-html-ui.mjs +30 -0
  133. package/test/manual-syntax-preview.mjs +26 -0
  134. package/test/manual-trash-ui.mjs +86 -0
  135. package/test/markdown-diagrams.test.js +93 -0
  136. package/test/markdown-html.test.js +105 -0
  137. package/test/mathml.test.js +92 -0
  138. package/test/menu-transfer.test.js +57 -0
  139. package/test/office-preview.test.js +154 -0
  140. package/test/pele-parity.test.js +28 -0
  141. package/test/places.test.js +134 -0
  142. package/test/preview-keys.test.js +60 -0
  143. package/test/preview-language.test.js +22 -0
  144. package/test/status-filename.test.js +20 -0
  145. package/test/transfer-replace-merge.test.js +187 -0
  146. package/test/trash-address.test.js +23 -0
  147. package/test/trash-view.test.js +37 -0
  148. package/test/trashbrowse.test.js +135 -0
  149. package/ui/AboutFacts.qml +78 -0
  150. package/ui/ArchiveCards.qml +68 -0
  151. package/ui/ArchiveCompressDialog.qml +378 -0
  152. package/ui/ArchivePasswordDialog.qml +257 -0
  153. package/ui/Backend.qml +771 -0
  154. package/ui/BulkOldNames.qml +47 -0
  155. package/ui/BulkRenameDialog.qml +380 -0
  156. package/ui/CardFrame.qml +67 -0
  157. package/ui/CardScroll.qml +79 -0
  158. package/ui/CheckBox.qml +73 -0
  159. package/ui/ChromeBar.qml +360 -0
  160. package/ui/ChromeButton.qml +131 -0
  161. package/ui/ChromeModes.qml +43 -0
  162. package/ui/CodeLines.qml +256 -0
  163. package/ui/CodeRefusal.qml +52 -0
  164. package/ui/CollideConfirm.qml +147 -0
  165. package/ui/CollideHost.qml +167 -0
  166. package/ui/ColumnPane.qml +523 -0
  167. package/ui/ColumnRow.qml +380 -0
  168. package/ui/ColumnsArea.qml +594 -0
  169. package/ui/ColumnsLanding.qml +147 -0
  170. package/ui/CompareView.qml +321 -0
  171. package/ui/ContextMenu.qml +743 -0
  172. package/ui/ConvertDialog.qml +456 -0
  173. package/ui/CountedWorker.qml +7 -0
  174. package/ui/Crumb.qml +64 -0
  175. package/ui/CrumbDrop.qml +25 -0
  176. package/ui/CsvTable.qml +394 -0
  177. package/ui/CursorMark.qml +41 -0
  178. package/ui/DefaultClaim.qml +130 -0
  179. package/ui/DeviceLock.qml +47 -0
  180. package/ui/DeviceMounts.qml +540 -0
  181. package/ui/DialogButton.qml +150 -0
  182. package/ui/DialogField.qml +147 -0
  183. package/ui/DialogHeader.qml +31 -0
  184. package/ui/DialogScrim.qml +20 -0
  185. package/ui/DialogTitle.qml +37 -0
  186. package/ui/Divider.qml +9 -0
  187. package/ui/DropInto.qml +126 -0
  188. package/ui/DropTarget.qml +35 -0
  189. package/ui/DropboxMark.qml +60 -0
  190. package/ui/EmptyState.qml +150 -0
  191. package/ui/EscapeHint.qml +11 -0
  192. package/ui/FactsTable.qml +54 -0
  193. package/ui/FastScrollHandler.qml +476 -0
  194. package/ui/FatNamesConfirm.qml +157 -0
  195. package/ui/Favourites.qml +143 -0
  196. package/ui/FigureService.qml +120 -0
  197. package/ui/FileDrag.qml +267 -0
  198. package/ui/FilterStrip.qml +133 -0
  199. package/ui/FitMetrics.qml +22 -0
  200. package/ui/FlashDriveRows.qml +137 -0
  201. package/ui/FlashHost.qml +72 -0
  202. package/ui/FlashImageRow.qml +104 -0
  203. package/ui/FlashRunner.qml +119 -0
  204. package/ui/FlashStepRow.qml +172 -0
  205. package/ui/FlashStrip.qml +38 -0
  206. package/ui/FlashView.qml +397 -0
  207. package/ui/FleaMark.qml +88 -0
  208. package/ui/FlyoutFade.qml +23 -0
  209. package/ui/FontPane.qml +170 -0
  210. package/ui/Glass.qml +114 -0
  211. package/ui/GlassTagger.qml +59 -0
  212. package/ui/Glyph.qml +64 -0
  213. package/ui/GlyphSolid.qml +22 -0
  214. package/ui/GridArea.qml +664 -0
  215. package/ui/GridTile.qml +325 -0
  216. package/ui/GvfsBridge.qml +164 -0
  217. package/ui/Header.qml +433 -0
  218. package/ui/HoverTimers.qml +39 -0
  219. package/ui/HtmlClip.qml +30 -0
  220. package/ui/HtmlPane.qml +63 -0
  221. package/ui/HyprlandMark.qml +45 -0
  222. package/ui/Ipc.qml +1005 -0
  223. package/ui/IpcControls.qml +7 -0
  224. package/ui/IpcMenuState.qml +48 -0
  225. package/ui/IpcPreviewState.qml +185 -0
  226. package/ui/JumpPath.qml +51 -0
  227. package/ui/KeymapSheet.qml +561 -0
  228. package/ui/KeymapSheetResult.qml +89 -0
  229. package/ui/KindStack.qml +40 -0
  230. package/ui/LangMark.qml +51 -0
  231. package/ui/LangMarkLoader.qml +26 -0
  232. package/ui/List.qml +480 -0
  233. package/ui/LoadingState.qml +54 -0
  234. package/ui/LocalSend.qml +56 -0
  235. package/ui/LocalSendMark.qml +73 -0
  236. package/ui/MarkdownBlockView.qml +311 -0
  237. package/ui/MarkdownBlocks.qml +35 -0
  238. package/ui/MarkdownFigure.qml +254 -0
  239. package/ui/MarkdownImages.qml +72 -0
  240. package/ui/MarkdownList.qml +81 -0
  241. package/ui/MarkdownMathsText.qml +97 -0
  242. package/ui/MarkdownPane.qml +83 -0
  243. package/ui/MarkdownPictures.qml +41 -0
  244. package/ui/MarkdownQuote.qml +52 -0
  245. package/ui/MarkdownRemote.qml +82 -0
  246. package/ui/MarkdownSource.qml +155 -0
  247. package/ui/MarkdownTable.qml +222 -0
  248. package/ui/MarkdownTableScroll.qml +64 -0
  249. package/ui/MarkdownTableWheel.qml +126 -0
  250. package/ui/MarkdownText.qml +87 -0
  251. package/ui/MarkdownWorker.js +132 -0
  252. package/ui/MatchText.qml +99 -0
  253. package/ui/MediaSession.qml +38 -0
  254. package/ui/MediaSound.qml +26 -0
  255. package/ui/MediaStrip.qml +191 -0
  256. package/ui/MediaTrack.qml +36 -0
  257. package/ui/MediaVolume.qml +54 -0
  258. package/ui/MenuActionDialog.qml +396 -0
  259. package/ui/MenuEdgeFade.qml +24 -0
  260. package/ui/MenuHost.qml +227 -0
  261. package/ui/MenuRow.qml +351 -0
  262. package/ui/MountListing.qml +100 -0
  263. package/ui/NetworkDialog.qml +435 -0
  264. package/ui/NetworkForm.qml +426 -0
  265. package/ui/NetworkMounts.qml +913 -0
  266. package/ui/NetworkPlaces.qml +127 -0
  267. package/ui/OpenWithDialog.qml +613 -0
  268. package/ui/Opener.qml +115 -0
  269. package/ui/Pane.qml +1296 -0
  270. package/ui/PaneMenuActions.qml +527 -0
  271. package/ui/PanePath.qml +90 -0
  272. package/ui/PaneRail.qml +239 -0
  273. package/ui/PaneStates.qml +88 -0
  274. package/ui/PaneSwap.qml +339 -0
  275. package/ui/PaneWire.qml +605 -0
  276. package/ui/ParkedWait.qml +9 -0
  277. package/ui/PathEntry.qml +186 -0
  278. package/ui/PathJump.qml +400 -0
  279. package/ui/PdfViewer.qml +297 -0
  280. package/ui/PermissionsDialog.qml +658 -0
  281. package/ui/PermissionsHeadings.qml +31 -0
  282. package/ui/PhoneMounts.qml +67 -0
  283. package/ui/PickerAnswers.qml +75 -0
  284. package/ui/PickerBox.qml +26 -0
  285. package/ui/PickerChrome.qml +295 -0
  286. package/ui/PickerFilter.qml +97 -0
  287. package/ui/PickerGrid.qml +308 -0
  288. package/ui/PickerHeader.qml +28 -0
  289. package/ui/PickerLifecycle.qml +27 -0
  290. package/ui/PickerList.qml +218 -0
  291. package/ui/PickerListing.qml +149 -0
  292. package/ui/PickerPathBar.qml +127 -0
  293. package/ui/PickerPlaces.qml +189 -0
  294. package/ui/PickerRecent.qml +92 -0
  295. package/ui/PickerSave.qml +101 -0
  296. package/ui/PickerSelection.qml +155 -0
  297. package/ui/PickerWindow.qml +846 -0
  298. package/ui/PowerSectorsReader.qml +10 -0
  299. package/ui/PrebuildDriver.qml +146 -0
  300. package/ui/Preview.qml +1004 -0
  301. package/ui/PreviewAnimation.qml +24 -0
  302. package/ui/PreviewArchive.qml +66 -0
  303. package/ui/PreviewColumn.qml +551 -0
  304. package/ui/PreviewEdge.qml +63 -0
  305. package/ui/PreviewFacts.qml +30 -0
  306. package/ui/PreviewFactsLine.qml +34 -0
  307. package/ui/PreviewHtml.qml +233 -0
  308. package/ui/PreviewImage.qml +253 -0
  309. package/ui/PreviewImagePan.qml +13 -0
  310. package/ui/PreviewImageSharp.qml +32 -0
  311. package/ui/PreviewLines.qml +106 -0
  312. package/ui/PreviewMarkdown.qml +706 -0
  313. package/ui/PreviewMedia.qml +250 -0
  314. package/ui/PreviewPdf.qml +482 -0
  315. package/ui/PreviewScroll.qml +201 -0
  316. package/ui/PreviewSwap.qml +335 -0
  317. package/ui/PreviewText.qml +149 -0
  318. package/ui/ProtocolChip.qml +18 -0
  319. package/ui/QuickLookArchive.qml +38 -0
  320. package/ui/QuickLookBar.qml +103 -0
  321. package/ui/QuickLookPrepare.qml +355 -0
  322. package/ui/QuickLookSwap.qml +10 -0
  323. package/ui/QuickLookWarm.qml +65 -0
  324. package/ui/RailDrop.qml +155 -0
  325. package/ui/RailHeading.qml +19 -0
  326. package/ui/RailPlaces.qml +38 -0
  327. package/ui/RailTrashRow.qml +67 -0
  328. package/ui/RenameField.qml +353 -0
  329. package/ui/RendererRetry.qml +11 -0
  330. package/ui/ResizeHandle.qml +52 -0
  331. package/ui/Reveal.qml +54 -0
  332. package/ui/Row.qml +476 -0
  333. package/ui/RowDrag.qml +213 -0
  334. package/ui/RowDropTarget.qml +27 -0
  335. package/ui/RunCard.qml +286 -0
  336. package/ui/Scripts.qml +65 -0
  337. package/ui/SearchStrip.qml +232 -0
  338. package/ui/SelectionBand.qml +140 -0
  339. package/ui/SelectionPreview.qml +370 -0
  340. package/ui/SettingsFavourite.qml +122 -0
  341. package/ui/SettingsGroup.qml +96 -0
  342. package/ui/SettingsHint.qml +12 -0
  343. package/ui/SettingsMetrics.qml +29 -0
  344. package/ui/SettingsPane.qml +178 -0
  345. package/ui/SettingsPanel.qml +547 -0
  346. package/ui/SettingsRail.qml +128 -0
  347. package/ui/SettingsRow.qml +409 -0
  348. package/ui/SettingsRuler.qml +85 -0
  349. package/ui/SettingsSegment.qml +85 -0
  350. package/ui/ShareBrowser.qml +113 -0
  351. package/ui/ShareLink.qml +67 -0
  352. package/ui/ShelfPins.qml +101 -0
  353. package/ui/Sidebar.qml +732 -0
  354. package/ui/SidebarRow.qml +358 -0
  355. package/ui/Spinner.qml +60 -0
  356. package/ui/StableModel.qml +89 -0
  357. package/ui/StateMessage.qml +84 -0
  358. package/ui/StatusBar.qml +512 -0
  359. package/ui/StatusFileName.qml +27 -0
  360. package/ui/TabBar.qml +673 -0
  361. package/ui/TabDragGeometry.qml +60 -0
  362. package/ui/TagMenuMark.qml +52 -0
  363. package/ui/TagRailSection.qml +53 -0
  364. package/ui/TagSlot.qml +40 -0
  365. package/ui/TagState.qml +150 -0
  366. package/ui/TagSwatch.qml +28 -0
  367. package/ui/Taildrop.qml +86 -0
  368. package/ui/TailscaleMark.qml +50 -0
  369. package/ui/Theme.qml +460 -0
  370. package/ui/ThemeColors.qml +43 -0
  371. package/ui/TransferCard.qml +239 -0
  372. package/ui/TrashConfirm.qml +129 -0
  373. package/ui/TrashDropHandler.qml +11 -0
  374. package/ui/TrashHost.qml +202 -0
  375. package/ui/TrashMonitor.qml +30 -0
  376. package/ui/TrashToolbar.qml +19 -0
  377. package/ui/TrashView.qml +492 -0
  378. package/ui/UnlockDialog.qml +450 -0
  379. package/ui/UnlockHost.qml +206 -0
  380. package/ui/UpdateCheck.qml +97 -0
  381. package/ui/UserDirs.qml +25 -0
  382. package/ui/ViewState.qml +565 -0
  383. package/ui/ViewportScrollBar.qml +195 -0
  384. package/ui/ViewportScrollBars.qml +25 -0
  385. package/ui/WebPreview.qml +33 -0
  386. package/ui/WebPreviewView.qml +26 -0
  387. package/ui/WindowBody.qml +827 -0
  388. package/ui/WindowDrag.qml +35 -0
  389. package/ui/boot/Commons/AudioNodes.qml +50 -0
  390. package/ui/boot/Commons/AudioNodesModel.js +96 -0
  391. package/ui/boot/Commons/Border.qml +243 -0
  392. package/ui/boot/Commons/BorderGeometry.js +373 -0
  393. package/ui/boot/Commons/Color.qml +258 -0
  394. package/ui/boot/Commons/I18n.qml +118 -0
  395. package/ui/boot/Commons/InputDevices.qml +112 -0
  396. package/ui/boot/Commons/InputMethodState.qml +38 -0
  397. package/ui/boot/Commons/IpcRegistry.qml +77 -0
  398. package/ui/boot/Commons/Numbers.js +19 -0
  399. package/ui/boot/Commons/Plural.js +91 -0
  400. package/ui/boot/Commons/ShellIpc.qml +12 -0
  401. package/ui/boot/Commons/Style.qml +561 -0
  402. package/ui/boot/Commons/Translations.qml +100 -0
  403. package/ui/boot/Commons/UntypedInput.qml +152 -0
  404. package/ui/boot/Commons/Util.qml +188 -0
  405. package/ui/boot/Commons/qmldir +13 -0
  406. package/ui/boot/LICENSE-omarchy +20 -0
  407. package/ui/boot/Ui/BackgroundMedia.qml +49 -0
  408. package/ui/boot/Ui/BarIconButton.qml +106 -0
  409. package/ui/boot/Ui/BarIconContent.qml +38 -0
  410. package/ui/boot/Ui/BarIndicator.qml +53 -0
  411. package/ui/boot/Ui/BarWidget.qml +50 -0
  412. package/ui/boot/Ui/BorderOverlay.qml +54 -0
  413. package/ui/boot/Ui/BorderSurface.qml +40 -0
  414. package/ui/boot/Ui/Button.qml +224 -0
  415. package/ui/boot/Ui/ButtonGroup.qml +131 -0
  416. package/ui/boot/Ui/ConfirmDialog.qml +134 -0
  417. package/ui/boot/Ui/CursorSurface.qml +42 -0
  418. package/ui/boot/Ui/Dropdown.qml +245 -0
  419. package/ui/boot/Ui/FilterInput.qml +26 -0
  420. package/ui/boot/Ui/IconContentBounds.qml +141 -0
  421. package/ui/boot/Ui/KeyboardPanel.qml +453 -0
  422. package/ui/boot/Ui/MultiSelect.qml +624 -0
  423. package/ui/boot/Ui/NumberField.qml +86 -0
  424. package/ui/boot/Ui/OpticalGlyph.qml +68 -0
  425. package/ui/boot/Ui/OverlayWindow.qml +102 -0
  426. package/ui/boot/Ui/Panel.qml +60 -0
  427. package/ui/boot/Ui/PanelActionButton.qml +105 -0
  428. package/ui/boot/Ui/PanelController.qml +16 -0
  429. package/ui/boot/Ui/PanelHero.qml +112 -0
  430. package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
  431. package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
  432. package/ui/boot/Ui/PanelSeparator.qml +19 -0
  433. package/ui/boot/Ui/PanelSlider.qml +156 -0
  434. package/ui/boot/Ui/PanelToolTip.qml +50 -0
  435. package/ui/boot/Ui/PluginBarApi.qml +89 -0
  436. package/ui/boot/Ui/PointerMoveGate.qml +54 -0
  437. package/ui/boot/Ui/PopupCard.qml +181 -0
  438. package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
  439. package/ui/boot/Ui/SearchableDropdown.qml +354 -0
  440. package/ui/boot/Ui/SizedImage.qml +125 -0
  441. package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
  442. package/ui/boot/Ui/TextField.qml +58 -0
  443. package/ui/boot/Ui/Toggle.qml +118 -0
  444. package/ui/boot/Ui/ToggleSwitch.qml +115 -0
  445. package/ui/boot/Ui/WidgetButton.qml +172 -0
  446. package/ui/boot/Ui/qmldir +41 -0
  447. package/ui/boot/fleatab.qml +50 -0
  448. package/ui/boot/picker.qml +39 -0
  449. package/ui/boot/shell.qml +122 -0
  450. package/ui/boot/tabtearoff.qml +51 -0
  451. package/ui/js/Anchor.js +379 -0
  452. package/ui/js/AnchorHold.js +162 -0
  453. package/ui/js/Archive.js +157 -0
  454. package/ui/js/ArchiveCard.js +44 -0
  455. package/ui/js/ArchiveOps.js +44 -0
  456. package/ui/js/BackReturn.js +118 -0
  457. package/ui/js/BulkRename.js +100 -0
  458. package/ui/js/Buttons.js +54 -0
  459. package/ui/js/ChromeKeys.js +116 -0
  460. package/ui/js/ClipMarks.js +73 -0
  461. package/ui/js/ClipSelection.js +67 -0
  462. package/ui/js/Clipboard.js +179 -0
  463. package/ui/js/Cloud.js +97 -0
  464. package/ui/js/CodeRuns.js +72 -0
  465. package/ui/js/Collide.js +212 -0
  466. package/ui/js/ColumnFit.js +28 -0
  467. package/ui/js/ColumnMenu.js +111 -0
  468. package/ui/js/Columns.js +298 -0
  469. package/ui/js/ColumnsCarry.js +103 -0
  470. package/ui/js/Compare.js +74 -0
  471. package/ui/js/Contrast.js +157 -0
  472. package/ui/js/ControlReader.js +42 -0
  473. package/ui/js/Convert.js +52 -0
  474. package/ui/js/CopyAs.js +52 -0
  475. package/ui/js/Crumbs.js +178 -0
  476. package/ui/js/Csv.js +231 -0
  477. package/ui/js/Density.js +44 -0
  478. package/ui/js/Devices.js +440 -0
  479. package/ui/js/DirSizes.js +180 -0
  480. package/ui/js/Drag.js +364 -0
  481. package/ui/js/DragOut.js +166 -0
  482. package/ui/js/DropDev.js +52 -0
  483. package/ui/js/Dropbox.js +41 -0
  484. package/ui/js/Eject.js +217 -0
  485. package/ui/js/Errors.js +188 -0
  486. package/ui/js/ExtThumbs.js +115 -0
  487. package/ui/js/Facts.js +402 -0
  488. package/ui/js/FigureDate.js +14 -0
  489. package/ui/js/Filter.js +285 -0
  490. package/ui/js/Find.js +46 -0
  491. package/ui/js/Flash.js +184 -0
  492. package/ui/js/FlashCard.js +79 -0
  493. package/ui/js/FlashEndings.js +125 -0
  494. package/ui/js/FlashPane.js +116 -0
  495. package/ui/js/FleaBin.js +12 -0
  496. package/ui/js/Focus.js +502 -0
  497. package/ui/js/FolderSorts.js +108 -0
  498. package/ui/js/FolderViews.js +44 -0
  499. package/ui/js/Format.js +305 -0
  500. package/ui/js/Fuzzy.js +141 -0
  501. package/ui/js/GlassTag.js +72 -0
  502. package/ui/js/Grid.js +33 -0
  503. package/ui/js/GridGeometry.js +90 -0
  504. package/ui/js/GridNames.js +153 -0
  505. package/ui/js/GvfsBridge.js +200 -0
  506. package/ui/js/HostClipboard.js +29 -0
  507. package/ui/js/Icons.js +218 -0
  508. package/ui/js/Input.js +50 -0
  509. package/ui/js/Jump.js +200 -0
  510. package/ui/js/KeyName.js +64 -0
  511. package/ui/js/Keymap.js +401 -0
  512. package/ui/js/Kinds.js +164 -0
  513. package/ui/js/LICENSES/ISC-lucide.txt +43 -0
  514. package/ui/js/LangMarkPaths.js +47 -0
  515. package/ui/js/LangMarks.js +120 -0
  516. package/ui/js/Lists.js +11 -0
  517. package/ui/js/LocalSend.js +39 -0
  518. package/ui/js/LockedMenu.js +63 -0
  519. package/ui/js/MakeDefault.js +186 -0
  520. package/ui/js/MarkBox.js +7 -0
  521. package/ui/js/Markdown.js +119 -0
  522. package/ui/js/MarkdownLists.js +86 -0
  523. package/ui/js/MarkdownMaths.js +58 -0
  524. package/ui/js/MarkdownPictures.js +32 -0
  525. package/ui/js/MarkdownPrepared.js +139 -0
  526. package/ui/js/MarkdownTableFit.js +34 -0
  527. package/ui/js/Marks.js +148 -0
  528. package/ui/js/Marquee.js +96 -0
  529. package/ui/js/Match.js +93 -0
  530. package/ui/js/Material.js +109 -0
  531. package/ui/js/MdBlocks.js +437 -0
  532. package/ui/js/MdBreak.js +71 -0
  533. package/ui/js/MdChunks.js +70 -0
  534. package/ui/js/MdContainer.js +137 -0
  535. package/ui/js/MdDocument.js +337 -0
  536. package/ui/js/MdEmph.js +97 -0
  537. package/ui/js/MdEntity.js +108 -0
  538. package/ui/js/MdEntityTable.js +223 -0
  539. package/ui/js/MdEscape.js +59 -0
  540. package/ui/js/MdFront.js +37 -0
  541. package/ui/js/MdHold.js +32 -0
  542. package/ui/js/MdHtml.js +307 -0
  543. package/ui/js/MdHtmlBlock.js +290 -0
  544. package/ui/js/MdHtmlHeading.js +53 -0
  545. package/ui/js/MdHtmlImage.js +179 -0
  546. package/ui/js/MdInline.js +154 -0
  547. package/ui/js/MdItems.js +212 -0
  548. package/ui/js/MdLeaf.js +309 -0
  549. package/ui/js/MdLink.js +196 -0
  550. package/ui/js/MdMath.js +67 -0
  551. package/ui/js/MdParagraphs.js +138 -0
  552. package/ui/js/MdRefs.js +232 -0
  553. package/ui/js/MdResolve.js +226 -0
  554. package/ui/js/MdRun.js +300 -0
  555. package/ui/js/MdUrl.js +226 -0
  556. package/ui/js/Menu.js +514 -0
  557. package/ui/js/MenuContext.js +85 -0
  558. package/ui/js/MenuFit.js +13 -0
  559. package/ui/js/MenuRefresh.js +74 -0
  560. package/ui/js/MenuWheel.js +35 -0
  561. package/ui/js/Messages.js +186 -0
  562. package/ui/js/Motion.js +16 -0
  563. package/ui/js/Mounts.js +320 -0
  564. package/ui/js/MouseNav.js +13 -0
  565. package/ui/js/Nav.js +329 -0
  566. package/ui/js/NetFs.js +26 -0
  567. package/ui/js/OpenWith.js +83 -0
  568. package/ui/js/Ops.js +585 -0
  569. package/ui/js/Palette.js +33 -0
  570. package/ui/js/PathBar.js +260 -0
  571. package/ui/js/Permissions.js +179 -0
  572. package/ui/js/Phones.js +112 -0
  573. package/ui/js/Picker.js +353 -0
  574. package/ui/js/PlaceMenu.js +96 -0
  575. package/ui/js/Places.js +265 -0
  576. package/ui/js/PreviewAsk.js +21 -0
  577. package/ui/js/PreviewKeys.js +155 -0
  578. package/ui/js/PreviewSettle.js +29 -0
  579. package/ui/js/PreviewSwap.js +101 -0
  580. package/ui/js/PropertyFacts.js +144 -0
  581. package/ui/js/Protocols.js +296 -0
  582. package/ui/js/QuickLookKinds.js +27 -0
  583. package/ui/js/QuickLookRect.js +24 -0
  584. package/ui/js/RailDrop.js +51 -0
  585. package/ui/js/RailKeys.js +87 -0
  586. package/ui/js/RailMenu.js +133 -0
  587. package/ui/js/Recent.js +135 -0
  588. package/ui/js/RecentMode.js +156 -0
  589. package/ui/js/Reload.js +60 -0
  590. package/ui/js/Renderer.js +9 -0
  591. package/ui/js/RunFile.js +143 -0
  592. package/ui/js/Scripts.js +28 -0
  593. package/ui/js/Scroll.js +304 -0
  594. package/ui/js/ScrollOff.js +69 -0
  595. package/ui/js/Search.js +478 -0
  596. package/ui/js/Selection.js +97 -0
  597. package/ui/js/Settings.js +476 -0
  598. package/ui/js/SettingsAbout.js +97 -0
  599. package/ui/js/SettingsShelf.js +52 -0
  600. package/ui/js/SheetKeys.js +42 -0
  601. package/ui/js/SheetQuery.js +281 -0
  602. package/ui/js/ShelfPile.js +27 -0
  603. package/ui/js/SlowClick.js +53 -0
  604. package/ui/js/SlowOp.js +6 -0
  605. package/ui/js/Sort.js +134 -0
  606. package/ui/js/Startup.js +63 -0
  607. package/ui/js/StateWords.js +17 -0
  608. package/ui/js/Status.js +238 -0
  609. package/ui/js/Swap.js +166 -0
  610. package/ui/js/TabMove.js +39 -0
  611. package/ui/js/Tabs.js +736 -0
  612. package/ui/js/Tags.js +134 -0
  613. package/ui/js/Taildrop.js +128 -0
  614. package/ui/js/Tap.js +174 -0
  615. package/ui/js/TextSize.js +107 -0
  616. package/ui/js/Thumbs.js +158 -0
  617. package/ui/js/Transfer.js +172 -0
  618. package/ui/js/Trash.js +81 -0
  619. package/ui/js/TrashDates.js +47 -0
  620. package/ui/js/TrashDrop.js +64 -0
  621. package/ui/js/UiState.js +438 -0
  622. package/ui/js/Unlock.js +152 -0
  623. package/ui/js/Update.js +130 -0
  624. package/ui/js/UserDirs.js +56 -0
  625. package/ui/qmldir +228 -0
@@ -0,0 +1,846 @@
1
+ // The pragmas that name this window's app id and shell id live in ui/boot/picker.qml, which is
2
+ // the Quickshell entry; this document is loaded from there by file: URL so that Qt caches it.
3
+
4
+ import Quickshell
5
+ import Quickshell.Io
6
+ import QtQuick
7
+ import qs.Commons
8
+ import "." as Flea
9
+ import "js/Picker.js" as Picker
10
+ import "js/Keymap.js" as Keymap
11
+ import "js/Thumbs.js" as Thumbs
12
+ // ControlReader.js loads with IpcControls.qml on the first cohesionControls call, never with the picker's first frame.
13
+
14
+ // One portal request, one window: the org.freedesktop.impl.portal.FileChooser dialog every caller on
15
+ // the box gets, opened by flea --pick and answered through the reply file tools/flea-portal reads.
16
+ // The same Backend, Row, Theme and places the browser window draws with, and none of its operations:
17
+ // a chooser that can rename or delete is a file manager wearing a dialog's clothes.
18
+ ShellRoot {
19
+ property alias pickerWin: win
20
+ FloatingWindow {
21
+ id: win
22
+
23
+ readonly property var req: Picker.request(Quickshell.env("FLEA_PICKER"))
24
+ readonly property string home: Quickshell.env("HOME")
25
+
26
+ title: Picker.title(win.req)
27
+ implicitWidth: Math.round(Theme.space(640) * Theme.dialogWidthRatio)
28
+ implicitHeight: Theme.space(440)
29
+ color: Theme.color.background
30
+
31
+ // Where the list is standing, what it holds of the listing, and where the cursor is in it.
32
+ property string path: ""
33
+ property int total: 0
34
+ property int held: 0
35
+ property var rows: []
36
+ // The per-response kind dictionary ui/Row.qml's Kind column indexes into.
37
+ property var kindNames: []
38
+ property int cursorIndex: 0
39
+ property string listingState: "loading"
40
+ // A refused scan leaves the worker holding the previous folder, which "empty" alone cannot say, so only a new listing clears it.
41
+ property bool listingFailed: false
42
+ // The one statement of when the listing may be reordered; the header and the keys both read it.
43
+ readonly property bool sortable: !win.backendUnavailable && !win.recent && !win.submitting && !win.listingFailed
44
+ && win.listingState !== "loading" && !(shares.item && shares.item.active)
45
+ // The order the listing is actually in, read through the picker because the backend id is out of scope.
46
+ readonly property string sortBy: backend.sortBy
47
+ readonly property bool sortDesc: backend.sortDesc
48
+ property int pendingListings: 0
49
+ property bool receivingLatestListing: false
50
+ property bool backendUnavailable: false
51
+ property string message: ""
52
+
53
+ // The checked identities, each a path and its size, so Back and Parent cannot rebind one.
54
+ property var marks: []
55
+ // Which chip is active: an index into the caller's filters, or -1 for All files.
56
+ property int filterIndex: Picker.currentChip(win.req)
57
+ readonly property var filter: win.filterIndex >= 0 ? win.req.filters[win.filterIndex] : null
58
+ readonly property var shown: null
59
+ readonly property int shownTotal: win.total
60
+ onFilterChanged: if (win.path.length) win.openWithoutHistory(win.path)
61
+
62
+ // The view the listing draws in; a launch reads pickerView and only setView writes it.
63
+ property string viewMode: "list"
64
+ function setView(mode) {
65
+ var before = win.actionFocus()
66
+ var next = Picker.viewSwitch(win.viewMode, mode)
67
+ if (!next) { win.restoreActionFocus(before); return }
68
+ win.viewMode = next
69
+ // The user's own switch, and only that, is what the next launch reopens: a launch
70
+ // reads, and cursor moves never owe the file anything.
71
+ ViewState.changeKey("pickerView", next)
72
+ // A reshow owns its window: the shown view moves to the cursor and refetches there.
73
+ var at = Math.max(0, Math.min(win.cursorIndex, win.total - 1))
74
+ if (next === "grid") grid.reshow(at)
75
+ else list.reshow(at)
76
+ win.restoreActionFocus(before)
77
+ }
78
+ function viewItem() { return win.viewMode === "grid" ? grid : list }
79
+ function focusView() { win.viewItem().forceActiveFocus() }
80
+ function actionFocus() {
81
+ var view = win.viewItem()
82
+ if (view.activeFocus) return view
83
+ var field = save.fieldItem
84
+ return field.activeFocus ? { field: field, cursor: field.cursorPosition,
85
+ anchor: field.cursorPosition === field.selectionStart ? field.selectionEnd : field.selectionStart } : null
86
+ }
87
+ function restoreActionFocus(before) {
88
+ if (before === list || before === grid) win.focusView()
89
+ else if (before) {
90
+ var field = before.field
91
+ if (!field.activeFocus) {
92
+ field.forceActiveFocus()
93
+ field.select(before.anchor, before.cursor)
94
+ }
95
+ }
96
+ else win.focusDefault()
97
+ }
98
+ // Automatic returns keep a save on Filename; Tab and row clicks can still focus the listing.
99
+ function focusDefault() {
100
+ if (win.saving) save.selectStem()
101
+ else win.focusView()
102
+ }
103
+ // Whether the listing shows the directory's dotfiles. The worker never sends what a
104
+ // request did not ask for, so the window re-reads the standing directory when this flips.
105
+ property bool showHidden: false
106
+ // The grid's visible-only thumbnail plan, owned here so both views share one map the way
107
+ // the main pane owns its views' states; only the grid ever asks.
108
+ property var thumbState: Thumbs.empty()
109
+ // Seven screens of history at the picker's row height, the main pane's own bound.
110
+ readonly property int thumbCap: 240
111
+ // The directory's storage class beside its fsinfo line, never per row; unknown holds the
112
+ // first settle the way the main pane holds its own, see ui/js/ExtThumbs.js.
113
+ property string storageClass: ""
114
+ property bool storageKnown: false
115
+
116
+ // Where Back goes, and it only ever goes back: Parent is its own button and pushes here too.
117
+ property var history: []
118
+ // The save mode's own name. It starts empty and Component.onCompleted seeds it from the caller's name.
119
+ property string saveName: ""
120
+ onSaveNameChanged: win.invalidateSave()
121
+ property int nextCheck: 0
122
+ property int markRequest: 0
123
+ property bool acceptMarks: false
124
+ property bool marksDirty: false
125
+ property int saveRequest: 0
126
+ property int reviewRequest: 0
127
+ property string probeKey: ""
128
+ property var saveReview: ({})
129
+ property string saveError: ""
130
+ readonly property string saveKey: JSON.stringify([win.path, win.saveName])
131
+ readonly property bool saveReady: win.saveReview.key === win.saveKey
132
+ readonly property bool saveCollision: win.saveReady && win.saveReview.collision
133
+ // Return asked once for a name that exists; the review stands until the name, folder or listing changes.
134
+ property bool reviewAsked: false
135
+ // The review is the asked review of a name the backend still reports as taken: Replace, its error line and its role read this alone.
136
+ readonly property bool collisionReview: win.reviewAsked && win.saveCollision
137
+ readonly property bool submitting: win.acceptMarks || win.reviewRequest > 0
138
+ // Submission keeps Cancel reachable and restores its initiating control only on refusal.
139
+ onSubmittingChanged: chrome.submissionChanged()
140
+ readonly property bool marksAllowed: win.req.multiple && !win.saving
141
+ readonly property var cursorRow: win.rowFor(win.cursorIndex)
142
+ readonly property bool cursorFile: win.listingState === "ready" && !win.listingFailed && win.cursorRow !== null && !Picker.directory(win.cursorRow)
143
+ readonly property bool canAccept: !win.backendUnavailable && !win.submitting && !win.markRequest && (win.saving
144
+ ? win.saveReady : win.marks.length > 0 || (!win.folderMode && win.cursorFile) || (win.folderMode && !win.recent && win.listingState !== "loading"))
145
+
146
+ // SendPicker.html draws every rule and control frame in one ink, a lift over whatever plane
147
+ // it sits on. Theme.color.surface is a drop on these palettes and vanishes against the chrome
148
+ // strips, so the picker takes the OEM's own resting border alpha, which lifts on both.
149
+ readonly property color edge: Style.hoverBorderColor
150
+
151
+ // Recent is a location and not a directory: the rail's own row opens it and the listing it
152
+ // builds comes from the desktop's history rather than a scan; see AGENTS.md "Recent, and why".
153
+ readonly property bool recent: Picker.isRecent(win.path)
154
+
155
+ readonly property bool saving: win.req.mode === "save"
156
+ readonly property bool folderMode: win.req.directory || win.req.mode === "savefiles"
157
+ // Twice the wider view's screen plus slack, so the screen still fits after the quarter lead.
158
+ readonly property int windowSize: Picker.windowSize(list.visibleRows, grid.visibleTileRows, grid.columns)
159
+ // Both views refetch through one interval and lead, the main views' own 16 ms and quarter window.
160
+ readonly property int coalesceMs: 16
161
+ readonly property real windowLead: 0.25
162
+
163
+ // Exactly one answer leaves this window, whichever way it is asked for.
164
+ property bool answered: false
165
+ // The typed-path probe: the mark that has the backend refuse a typed file in a save or folder dialog.
166
+ property int pathProbe: 0
167
+
168
+ function rowFor(index) {
169
+ var at = index - win.held
170
+ return at >= 0 && at < win.rows.length ? win.rows[at] : null
171
+ }
172
+
173
+ function open(next) {
174
+ if (win.submitting || next === win.path) return
175
+ if (win.path.length > 0) win.history = win.history.concat([win.path])
176
+ win.openWithoutHistory(next)
177
+ }
178
+
179
+ function openWithoutHistory(next) {
180
+ if (win.backendUnavailable) return
181
+ listing.clear()
182
+ if (shares.item) shares.item.close()
183
+ win.path = next
184
+ win.listingFailed = false
185
+ win.clearListing()
186
+ win.invalidateSave()
187
+ win.validateMarks(false)
188
+ if (Picker.isRecent(next)) {
189
+ recents.refresh()
190
+ return
191
+ }
192
+ backend.resetSort(next)
193
+ win.requestListing(backend.listRequest(next, win.windowSize, win.showHidden))
194
+ }
195
+
196
+ function requestListing(request) {
197
+ win.pendingListings = 1
198
+ listing.request(Object.assign(request, win.filterRequest()))
199
+ }
200
+
201
+ // Rows are named by index, so a replaced listing is dropped whole; emptying the model returns the viewport to the top.
202
+ function clearListing() {
203
+ selection.reset()
204
+ win.total = 0
205
+ win.held = 0
206
+ win.rows = []
207
+ win.cursorIndex = 0
208
+ win.listingState = "loading"
209
+ win.receivingLatestListing = false
210
+ // A replaced listing renumbers every row, so no thumb answer may outlive it.
211
+ win.thumbState = Thumbs.empty()
212
+ win.storageClass = ""
213
+ win.storageKnown = false
214
+ }
215
+
216
+ // Sort reorders the worker's listing, marks stay; remembers folder sort and default sort in ui.json.
217
+ function requestSort(order) {
218
+ var before = win.actionFocus()
219
+ if (!order || !win.sortable || (backend.sortBy === order.key && backend.sortDesc === order.desc))
220
+ return
221
+ backend.sortBy = order.key
222
+ backend.sortDesc = order.desc
223
+ // A user sort writes its folder only while remembering is on; the default follows every sort.
224
+ if (!win.recent && win.path.length > 0 && ViewState.state.rememberSort !== false)
225
+ backend.rememberFolderSort(win.path, order.key, order.desc)
226
+ ViewState.changeLeaf("sort", { key: order.key === "mtime" ? "date" : order.key, reverse: order.desc })
227
+ win.clearListing()
228
+ win.pendingListings = 1
229
+ listing.sort(order.key, order.desc, ViewState.state.foldersFirst !== false, ViewState.state.groupByKind === true, ViewState.state.hiddenLast === true)
230
+ // sort answers a listed line and no rows of its own, so the reordered window is asked for.
231
+ listing.window(0, win.windowSize)
232
+ win.restoreActionFocus(before)
233
+ }
234
+
235
+ function filterRequest() {
236
+ return {pickerGlobs: win.filter ? win.filter.globs : [], pickerMimes: win.filter ? win.filter.mimes : []}
237
+ }
238
+
239
+ function check(request) {
240
+ request.c = "picker"
241
+ request.id = ++win.nextCheck
242
+ backend.send(request)
243
+ return request.id
244
+ }
245
+
246
+ function validateMarks(accepting) { selection.validate(accepting) }
247
+
248
+ function invalidateSave() {
249
+ win.saveReview = ({})
250
+ win.reviewAsked = false
251
+ win.saveError = ""
252
+ if (win.saving) Qt.callLater(win.probeSave)
253
+ }
254
+
255
+ function probeSave() {
256
+ if (win.backendUnavailable || !win.saving || win.saveRequest || !win.path.length || !Picker.validName(win.saveName)) return
257
+ win.probeKey = win.saveKey
258
+ win.saveRequest = win.check({op: "save", folder: win.path, name: win.saveName})
259
+ }
260
+
261
+ // Reassign history so property var notifies.
262
+ function goBack() {
263
+ if (shares.item && shares.item.active) { shares.item.close(); return }
264
+ if (win.submitting || win.history.length === 0)
265
+ return
266
+ var target = win.history[win.history.length - 1]
267
+ win.history = win.history.slice(0, win.history.length - 1)
268
+ win.openWithoutHistory(target)
269
+ }
270
+
271
+ function goUp() {
272
+ // The board's own rule: Parent is unavailable in Recent, because a history has no parent.
273
+ if (win.recent) {
274
+ return
275
+ }
276
+ var up = Picker.parentOf(win.path)
277
+ if (up !== win.path)
278
+ win.open(up)
279
+ }
280
+
281
+ function toggleMark(index) { selection.toggle(index) }
282
+ function markRange(was, index) { selection.range(was, index) }
283
+ function selectAll() { selection.all() }
284
+ function endRange() { selection.endRange() }
285
+
286
+ // Enter walks folders and submits the checked files, or the cursor file when none are checked.
287
+ function activate(index) {
288
+ var row = win.rowFor(index)
289
+ if (!row)
290
+ return
291
+ if (Picker.directory(row)) {
292
+ win.open(Picker.rowPath(win.path, row.n))
293
+ return
294
+ }
295
+ win.accept()
296
+ }
297
+
298
+ // File double clicks toggle marks in several-file requests; one-file requests mark when needed, then accept.
299
+ function doubleActivate(index, rowPath, firstPath) {
300
+ var row = win.rowFor(index)
301
+ var choice = Picker.doubleAction(win.req, row, rowPath, firstPath, win.marks)
302
+ if (choice === Picker.DOUBLE_OPEN) {
303
+ win.open(Picker.rowPath(win.path, row.n))
304
+ return
305
+ }
306
+ if (choice === Picker.DOUBLE_MARK) {
307
+ win.toggleMark(index)
308
+ return
309
+ }
310
+ if (choice !== Picker.DOUBLE_ACCEPT && choice !== Picker.DOUBLE_MARK_ACCEPT) {
311
+ return
312
+ }
313
+ if (win.backendUnavailable || win.markRequest || win.submitting) {
314
+ win.say("Selection is still being checked.")
315
+ return
316
+ }
317
+ if (choice === Picker.DOUBLE_ACCEPT) {
318
+ win.accept()
319
+ return
320
+ }
321
+ win.acceptMarks = true
322
+ win.markRequest = win.check({op: "mark", path: rowPath, directory: false, multiple: win.req.multiple})
323
+ }
324
+
325
+ function accept() {
326
+ if (win.backendUnavailable || win.submitting || win.markRequest) return
327
+ if (win.saving) {
328
+ if (win.saveName.length === 0) {
329
+ win.say("Name the file before saving it")
330
+ return
331
+ }
332
+ // The answer has to name the folder the user was shown, so a typed separator is
333
+ // refused here rather than rewritten: a rewrite would send a path nobody approved.
334
+ if (!Picker.validName(win.saveName)) {
335
+ win.say(Picker.NAME_REFUSED)
336
+ return
337
+ }
338
+ if (!win.saveReady) { win.probeSave(); return }
339
+ // Return, the field and a file row only ask: the review starts on a taken name and stays until Replace answers.
340
+ if (win.saveCollision) {
341
+ if (!win.reviewAsked) { win.reviewAsked = true; save.selectStem() }
342
+ return
343
+ }
344
+ win.submitSave()
345
+ return
346
+ }
347
+ // A folder request with nothing checked takes the directory the window is standing in,
348
+ // which is what the board's Choose folder button does with no row marked.
349
+ if (win.marks.length === 0 && win.folderMode) {
350
+ // Recent is not a directory, so there is nothing here to hand back unasked.
351
+ if (win.recent) {
352
+ win.say("Press Space to select a folder first")
353
+ return
354
+ }
355
+ win.acceptMarks = true
356
+ win.markRequest = win.check({op: "mark", path: win.path, directory: true, multiple: false})
357
+ return
358
+ }
359
+ if (win.marks.length === 0) {
360
+ if (!win.cursorFile) return
361
+ win.acceptMarks = true
362
+ win.markRequest = win.check({op: "mark", path: Picker.rowPath(win.path, win.cursorRow.n), directory: false, multiple: false})
363
+ return
364
+ }
365
+ win.validateMarks(true)
366
+ }
367
+
368
+ // Replace is the only answer that overwrites; Enter or Space on it, or a click, arrives here and nothing else does.
369
+ function replace() {
370
+ if (win.backendUnavailable || win.submitting || win.markRequest || !win.collisionReview) return
371
+ win.submitSave()
372
+ }
373
+
374
+ function submitSave() { win.reviewRequest = win.check({op: "review", review: win.saveReview.review}) }
375
+
376
+ function cancel() {
377
+ win.finish(Picker.RESPONSE_CANCELLED, [])
378
+ }
379
+
380
+ // ":" and Ctrl+L are the main window's own pathBar rows; keys.toml is not edited.
381
+ function openPathBar() { chrome.startEdit() }
382
+
383
+ // The portal reads this reply after exit, so its atomic write must finish before teardown.
384
+ function finish(response, list) {
385
+ if (win.answered)
386
+ return
387
+ // Built before the flag is set, so a throw here leaves the window answerable rather than shut.
388
+ var text = Picker.reply(response, list, win.filterIndex)
389
+ win.answered = true
390
+ if (!win.backendUnavailable) backend.send({c: "picker", op: "close"})
391
+ replyFile.setText(text)
392
+ }
393
+
394
+ property bool messageError: false
395
+ function say(text, error) {
396
+ win.message = text
397
+ win.messageError = error === true
398
+ messageLife.stop()
399
+ if (!win.messageError) messageLife.restart()
400
+ }
401
+
402
+ Timer {
403
+ id: messageLife
404
+ interval: 4000
405
+ onTriggered: win.message = ""
406
+ }
407
+
408
+ FileView {
409
+ id: replyFile
410
+ path: Quickshell.env("FLEA_PICKER_REPLY")
411
+ atomicWrites: true
412
+ // The reply file does not exist until this window writes it, and a preload read of a
413
+ // path that is not there is not an error worth a line; onSaveFailed below is.
414
+ printErrors: false
415
+ // Sequenced on saved(), never on setText() returning: the reply is on disk before both children are reaped.
416
+ onSaved: lifecycle.quit()
417
+ onSaveFailed: {
418
+ console.warn("the portal reply could not be written, so the request fails rather than reporting a refusal")
419
+ lifecycle.quit()
420
+ }
421
+ }
422
+
423
+ // A window-manager close takes the same refusal path as Cancel.
424
+ Connections {
425
+ target: Quickshell
426
+ function onLastWindowClosed() {
427
+ if (win.answered)
428
+ return
429
+ win.finish(Picker.RESPONSE_CANCELLED, [])
430
+ }
431
+ }
432
+
433
+ // The saved reply can leave once both chooser-owned read processes have exited.
434
+ Flea.PickerLifecycle {
435
+ id: lifecycle; checks: backend; listing: listing
436
+ onStopped: Quickshell.execDetached(["kill", String(Quickshell.processId)])
437
+ }
438
+ Flea.PickerListing {
439
+ id: listing
440
+ onFailed: function(reason) { backend.failed("scan", win.path, reason, 0) }
441
+ onMessage: function(message) { backend.receive(JSON.stringify(message)) }
442
+ }
443
+
444
+ Flea.Backend {
445
+ id: backend
446
+ pickerOnly: true
447
+ // Each listing resolves its folder explicitly; between requests the mark must keep describing the held rows.
448
+ preserveSort: true
449
+
450
+ onListed: function (n, readMs, sortMs) {
451
+ if (win.backendUnavailable) return
452
+ win.pendingListings = 0
453
+ win.receivingLatestListing = true
454
+ win.total = n
455
+ win.listingState = n === 0 ? "empty" : "ready"
456
+ // Grid needs the storage class, so ask fsinfo except on Recent.
457
+ if (!win.recent)
458
+ listing.fsinfo()
459
+ }
460
+ onRows: function (start, items, ms, kinds) {
461
+ if (!win.receivingLatestListing) return
462
+ win.held = start
463
+ win.rows = items
464
+ win.kindNames = kinds
465
+ }
466
+ // A thumbed line for the previous listing is still in the pipe when clearListing
467
+ // emptied the map, so only the live listing's answers land.
468
+ onThumbed: function (row, file) {
469
+ if (!win.receivingLatestListing) return
470
+ win.thumbState = Thumbs.remember(win.thumbState, row, file, win.thumbCap)
471
+ }
472
+ // The worker stats its own base after the rows, so the class lands after them; a
473
+ // settle fired in between held on unknown, and restarts now that it is named.
474
+ onFsInfo: function (fs, free, path, storageClass) {
475
+ if (path.length > 0 && path !== win.path) return
476
+ win.storageClass = storageClass || ""
477
+ win.storageKnown = true
478
+ grid.restartSettle()
479
+ }
480
+ onFailed: function (where, input, msg, mode) {
481
+ if (where === "backend") {
482
+ win.backendUnavailable = true
483
+ win.pendingListings = 0
484
+ win.receivingLatestListing = false
485
+ win.markRequest = 0
486
+ win.saveRequest = 0
487
+ win.reviewRequest = 0
488
+ win.acceptMarks = false
489
+ win.marksDirty = false
490
+ win.saveReview = ({})
491
+ win.saveError = "Flea stopped responding."
492
+ win.say("Flea stopped responding. Cancel and reopen this request.", true)
493
+ win.stepFocus(null, false)
494
+ return
495
+ }
496
+ if (where === "scan" || where === "sort") {
497
+ selection.reset()
498
+ win.pendingListings = 0
499
+ win.listingFailed = true
500
+ }
501
+ // A worker lost over held rows keeps them and the footer error; only a listing that holds nothing reads as empty.
502
+ if (win.total === 0) win.listingState = "empty"
503
+ win.say(msg, true)
504
+ }
505
+ onChanged: function (path) {
506
+ if (path === win.path && !win.submitting) win.openWithoutHistory(win.path)
507
+ }
508
+ onPickerResult: function (message) {
509
+ if (win.answered || win.backendUnavailable) return
510
+ if (message.id === win.markRequest || message.id === win.pathProbe) {
511
+ selection.received(message)
512
+ } else if (message.id === win.saveRequest) {
513
+ win.saveRequest = 0
514
+ if (win.probeKey !== win.saveKey) { win.probeSave(); return }
515
+ if (!message.ok) { win.saveError = message.error; win.say(message.error, true); return }
516
+ win.saveReview = Object.assign({key: win.saveKey}, message)
517
+ } else if (message.id === win.reviewRequest) {
518
+ win.reviewRequest = 0
519
+ if (!message.ok || !win.saveReady) {
520
+ win.say(message.error || "The save location changed; review it again.", true)
521
+ win.invalidateSave()
522
+ return
523
+ }
524
+ win.finish(Picker.RESPONSE_OK, [message.path])
525
+ }
526
+ }
527
+ }
528
+
529
+ Flea.PickerSelection {
530
+ id: selection
531
+ picker: win
532
+ listing: listing
533
+ backend: backend
534
+ }
535
+
536
+ // The history the Recent location lists, read only when that location is opened. The listing
537
+ // is the client's own order, so the backend is asked for these paths and never to sort them.
538
+ Flea.PickerRecent {
539
+ id: recents
540
+ onRefreshed: if (win.recent) win.requestListing({c: "listpaths", paths: recents.paths, first: win.windowSize})
541
+ }
542
+
543
+ Rectangle {
544
+ anchors.fill: parent
545
+ color: Theme.color.background
546
+ focus: true
547
+ // The save field takes the keyboard from the list, and Escape has to refuse from there too.
548
+ Keys.onEscapePressed: win.cancel()
549
+
550
+ Flea.PickerChrome {
551
+ id: chrome
552
+ anchors.left: parent.left
553
+ anchors.right: parent.right
554
+ anchors.top: parent.top
555
+ picker: win
556
+ listing: listing
557
+ backend: backend
558
+ onBackRequested: { win.focusDefault(); win.goBack() }
559
+ onUpRequested: { win.focusDefault(); win.goUp() }
560
+ onViewChosen: function (mode) { win.setView(mode) }
561
+ }
562
+
563
+ Flea.PickerPlaces {
564
+ id: places
565
+ anchors.left: parent.left
566
+ anchors.top: chrome.bottom
567
+ anchors.bottom: save.top
568
+ home: win.home
569
+ picker: win
570
+ current: win.path
571
+ edge: win.edge
572
+ offerRecent: !win.saving
573
+ onChosen: function (path) { win.open(path); win.focusDefault() }
574
+ onNetworkCompleted: function(requestId, uri, success, reason, connectFailed) {
575
+ if (networkDialog.item) networkDialog.item.mountFinished(requestId, uri, success, reason, connectFailed)
576
+ }
577
+ }
578
+
579
+ Flea.PickerHeader {
580
+ id: header
581
+ anchors.left: places.right
582
+ anchors.right: parent.right
583
+ anchors.top: chrome.bottom
584
+ picker: win
585
+ backend: backend
586
+ leadingSlot: win.marksAllowed ? list.checkSize + Theme.spacing.gap : 0
587
+ // Only the list heads columns; the save form keeps its old room, so the header yields when it and one row do not fit.
588
+ visible: win.viewMode === "list" && save.y - chrome.height >= implicitHeight + Theme.rowHeight
589
+ height: visible ? implicitHeight : 0
590
+ }
591
+
592
+ Flea.PickerList {
593
+ id: list
594
+ visible: win.viewMode === "list"
595
+ anchors.left: places.right
596
+ anchors.right: parent.right
597
+ anchors.top: header.bottom
598
+ anchors.bottom: save.top
599
+ picker: win
600
+ backend: listing
601
+ clip: true
602
+ focus: win.viewMode === "list"
603
+ enabled: !win.submitting && !win.backendUnavailable
604
+ }
605
+
606
+ // The grid draws the same rows through the main window's tiles, with its own
607
+ // visible-only thumbnail plan; see ui/PickerGrid.qml.
608
+ Flea.PickerGrid {
609
+ id: grid
610
+ visible: win.viewMode === "grid"
611
+ anchors.left: places.right
612
+ anchors.right: parent.right
613
+ anchors.top: header.bottom
614
+ anchors.bottom: save.top
615
+ picker: win
616
+ backend: listing
617
+ clip: true
618
+ focus: win.viewMode === "grid"
619
+ enabled: !win.submitting && !win.backendUnavailable
620
+ }
621
+
622
+ Connections {
623
+ target: grid
624
+ function onThumbsApplied(work) { win.thumbState = Thumbs.applied(win.thumbState, work) }
625
+ }
626
+
627
+ // The same empty hero the browser window draws, over the list area alone.
628
+ Flea.EmptyState {
629
+ id: hero
630
+ x: list.x
631
+ y: list.y
632
+ width: list.width
633
+ height: list.height
634
+ visible: win.listingState === "empty"
635
+ }
636
+
637
+ FocusScope {
638
+ id: shareFocus
639
+ x: list.x
640
+ y: list.y
641
+ width: list.width
642
+ height: list.height
643
+ visible: shares.item !== null && shares.item.visible
644
+ Keys.onPressed: function(event) {
645
+ var action = Keymap.lookup(event.key, event.text, event.modifiers, "rail")
646
+ event.accepted = true
647
+ if (event.key === Qt.Key_Escape || action === "parent" || action === "historyBack") shares.item.close()
648
+ else if (action === "cursorDown") shares.item.moveCursor(1)
649
+ else if (action === "cursorUp") shares.item.moveCursor(-1)
650
+ else if (action === "open" || event.key === Qt.Key_Space) shares.item.activateCursor()
651
+ else if (event.key === Qt.Key_Tab) win.stepFocus(list, (event.modifiers & Qt.ShiftModifier) !== 0)
652
+ else if (event.key === Qt.Key_Backtab) win.stepFocus(list, true)
653
+ }
654
+ Loader {
655
+ id: shares
656
+ anchors.fill: parent
657
+ active: false
658
+ source: "ShareBrowser.qml"
659
+ }
660
+ Connections {
661
+ target: shares.item
662
+ function onClosed() { win.focusDefault() }
663
+ function onActivated(uri, label) { places.openChild(uri, label) }
664
+ }
665
+ }
666
+
667
+ Flea.PickerSave {
668
+ id: save
669
+ keyTarget: win.viewItem()
670
+ anchors.left: parent.left
671
+ anchors.right: parent.right
672
+ anchors.bottom: answers.top
673
+ maximumHeight: Math.max(0, answers.y - chrome.height - Theme.rowHeight)
674
+ picker: win
675
+ onNameEdited: function (text) { win.saveName = text }
676
+ onAccepted: win.accept()
677
+ }
678
+
679
+ // The answer row stands under the listing and the save area in every mode; the footer is under it.
680
+ Flea.PickerAnswers { id: answers; anchors.bottom: status.top; picker: win }
681
+
682
+ // The footer: what is checked on the left, the keys that act on it on the right.
683
+ Item {
684
+ id: status
685
+ anchors.left: parent.left
686
+ anchors.right: parent.right
687
+ anchors.bottom: parent.bottom
688
+ height: Theme.chromeHeight
689
+
690
+ // The footer takes the chrome plane, the same strip the ask above it stands on.
691
+ Rectangle {
692
+ anchors.fill: parent
693
+ color: Theme.color.surface
694
+ }
695
+
696
+ Rectangle {
697
+ anchors.top: parent.top
698
+ width: parent.width
699
+ height: Theme.spacing.hairline
700
+ color: win.edge
701
+ }
702
+
703
+ Text {
704
+ id: statusMessage
705
+ anchors.left: parent.left
706
+ anchors.leftMargin: Theme.spacing.rowPaddingX
707
+ anchors.right: statusHints.left
708
+ anchors.rightMargin: Theme.spacing.gap
709
+ anchors.verticalCenter: parent.verticalCenter
710
+ text: win.message.length > 0 ? win.message : !win.marksAllowed ? "" : Picker.statusLine(win.marks.length, Picker.totalBytes(win.marks))
711
+ color: win.messageError ? Theme.color.errorOnSurface : Theme.color.foreground
712
+ font.family: Theme.font.family
713
+ font.pixelSize: Theme.font.caption
714
+ textFormat: Text.PlainText
715
+ elide: Text.ElideRight
716
+ }
717
+
718
+ Text {
719
+ id: statusHints
720
+ anchors.right: parent.right
721
+ anchors.rightMargin: Theme.spacing.rowPaddingX
722
+ width: Math.min(implicitWidth, Math.max(0, parent.width - 2 * Theme.spacing.rowPaddingX
723
+ - Theme.spacing.gap - Math.min(statusMessage.implicitWidth, parent.width / 2)))
724
+ anchors.verticalCenter: parent.verticalCenter
725
+ text: win.backendUnavailable ? "Esc cancel" : Picker.hints(win.req)
726
+ color: Theme.color.foreground
727
+ font.family: Theme.font.family
728
+ font.pixelSize: Theme.font.caption
729
+ textFormat: Text.PlainText
730
+ elide: Text.ElideRight
731
+ }
732
+ }
733
+
734
+ Loader {
735
+ id: networkDialog
736
+ anchors.fill: parent
737
+ active: false
738
+ sourceComponent: Component {
739
+ Flea.NetworkDialog {
740
+ onClosed: win.focusDefault()
741
+ onMountRequested: function(requestId, uri, label, password) { places.retry(requestId, uri, label, password) }
742
+ onCancelRequested: function(requestId) { places.cancelNetwork(requestId) }
743
+ }
744
+ }
745
+ }
746
+ }
747
+
748
+ function showShares(uri, label, names) {
749
+ shares.active = true
750
+ shares.item.open(uri, label, names)
751
+ shareFocus.forceActiveFocus()
752
+ }
753
+ function retryNetwork(uri, label, password, reason, failed) {
754
+ networkDialog.active = true
755
+ networkDialog.item.openLocation(uri, label, password, reason, failed)
756
+ }
757
+
758
+ Component.onCompleted: {
759
+ // Only an absolute path is a folder, so a caller cannot name the Recent token, or any
760
+ // other text, as the directory this window opens on.
761
+ var start = win.req.folder.charAt(0) === "/" ? win.req.folder : win.home
762
+ win.showHidden = typeof win.req.hidden === "boolean" ? win.req.hidden : ViewState.state.hidden === true
763
+ win.viewMode = Picker.rememberedView(ViewState.pickerView)
764
+ // A one-time seed of a filename only: a binding would read first inside saveReady, and flea-portal passes current_name verbatim.
765
+ win.saveName = Picker.validName(win.req.name) ? win.req.name : ""
766
+ win.openWithoutHistory(start)
767
+ // Seed focus explicitly: saves start on Filename, other requests on the listing.
768
+ win.focusDefault()
769
+ }
770
+
771
+ // The seam tests/picker.sh drives, the same read-only shape ui/Ipc.qml has for the window.
772
+ IpcHandler {
773
+ target: "fleapicker"
774
+ function ready(): bool { return true }
775
+ function path(): string { return win.path }
776
+ function total(): int { return win.total }
777
+ function shownTotal(): int { return win.shownTotal }
778
+ function cursor(): int { return win.cursorIndex }
779
+ function marks(): string { return Picker.paths(win.marks).join(",") }
780
+ function rowAt(index: int): string { var row = win.rowFor(index); return row ? row.n : "" }
781
+ function cursorName(): string { return win.rowFor(win.cursorIndex) ? win.rowFor(win.cursorIndex).n : "" }
782
+ function state(): string { return win.listingState }
783
+ function recent(): bool { return win.recent }
784
+ function accept(): string { return Picker.acceptLabel(win.req, win.marks.length) }
785
+ function chip(): int { return win.filterIndex }
786
+ function saveName(): string { return win.saveName }
787
+ function message(): string { return win.message }
788
+ function pathBarOpen(): bool { return chrome.editing }
789
+ function checks(): string { return JSON.stringify({marksBusy: win.markRequest > 0, saveBusy: win.saveRequest > 0, submitting: win.submitting, canAccept: win.canAccept, saveReady: win.saveReady, collision: win.saveCollision, review: win.saveReview.review || 0}) }
790
+ function markedUris(): string { return JSON.stringify(win.marks.map(function(mark) { return mark.uri })) }
791
+ function controls(): string { return JSON.stringify(chrome.controls().concat(answers.controls(), header.controls(), save.controls(), places.controls())) }
792
+ function rowCentre(index: int): string { return win.centre(win.viewItem().itemAtIndex(index)) }
793
+ function saveState(): string { return JSON.stringify({name: win.saveName, path: win.saveReview.path || "", collision: win.saveCollision, error: win.saveError, field: win.centre(save.fieldItem)}) }
794
+ // The cohesion captures' reader over the picker, see ui/js/ControlReader.js; its answer and chrome marks are inline components, and its filter is PickerFilter.
795
+ function cohesionControls(): string {
796
+ var kinds = /^(DialogButton|ChromeButton|SidebarRow|Answer|Mark|PickerFilter)(?:_QML|\()/
797
+ var alias = {Answer: "DialogButton", Mark: "ChromeButton"}
798
+ return Qt.createComponent("IpcControls.qml").createObject(null).read(win.contentItem ? win.contentItem.Window.window : null, win.contentItem, kinds, alias)
799
+ }
800
+ function snapshot(): string {
801
+ // One reader answers path, field and Jump together, so a poll reads one instant.
802
+ var jumpItem = chrome.jump
803
+ var jumpFrame = jumpItem ? jumpItem.dropItem : null
804
+ var jumpVisible = jumpFrame ? Math.floor((jumpFrame.height - 2 * Theme.spacing.rowPaddingY) / Theme.rowHeight) : 0
805
+ return JSON.stringify({path: win.path, total: win.total, held: win.held, rows: win.rows,
806
+ pathBarOpen: chrome.editing, pathFieldText: chrome.jump ? chrome.jump.query : "",
807
+ jump: {open: !!jumpItem && jumpItem.shown, rows: jumpItem ? jumpItem.entries.length : 0, visibleRows: jumpVisible, contentY: jumpFrame ? jumpFrame.scrollY : 0, cursor: jumpItem ? jumpItem.cursor : -1, paths: jumpItem ? jumpItem.entries.map(function(entry) { return entry.path }) : []},
808
+ cursor: win.cursorIndex, cursorName: win.rowFor(win.cursorIndex) ? win.rowFor(win.cursorIndex).n : "",
809
+ marks: win.marks, state: win.listingState, emptyHero: hero.visible, emptyRest: hero.atRest, listingFailed: win.listingFailed, sortBy: backend.sortBy, sortDesc: backend.sortDesc, sortable: win.sortable, filter: win.filterIndex, history: win.history,
810
+ view: win.viewMode, thumbPending: Object.keys(win.thumbState.file).filter(function(index) { return win.thumbState.file[index] === null || win.thumbState.file[index] === "cache-asked" }).length,
811
+ marksBusy: win.markRequest > 0, saveBusy: win.saveRequest > 0, submitting: win.submitting, backendUnavailable: win.backendUnavailable,
812
+ canAccept: win.canAccept, saveReady: win.saveReady, collision: win.saveCollision, collisionReview: win.collisionReview,
813
+ saveName: win.saveName, saveError: win.saveError, message: win.message, messageError: win.messageError, hints: statusHints.text,
814
+ controls: chrome.controls().concat(answers.controls(), header.controls(), save.controls(), places.controls()), headerMark: header.sortBy, listFocus: list.activeFocus, gridFocus: grid.activeFocus,
815
+ railFocus: places.focusItem.activeFocus, preset: Flea.ViewState.keysPreset,
816
+ bodySmall: Theme.font.bodySmall, body: Theme.font.body, width: win.width, height: win.height,
817
+ themeLoaded: Theme.ready, themeForeground: String(Theme.color.foreground),
818
+ geometry: {chrome: chrome.height, header: header.height, rail: places.width, row: Theme.rowHeight, footer: status.height,
819
+ save: save.height, list: list.height, saveViewport: win.bounds(save.scrollItem), saveScroll: save.scrollItem.contentY}, title: win.title, app: win.req.app})
820
+ }
821
+ }
822
+
823
+ function centre(item) {
824
+ if (!item) return ""
825
+ var point = item.mapToItem(win.contentItem, item.width / 2, item.height / 2)
826
+ return Math.round(point.x) + " " + Math.round(point.y)
827
+ }
828
+ function control(name, item, available) {
829
+ return {name: name, visible: item.visible, enabled: available, focused: item.activeFocus, centre: win.centre(item), bounds: win.bounds(item)}
830
+ }
831
+ function bounds(item) {
832
+ if (!item) return []
833
+ var point = item.mapToItem(win.contentItem, 0, 0)
834
+ return [point.x, point.y, item.width, item.height]
835
+ }
836
+ function stepFocus(from, back) {
837
+ var items = Picker.tabOrder({chrome: chrome.focusItems(), rail: places.focusItem, view: win.viewItem(), save: save.focusItems(), answers: answers.focusItems()})
838
+ if (!items.length) return
839
+ var at = items.indexOf(from)
840
+ // No origin lands on Cancel: a submission or a lost backend disables Filter and Open, and Filter is still enabled when this runs.
841
+ var next = from ? items[(at + (back ? -1 : 1) + items.length) % items.length] : (items.indexOf(answers.cancelItem) >= 0 ? answers.cancelItem : items[0])
842
+ if (next === list && shares.item && shares.item.active) shareFocus.forceActiveFocus(Qt.TabFocusReason)
843
+ else next.forceActiveFocus(Qt.TabFocusReason)
844
+ }
845
+ }
846
+ }