flea-qml 0.0.0-stage → 0.2.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 (637) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +511 -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 +407 -0
  51. package/src/archiveops.js +728 -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 +135 -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/terminal-server.js +95 -0
  82. package/src/terminal-shell.js +26 -0
  83. package/src/transfer.js +669 -0
  84. package/src/trash-record.js +41 -0
  85. package/src/trash.js +210 -0
  86. package/src/trashbrowse.js +327 -0
  87. package/src/uistate.js +393 -0
  88. package/src/uistore.js +168 -0
  89. package/src/vendor/LICENSES/Apache-2.0.txt +202 -0
  90. package/src/vendor/LICENSES/MIT-katex-2015.txt +27 -0
  91. package/src/vendor/LICENSES/MIT-katex.txt +21 -0
  92. package/src/vendor/LICENSES/MIT-mermaid.txt +21 -0
  93. package/src/vendor/LICENSES/MIT-pele.txt +40 -0
  94. package/src/vendor/LICENSES/MIT-perseus.txt +18 -0
  95. package/src/vendor/LICENSES/MIT-temml.txt +21 -0
  96. package/src/vendor/pele.js +145 -0
  97. package/src/vendor/temml.mjs +14708 -0
  98. package/src/watch.js +98 -0
  99. package/src/wayland/client.js +109 -0
  100. package/src/wayland/protocol.js +55 -0
  101. package/src/wayland/protocols/ext-data-control-v1.xml +276 -0
  102. package/src/wayland/protocols/wayland.xml +3248 -0
  103. package/src/wayland/protocols/wlr-data-control-unstable-v1.xml +278 -0
  104. package/src/wayland/resource-io.js +6 -0
  105. package/src/wayland/socket-discovery.js +26 -0
  106. package/src/wayland/transport-posix.js +120 -0
  107. package/src/wayland/transport-win32.js +11 -0
  108. package/src/wayland/transport.js +3 -0
  109. package/src/wayland/win32-resources.js +80 -0
  110. package/src/wayland/win32-wire.js +62 -0
  111. package/test/archive.test.js +80 -0
  112. package/test/bench-markdown-scroll.mjs +190 -0
  113. package/test/clip-host.test.js +113 -0
  114. package/test/code-preview.test.js +35 -0
  115. package/test/compare-ui-paths.test.js +24 -0
  116. package/test/compare.test.js +54 -0
  117. package/test/delete-trash.test.js +67 -0
  118. package/test/dirsize.test.js +138 -0
  119. package/test/file-ops.test.js +307 -0
  120. package/test/file-url.test.js +24 -0
  121. package/test/find-contents.test.js +161 -0
  122. package/test/find-input.test.js +66 -0
  123. package/test/fixtures/pele-parity.json +1006 -0
  124. package/test/highlight-launcher.test.js +35 -0
  125. package/test/html-preview.test.js +22 -0
  126. package/test/input.test.js +15 -0
  127. package/test/keymap.test.js +26 -0
  128. package/test/language-icons.test.js +36 -0
  129. package/test/launcher.test.js +51 -0
  130. package/test/manual-archive-here-ui.mjs +47 -0
  131. package/test/manual-code-scroll.mjs +28 -0
  132. package/test/manual-column-scroll.mjs +24 -0
  133. package/test/manual-compare-ui.mjs +20 -0
  134. package/test/manual-find-status.mjs +37 -0
  135. package/test/manual-folder-focus.mjs +63 -0
  136. package/test/manual-html-security.mjs +23 -0
  137. package/test/manual-html-ui.mjs +30 -0
  138. package/test/manual-syntax-preview.mjs +26 -0
  139. package/test/manual-terminal-lifecycle.mjs +42 -0
  140. package/test/manual-terminal-navigation.mjs +48 -0
  141. package/test/manual-terminal-ui.mjs +47 -0
  142. package/test/manual-trash-ui.mjs +86 -0
  143. package/test/markdown-diagrams.test.js +93 -0
  144. package/test/markdown-html.test.js +105 -0
  145. package/test/mathml.test.js +92 -0
  146. package/test/menu-transfer.test.js +57 -0
  147. package/test/office-preview.test.js +154 -0
  148. package/test/pele-parity.test.js +28 -0
  149. package/test/places.test.js +134 -0
  150. package/test/preview-keys.test.js +60 -0
  151. package/test/preview-language.test.js +22 -0
  152. package/test/status-filename.test.js +20 -0
  153. package/test/transfer-replace-merge.test.js +187 -0
  154. package/test/trash-address.test.js +23 -0
  155. package/test/trash-view.test.js +37 -0
  156. package/test/trashbrowse.test.js +135 -0
  157. package/ui/AboutFacts.qml +78 -0
  158. package/ui/ArchiveCards.qml +95 -0
  159. package/ui/ArchiveCompressDialog.qml +378 -0
  160. package/ui/ArchiveHereConfirm.qml +127 -0
  161. package/ui/ArchivePasswordDialog.qml +257 -0
  162. package/ui/Backend.qml +775 -0
  163. package/ui/BulkOldNames.qml +47 -0
  164. package/ui/BulkRenameDialog.qml +380 -0
  165. package/ui/CardFrame.qml +67 -0
  166. package/ui/CardScroll.qml +79 -0
  167. package/ui/CheckBox.qml +73 -0
  168. package/ui/ChromeBar.qml +360 -0
  169. package/ui/ChromeButton.qml +131 -0
  170. package/ui/ChromeModes.qml +43 -0
  171. package/ui/CodeLines.qml +256 -0
  172. package/ui/CodeRefusal.qml +52 -0
  173. package/ui/CollideConfirm.qml +147 -0
  174. package/ui/CollideHost.qml +167 -0
  175. package/ui/ColumnPane.qml +525 -0
  176. package/ui/ColumnRow.qml +380 -0
  177. package/ui/ColumnsArea.qml +594 -0
  178. package/ui/ColumnsLanding.qml +147 -0
  179. package/ui/CompareView.qml +321 -0
  180. package/ui/ContextMenu.qml +743 -0
  181. package/ui/ConvertDialog.qml +456 -0
  182. package/ui/CountedWorker.qml +7 -0
  183. package/ui/Crumb.qml +64 -0
  184. package/ui/CrumbDrop.qml +25 -0
  185. package/ui/CsvTable.qml +394 -0
  186. package/ui/CursorMark.qml +41 -0
  187. package/ui/DefaultClaim.qml +130 -0
  188. package/ui/DeviceLock.qml +47 -0
  189. package/ui/DeviceMounts.qml +540 -0
  190. package/ui/DialogButton.qml +150 -0
  191. package/ui/DialogField.qml +147 -0
  192. package/ui/DialogHeader.qml +31 -0
  193. package/ui/DialogScrim.qml +20 -0
  194. package/ui/DialogTitle.qml +37 -0
  195. package/ui/Divider.qml +9 -0
  196. package/ui/DropInto.qml +126 -0
  197. package/ui/DropTarget.qml +35 -0
  198. package/ui/DropboxMark.qml +60 -0
  199. package/ui/EmptyState.qml +150 -0
  200. package/ui/EscapeHint.qml +11 -0
  201. package/ui/FactsTable.qml +54 -0
  202. package/ui/FastScrollHandler.qml +476 -0
  203. package/ui/FatNamesConfirm.qml +157 -0
  204. package/ui/Favourites.qml +143 -0
  205. package/ui/FigureService.qml +120 -0
  206. package/ui/FileDrag.qml +267 -0
  207. package/ui/FilterStrip.qml +133 -0
  208. package/ui/FitMetrics.qml +22 -0
  209. package/ui/FlashDriveRows.qml +137 -0
  210. package/ui/FlashHost.qml +72 -0
  211. package/ui/FlashImageRow.qml +104 -0
  212. package/ui/FlashRunner.qml +119 -0
  213. package/ui/FlashStepRow.qml +172 -0
  214. package/ui/FlashStrip.qml +38 -0
  215. package/ui/FlashView.qml +397 -0
  216. package/ui/FleaMark.qml +88 -0
  217. package/ui/FlyoutFade.qml +23 -0
  218. package/ui/FontPane.qml +170 -0
  219. package/ui/Glass.qml +114 -0
  220. package/ui/GlassTagger.qml +59 -0
  221. package/ui/Glyph.qml +64 -0
  222. package/ui/GlyphSolid.qml +22 -0
  223. package/ui/GridArea.qml +664 -0
  224. package/ui/GridTile.qml +325 -0
  225. package/ui/GvfsBridge.qml +164 -0
  226. package/ui/Header.qml +433 -0
  227. package/ui/HoverTimers.qml +39 -0
  228. package/ui/HtmlClip.qml +30 -0
  229. package/ui/HtmlPane.qml +63 -0
  230. package/ui/HyprlandMark.qml +45 -0
  231. package/ui/Ipc.qml +1005 -0
  232. package/ui/IpcControls.qml +7 -0
  233. package/ui/IpcMenuState.qml +48 -0
  234. package/ui/IpcPreviewState.qml +185 -0
  235. package/ui/JumpPath.qml +51 -0
  236. package/ui/KeymapSheet.qml +561 -0
  237. package/ui/KeymapSheetResult.qml +89 -0
  238. package/ui/KindStack.qml +40 -0
  239. package/ui/LangMark.qml +51 -0
  240. package/ui/LangMarkLoader.qml +26 -0
  241. package/ui/List.qml +480 -0
  242. package/ui/LoadingState.qml +54 -0
  243. package/ui/LocalSend.qml +56 -0
  244. package/ui/LocalSendMark.qml +73 -0
  245. package/ui/MarkdownBlockView.qml +311 -0
  246. package/ui/MarkdownBlocks.qml +35 -0
  247. package/ui/MarkdownFigure.qml +254 -0
  248. package/ui/MarkdownImages.qml +72 -0
  249. package/ui/MarkdownList.qml +81 -0
  250. package/ui/MarkdownMathsText.qml +97 -0
  251. package/ui/MarkdownPane.qml +83 -0
  252. package/ui/MarkdownPictures.qml +41 -0
  253. package/ui/MarkdownQuote.qml +52 -0
  254. package/ui/MarkdownRemote.qml +82 -0
  255. package/ui/MarkdownSource.qml +155 -0
  256. package/ui/MarkdownTable.qml +222 -0
  257. package/ui/MarkdownTableScroll.qml +64 -0
  258. package/ui/MarkdownTableWheel.qml +126 -0
  259. package/ui/MarkdownText.qml +87 -0
  260. package/ui/MarkdownWorker.js +132 -0
  261. package/ui/MatchText.qml +99 -0
  262. package/ui/MediaSession.qml +38 -0
  263. package/ui/MediaSound.qml +26 -0
  264. package/ui/MediaStrip.qml +191 -0
  265. package/ui/MediaTrack.qml +36 -0
  266. package/ui/MediaVolume.qml +54 -0
  267. package/ui/MenuActionDialog.qml +396 -0
  268. package/ui/MenuEdgeFade.qml +24 -0
  269. package/ui/MenuHost.qml +227 -0
  270. package/ui/MenuRow.qml +351 -0
  271. package/ui/MountListing.qml +100 -0
  272. package/ui/NetworkDialog.qml +435 -0
  273. package/ui/NetworkForm.qml +426 -0
  274. package/ui/NetworkMounts.qml +913 -0
  275. package/ui/NetworkPlaces.qml +127 -0
  276. package/ui/OpenWithDialog.qml +613 -0
  277. package/ui/Opener.qml +95 -0
  278. package/ui/Pane.qml +1320 -0
  279. package/ui/PaneMenuActions.qml +527 -0
  280. package/ui/PanePath.qml +90 -0
  281. package/ui/PaneRail.qml +268 -0
  282. package/ui/PaneStates.qml +88 -0
  283. package/ui/PaneSwap.qml +339 -0
  284. package/ui/PaneWire.qml +601 -0
  285. package/ui/ParkedWait.qml +9 -0
  286. package/ui/PathEntry.qml +186 -0
  287. package/ui/PathJump.qml +400 -0
  288. package/ui/PdfViewer.qml +297 -0
  289. package/ui/PermissionsDialog.qml +658 -0
  290. package/ui/PermissionsHeadings.qml +31 -0
  291. package/ui/PhoneMounts.qml +67 -0
  292. package/ui/PickerAnswers.qml +75 -0
  293. package/ui/PickerBox.qml +26 -0
  294. package/ui/PickerChrome.qml +295 -0
  295. package/ui/PickerFilter.qml +97 -0
  296. package/ui/PickerGrid.qml +308 -0
  297. package/ui/PickerHeader.qml +28 -0
  298. package/ui/PickerLifecycle.qml +27 -0
  299. package/ui/PickerList.qml +218 -0
  300. package/ui/PickerListing.qml +149 -0
  301. package/ui/PickerPathBar.qml +127 -0
  302. package/ui/PickerPlaces.qml +189 -0
  303. package/ui/PickerRecent.qml +92 -0
  304. package/ui/PickerSave.qml +101 -0
  305. package/ui/PickerSelection.qml +155 -0
  306. package/ui/PickerWindow.qml +846 -0
  307. package/ui/PowerSectorsReader.qml +10 -0
  308. package/ui/PrebuildDriver.qml +146 -0
  309. package/ui/Preview.qml +1005 -0
  310. package/ui/PreviewAnimation.qml +24 -0
  311. package/ui/PreviewArchive.qml +91 -0
  312. package/ui/PreviewColumn.qml +553 -0
  313. package/ui/PreviewEdge.qml +63 -0
  314. package/ui/PreviewFacts.qml +30 -0
  315. package/ui/PreviewFactsLine.qml +34 -0
  316. package/ui/PreviewHtml.qml +233 -0
  317. package/ui/PreviewImage.qml +253 -0
  318. package/ui/PreviewImagePan.qml +13 -0
  319. package/ui/PreviewImageSharp.qml +32 -0
  320. package/ui/PreviewLines.qml +106 -0
  321. package/ui/PreviewMarkdown.qml +706 -0
  322. package/ui/PreviewMedia.qml +250 -0
  323. package/ui/PreviewPdf.qml +482 -0
  324. package/ui/PreviewScroll.qml +201 -0
  325. package/ui/PreviewSwap.qml +335 -0
  326. package/ui/PreviewText.qml +149 -0
  327. package/ui/ProtocolChip.qml +18 -0
  328. package/ui/QuickLookArchive.qml +41 -0
  329. package/ui/QuickLookBar.qml +103 -0
  330. package/ui/QuickLookPrepare.qml +355 -0
  331. package/ui/QuickLookSwap.qml +10 -0
  332. package/ui/QuickLookWarm.qml +65 -0
  333. package/ui/RailDrop.qml +155 -0
  334. package/ui/RailHeading.qml +19 -0
  335. package/ui/RailPlaces.qml +38 -0
  336. package/ui/RailTrashRow.qml +67 -0
  337. package/ui/RenameField.qml +353 -0
  338. package/ui/RendererRetry.qml +11 -0
  339. package/ui/ResizeHandle.qml +52 -0
  340. package/ui/Reveal.qml +54 -0
  341. package/ui/Row.qml +476 -0
  342. package/ui/RowDrag.qml +213 -0
  343. package/ui/RowDropTarget.qml +27 -0
  344. package/ui/RunCard.qml +286 -0
  345. package/ui/Scripts.qml +65 -0
  346. package/ui/SearchStrip.qml +232 -0
  347. package/ui/SelectionBand.qml +140 -0
  348. package/ui/SelectionPreview.qml +370 -0
  349. package/ui/SettingsFavourite.qml +122 -0
  350. package/ui/SettingsGroup.qml +96 -0
  351. package/ui/SettingsHint.qml +12 -0
  352. package/ui/SettingsMetrics.qml +29 -0
  353. package/ui/SettingsPane.qml +178 -0
  354. package/ui/SettingsPanel.qml +547 -0
  355. package/ui/SettingsRail.qml +128 -0
  356. package/ui/SettingsRow.qml +409 -0
  357. package/ui/SettingsRuler.qml +85 -0
  358. package/ui/SettingsSegment.qml +85 -0
  359. package/ui/ShareBrowser.qml +113 -0
  360. package/ui/ShareLink.qml +67 -0
  361. package/ui/ShelfPins.qml +101 -0
  362. package/ui/Sidebar.qml +728 -0
  363. package/ui/SidebarRow.qml +358 -0
  364. package/ui/Spinner.qml +60 -0
  365. package/ui/StableModel.qml +89 -0
  366. package/ui/StateMessage.qml +84 -0
  367. package/ui/StatusBar.qml +512 -0
  368. package/ui/StatusFileName.qml +27 -0
  369. package/ui/TabBar.qml +673 -0
  370. package/ui/TabDragGeometry.qml +60 -0
  371. package/ui/TagMenuMark.qml +52 -0
  372. package/ui/TagRailSection.qml +53 -0
  373. package/ui/TagSlot.qml +40 -0
  374. package/ui/TagState.qml +150 -0
  375. package/ui/TagSwatch.qml +28 -0
  376. package/ui/Taildrop.qml +86 -0
  377. package/ui/TailscaleMark.qml +50 -0
  378. package/ui/TerminalService.qml +69 -0
  379. package/ui/TerminalTab.qml +48 -0
  380. package/ui/TerminalTabs.qml +27 -0
  381. package/ui/Theme.qml +460 -0
  382. package/ui/ThemeColors.qml +43 -0
  383. package/ui/TransferCard.qml +239 -0
  384. package/ui/TrashConfirm.qml +129 -0
  385. package/ui/TrashDropHandler.qml +11 -0
  386. package/ui/TrashHost.qml +202 -0
  387. package/ui/TrashMonitor.qml +30 -0
  388. package/ui/TrashToolbar.qml +19 -0
  389. package/ui/TrashView.qml +492 -0
  390. package/ui/UnlockDialog.qml +450 -0
  391. package/ui/UnlockHost.qml +206 -0
  392. package/ui/UpdateCheck.qml +97 -0
  393. package/ui/UserDirs.qml +25 -0
  394. package/ui/ViewState.qml +565 -0
  395. package/ui/ViewportScrollBar.qml +195 -0
  396. package/ui/ViewportScrollBars.qml +25 -0
  397. package/ui/WebPreview.qml +33 -0
  398. package/ui/WebPreviewView.qml +26 -0
  399. package/ui/WindowBody.qml +829 -0
  400. package/ui/WindowDrag.qml +35 -0
  401. package/ui/boot/Commons/AudioNodes.qml +50 -0
  402. package/ui/boot/Commons/AudioNodesModel.js +96 -0
  403. package/ui/boot/Commons/Border.qml +243 -0
  404. package/ui/boot/Commons/BorderGeometry.js +373 -0
  405. package/ui/boot/Commons/Color.qml +258 -0
  406. package/ui/boot/Commons/I18n.qml +118 -0
  407. package/ui/boot/Commons/InputDevices.qml +112 -0
  408. package/ui/boot/Commons/InputMethodState.qml +38 -0
  409. package/ui/boot/Commons/IpcRegistry.qml +77 -0
  410. package/ui/boot/Commons/Numbers.js +19 -0
  411. package/ui/boot/Commons/Plural.js +91 -0
  412. package/ui/boot/Commons/ShellIpc.qml +12 -0
  413. package/ui/boot/Commons/Style.qml +561 -0
  414. package/ui/boot/Commons/Translations.qml +100 -0
  415. package/ui/boot/Commons/UntypedInput.qml +152 -0
  416. package/ui/boot/Commons/Util.qml +188 -0
  417. package/ui/boot/Commons/qmldir +13 -0
  418. package/ui/boot/LICENSE-omarchy +20 -0
  419. package/ui/boot/Ui/BackgroundMedia.qml +49 -0
  420. package/ui/boot/Ui/BarIconButton.qml +106 -0
  421. package/ui/boot/Ui/BarIconContent.qml +38 -0
  422. package/ui/boot/Ui/BarIndicator.qml +53 -0
  423. package/ui/boot/Ui/BarWidget.qml +50 -0
  424. package/ui/boot/Ui/BorderOverlay.qml +54 -0
  425. package/ui/boot/Ui/BorderSurface.qml +40 -0
  426. package/ui/boot/Ui/Button.qml +224 -0
  427. package/ui/boot/Ui/ButtonGroup.qml +131 -0
  428. package/ui/boot/Ui/ConfirmDialog.qml +134 -0
  429. package/ui/boot/Ui/CursorSurface.qml +42 -0
  430. package/ui/boot/Ui/Dropdown.qml +245 -0
  431. package/ui/boot/Ui/FilterInput.qml +26 -0
  432. package/ui/boot/Ui/IconContentBounds.qml +141 -0
  433. package/ui/boot/Ui/KeyboardPanel.qml +453 -0
  434. package/ui/boot/Ui/MultiSelect.qml +624 -0
  435. package/ui/boot/Ui/NumberField.qml +86 -0
  436. package/ui/boot/Ui/OpticalGlyph.qml +68 -0
  437. package/ui/boot/Ui/OverlayWindow.qml +102 -0
  438. package/ui/boot/Ui/Panel.qml +60 -0
  439. package/ui/boot/Ui/PanelActionButton.qml +105 -0
  440. package/ui/boot/Ui/PanelController.qml +16 -0
  441. package/ui/boot/Ui/PanelHero.qml +112 -0
  442. package/ui/boot/Ui/PanelKeyCatcher.qml +100 -0
  443. package/ui/boot/Ui/PanelSectionHeader.qml +31 -0
  444. package/ui/boot/Ui/PanelSeparator.qml +19 -0
  445. package/ui/boot/Ui/PanelSlider.qml +156 -0
  446. package/ui/boot/Ui/PanelToolTip.qml +50 -0
  447. package/ui/boot/Ui/PluginBarApi.qml +89 -0
  448. package/ui/boot/Ui/PointerMoveGate.qml +54 -0
  449. package/ui/boot/Ui/PopupCard.qml +181 -0
  450. package/ui/boot/Ui/ScreenMoveRemap.qml +43 -0
  451. package/ui/boot/Ui/SearchableDropdown.qml +354 -0
  452. package/ui/boot/Ui/SizedImage.qml +125 -0
  453. package/ui/boot/Ui/SpeedTestOverlay.qml +412 -0
  454. package/ui/boot/Ui/TextField.qml +58 -0
  455. package/ui/boot/Ui/Toggle.qml +118 -0
  456. package/ui/boot/Ui/ToggleSwitch.qml +115 -0
  457. package/ui/boot/Ui/WidgetButton.qml +172 -0
  458. package/ui/boot/Ui/qmldir +41 -0
  459. package/ui/boot/fleatab.qml +50 -0
  460. package/ui/boot/picker.qml +39 -0
  461. package/ui/boot/shell.qml +131 -0
  462. package/ui/boot/tabtearoff.qml +51 -0
  463. package/ui/js/Anchor.js +379 -0
  464. package/ui/js/AnchorHold.js +162 -0
  465. package/ui/js/Archive.js +157 -0
  466. package/ui/js/ArchiveCard.js +44 -0
  467. package/ui/js/ArchiveOps.js +44 -0
  468. package/ui/js/BackReturn.js +118 -0
  469. package/ui/js/BulkRename.js +100 -0
  470. package/ui/js/Buttons.js +54 -0
  471. package/ui/js/ChromeKeys.js +116 -0
  472. package/ui/js/ClipMarks.js +73 -0
  473. package/ui/js/ClipSelection.js +67 -0
  474. package/ui/js/Clipboard.js +179 -0
  475. package/ui/js/Cloud.js +97 -0
  476. package/ui/js/CodeRuns.js +72 -0
  477. package/ui/js/Collide.js +212 -0
  478. package/ui/js/ColumnFit.js +28 -0
  479. package/ui/js/ColumnMenu.js +111 -0
  480. package/ui/js/Columns.js +298 -0
  481. package/ui/js/ColumnsCarry.js +103 -0
  482. package/ui/js/Compare.js +74 -0
  483. package/ui/js/Contrast.js +157 -0
  484. package/ui/js/ControlReader.js +42 -0
  485. package/ui/js/Convert.js +52 -0
  486. package/ui/js/CopyAs.js +52 -0
  487. package/ui/js/Crumbs.js +178 -0
  488. package/ui/js/Csv.js +231 -0
  489. package/ui/js/Density.js +44 -0
  490. package/ui/js/Devices.js +440 -0
  491. package/ui/js/DirSizes.js +180 -0
  492. package/ui/js/Drag.js +360 -0
  493. package/ui/js/DragOut.js +166 -0
  494. package/ui/js/DropDev.js +52 -0
  495. package/ui/js/Dropbox.js +41 -0
  496. package/ui/js/Eject.js +217 -0
  497. package/ui/js/Errors.js +188 -0
  498. package/ui/js/ExtThumbs.js +115 -0
  499. package/ui/js/Facts.js +402 -0
  500. package/ui/js/FigureDate.js +14 -0
  501. package/ui/js/Filter.js +285 -0
  502. package/ui/js/Find.js +46 -0
  503. package/ui/js/Flash.js +184 -0
  504. package/ui/js/FlashCard.js +79 -0
  505. package/ui/js/FlashEndings.js +125 -0
  506. package/ui/js/FlashPane.js +116 -0
  507. package/ui/js/FleaBin.js +12 -0
  508. package/ui/js/Focus.js +504 -0
  509. package/ui/js/FolderSorts.js +108 -0
  510. package/ui/js/FolderViews.js +44 -0
  511. package/ui/js/Format.js +314 -0
  512. package/ui/js/Fuzzy.js +141 -0
  513. package/ui/js/GlassTag.js +72 -0
  514. package/ui/js/Grid.js +33 -0
  515. package/ui/js/GridGeometry.js +90 -0
  516. package/ui/js/GridNames.js +153 -0
  517. package/ui/js/GvfsBridge.js +200 -0
  518. package/ui/js/HostClipboard.js +29 -0
  519. package/ui/js/Icons.js +218 -0
  520. package/ui/js/Input.js +50 -0
  521. package/ui/js/Jump.js +200 -0
  522. package/ui/js/KeyName.js +64 -0
  523. package/ui/js/Keymap.js +401 -0
  524. package/ui/js/Kinds.js +164 -0
  525. package/ui/js/LICENSES/ISC-lucide.txt +43 -0
  526. package/ui/js/LangMarkPaths.js +47 -0
  527. package/ui/js/LangMarks.js +120 -0
  528. package/ui/js/Lists.js +11 -0
  529. package/ui/js/LocalSend.js +39 -0
  530. package/ui/js/LockedMenu.js +63 -0
  531. package/ui/js/MakeDefault.js +186 -0
  532. package/ui/js/MarkBox.js +7 -0
  533. package/ui/js/Markdown.js +119 -0
  534. package/ui/js/MarkdownLists.js +86 -0
  535. package/ui/js/MarkdownMaths.js +58 -0
  536. package/ui/js/MarkdownPictures.js +32 -0
  537. package/ui/js/MarkdownPrepared.js +139 -0
  538. package/ui/js/MarkdownTableFit.js +34 -0
  539. package/ui/js/Marks.js +148 -0
  540. package/ui/js/Marquee.js +96 -0
  541. package/ui/js/Match.js +93 -0
  542. package/ui/js/Material.js +109 -0
  543. package/ui/js/MdBlocks.js +437 -0
  544. package/ui/js/MdBreak.js +71 -0
  545. package/ui/js/MdChunks.js +70 -0
  546. package/ui/js/MdContainer.js +137 -0
  547. package/ui/js/MdDocument.js +337 -0
  548. package/ui/js/MdEmph.js +97 -0
  549. package/ui/js/MdEntity.js +108 -0
  550. package/ui/js/MdEntityTable.js +223 -0
  551. package/ui/js/MdEscape.js +59 -0
  552. package/ui/js/MdFront.js +37 -0
  553. package/ui/js/MdHold.js +32 -0
  554. package/ui/js/MdHtml.js +307 -0
  555. package/ui/js/MdHtmlBlock.js +290 -0
  556. package/ui/js/MdHtmlHeading.js +53 -0
  557. package/ui/js/MdHtmlImage.js +179 -0
  558. package/ui/js/MdInline.js +154 -0
  559. package/ui/js/MdItems.js +212 -0
  560. package/ui/js/MdLeaf.js +309 -0
  561. package/ui/js/MdLink.js +196 -0
  562. package/ui/js/MdMath.js +67 -0
  563. package/ui/js/MdParagraphs.js +138 -0
  564. package/ui/js/MdRefs.js +232 -0
  565. package/ui/js/MdResolve.js +226 -0
  566. package/ui/js/MdRun.js +300 -0
  567. package/ui/js/MdUrl.js +226 -0
  568. package/ui/js/Menu.js +514 -0
  569. package/ui/js/MenuContext.js +85 -0
  570. package/ui/js/MenuFit.js +13 -0
  571. package/ui/js/MenuRefresh.js +74 -0
  572. package/ui/js/MenuWheel.js +35 -0
  573. package/ui/js/Messages.js +186 -0
  574. package/ui/js/Motion.js +16 -0
  575. package/ui/js/Mounts.js +320 -0
  576. package/ui/js/MouseNav.js +13 -0
  577. package/ui/js/Nav.js +329 -0
  578. package/ui/js/NetFs.js +26 -0
  579. package/ui/js/OpenWith.js +83 -0
  580. package/ui/js/Ops.js +585 -0
  581. package/ui/js/Palette.js +33 -0
  582. package/ui/js/PathBar.js +260 -0
  583. package/ui/js/Permissions.js +179 -0
  584. package/ui/js/Phones.js +112 -0
  585. package/ui/js/Picker.js +353 -0
  586. package/ui/js/PlaceMenu.js +96 -0
  587. package/ui/js/Places.js +265 -0
  588. package/ui/js/PreviewAsk.js +21 -0
  589. package/ui/js/PreviewKeys.js +155 -0
  590. package/ui/js/PreviewSettle.js +29 -0
  591. package/ui/js/PreviewSwap.js +101 -0
  592. package/ui/js/PropertyFacts.js +144 -0
  593. package/ui/js/Protocols.js +296 -0
  594. package/ui/js/QuickLookKinds.js +27 -0
  595. package/ui/js/QuickLookRect.js +24 -0
  596. package/ui/js/RailDrop.js +51 -0
  597. package/ui/js/RailKeys.js +87 -0
  598. package/ui/js/RailMenu.js +133 -0
  599. package/ui/js/Recent.js +135 -0
  600. package/ui/js/RecentMode.js +156 -0
  601. package/ui/js/Reload.js +60 -0
  602. package/ui/js/Renderer.js +9 -0
  603. package/ui/js/RunFile.js +143 -0
  604. package/ui/js/Scripts.js +28 -0
  605. package/ui/js/Scroll.js +304 -0
  606. package/ui/js/ScrollOff.js +69 -0
  607. package/ui/js/Search.js +478 -0
  608. package/ui/js/Selection.js +97 -0
  609. package/ui/js/Settings.js +476 -0
  610. package/ui/js/SettingsAbout.js +97 -0
  611. package/ui/js/SettingsShelf.js +52 -0
  612. package/ui/js/SheetKeys.js +42 -0
  613. package/ui/js/SheetQuery.js +281 -0
  614. package/ui/js/ShelfPile.js +27 -0
  615. package/ui/js/SlowClick.js +53 -0
  616. package/ui/js/SlowOp.js +6 -0
  617. package/ui/js/Sort.js +134 -0
  618. package/ui/js/Startup.js +63 -0
  619. package/ui/js/StateWords.js +17 -0
  620. package/ui/js/Status.js +238 -0
  621. package/ui/js/Swap.js +166 -0
  622. package/ui/js/TabMove.js +39 -0
  623. package/ui/js/Tabs.js +751 -0
  624. package/ui/js/Tags.js +134 -0
  625. package/ui/js/Taildrop.js +128 -0
  626. package/ui/js/Tap.js +180 -0
  627. package/ui/js/TextSize.js +107 -0
  628. package/ui/js/Thumbs.js +158 -0
  629. package/ui/js/Transfer.js +172 -0
  630. package/ui/js/Trash.js +84 -0
  631. package/ui/js/TrashDates.js +47 -0
  632. package/ui/js/TrashDrop.js +64 -0
  633. package/ui/js/UiState.js +438 -0
  634. package/ui/js/Unlock.js +152 -0
  635. package/ui/js/Update.js +130 -0
  636. package/ui/js/UserDirs.js +56 -0
  637. package/ui/qmldir +230 -0
package/ui/Ipc.qml ADDED
@@ -0,0 +1,1005 @@
1
+ import QtQuick
2
+ import Quickshell.Io
3
+ import qs.Commons
4
+ import "js/Tabs.js" as Tabs
5
+ import "js/Search.js" as Search
6
+ // ControlReader.js loads with IpcControls.qml on the first cohesionControls call, never with the first frame.
7
+
8
+ // The seam the tests drive, see AGENTS.md "Testing". Read-only: it reports, never acts.
9
+ QtObject {
10
+ id: root
11
+
12
+ property var fleaWindow: null
13
+ property var pane: null
14
+ property var panes: []
15
+ property var bar: null
16
+ property var backend: null
17
+ property var chrome: null
18
+ property var tabBar: null
19
+ property var convertDialog: null
20
+ property var keymapSheet: null
21
+ property var settingsPanel: null
22
+ property var networkDialog: null
23
+ property var shareBrowser: null
24
+ property var emptyState: null
25
+ property var permissionsDialog: null
26
+ property var archiveCards: null
27
+ property var unlockHost: null
28
+ property IpcMenuState menuProbe: IpcMenuState { pane: root.pane; fleaWindow: root.fleaWindow }
29
+ property IpcPreviewState previewReaders: IpcPreviewState {
30
+ fleaWindow: root.fleaWindow
31
+ pane: root.pane
32
+ }
33
+ property var bulkRenameDialog: null
34
+ // Overlays and the columns view are built by their first open, see ui/shell.qml, so until then
35
+ // each reader below answers the empty value its type has: "", false or -1, never a throw.
36
+ readonly property var columns: root.pane ? root.pane.columnsArea : null
37
+ function controlState(name, item) {
38
+ return {name: name, visible: !!item && item.visible, enabled: !!item && item.enabled
39
+ && (item.available === undefined || item.available), focused: !!item && item.activeFocus, hovered: !!item && !!item.hovered,
40
+ pressed: !!item && !!item.pressed, centre: item ? root.fleaWindow.centreOf(item) : "", rect: item ? root.fleaWindow.rectOf(item) : ""}
41
+ }
42
+ // Which role a frame draws in, so a capture asserts the frame's colour without knowing the theme.
43
+ function roleOf(color) {
44
+ return Qt.colorEqual(color, Theme.color.error) ? "error" : Qt.colorEqual(color, Theme.color.accent) ? "accent" : Qt.colorEqual(color, Theme.color.mutedFrame) ? "mutedFrame" : Qt.colorEqual(color, Theme.color.muted) ? "muted" : "other"
45
+ }
46
+ // A dialog's rect at rest: the open fade lifts its card by a Translate for 180 ms, so a read in that window adds the rise back out.
47
+ function cardRectOf(item) {
48
+ var rect = root.fleaWindow.rectOf(item), rise = 0
49
+ for (var up = item; up; up = up.parent)
50
+ if (up.transform.length > 0) rise += up.transform[0].y
51
+ if (rect === "" || rise === 0) return rect
52
+ var edges = rect.split(" ")
53
+ edges[1] = String(Number(edges[1]) - Math.round(rise))
54
+ return edges.join(" ")
55
+ }
56
+ // The Trash row as the rail draws it: current is the lit place, not the keyboard cursor.
57
+ function trashRail() {
58
+ for (var index = 0; index < root.pane.railCount; index++) {
59
+ var row = root.pane.sidebar.railItemFor(index)
60
+ if (row && row.modelData.kind === "trash") return {current: row.lit, countText: row.detail}
61
+ }
62
+ return null
63
+ }
64
+ function confirmationState(item) {
65
+ return item ? {opened: item.opened, token: item.snapshot.token || 0, count: item.snapshot.count || 0,
66
+ all: item.snapshot.all === true, destructiveFocus: item.destructiveFocus, title: item.titleText, body: item.bodyText,
67
+ rect: root.cardRectOf(item.cardItem), cancel: root.controlState("Cancel", item.cancelItem),
68
+ danger: root.controlState("Delete", item.dangerItem)} : {opened: false}
69
+ }
70
+ function firstVisibleText(item) {
71
+ if (!item || !item.visible) return ""
72
+ if (typeof item.text === "string" && item.text.length > 0) return item.text
73
+ for (var i = 0; i < item.children.length; i++) {
74
+ var text = root.firstVisibleText(item.children[i])
75
+ if (text.length > 0) return text
76
+ }
77
+ return ""
78
+ }
79
+
80
+ // The wrapper holds the references because an IpcHandler marshals every property it owns.
81
+ property IpcHandler seam: IpcHandler {
82
+ target: "flea"
83
+ function ready(): bool { return true }
84
+ function themeLoaded(): bool { return Theme.ready }
85
+ // Sample output: 1100 800; the content the dialogs centre in, which ui-permissions.sh waits to equal hyprctl's size.
86
+ function windowSize(): string { var c = root.fleaWindow.contentItem; return Math.round(c.width) + " " + Math.round(c.height) }
87
+ function themeForeground(): string { return String(Theme.color.opaquePalette.foreground) }
88
+ // Sample output: {"clear":true,"alpha":0.3411764705882353,"window":"#00000000","early":true}; tests/glass-boot.sh reads the window the entry made.
89
+ function glass(): string { return JSON.stringify({ clear: Glass.clear, mode: Glass.mode, focused: Glass.focused, automatic: Glass.usesDefault, alpha: Glass.alpha, listAlpha: Glass.listAlpha, foreground: String(Theme.color.foreground), muted: String(Theme.color.muted), metadata: String(Theme.color.metadata), window: String(root.fleaWindow.color), early: root.fleaWindow.clearedEarly }) }
90
+ function selectedFill(): string { return String(Style.selectedFill) }
91
+ function palette(): string {
92
+ var c = Theme.color;
93
+ return [c.background, c.surface, c.foreground, c.muted, c.accent, c.error, c.symlink, c.executable, c.accentFrame, c.mutedFrame].join(" ");
94
+ }
95
+ // The size running text really draws at, and a row name's own: the settings case pins both to the stop.
96
+ function bodyPx(): int { return Theme.font.body }
97
+ function rowNamePx(i: int): int { var item = root.pane.itemFor(i); return item ? item.namePx : -1 }
98
+ function metrics(): string { return Theme.font.bodySmall + " " + Theme.font.caption + " " + Theme.spacing.rowPaddingX + " " + Theme.rowHeight }
99
+ // A file row at the stored density; metrics() keeps the board rowHeight, which density never moves.
100
+ function fileRowHeight(): int { return Theme.fileRowHeight }
101
+ // Every token the Blueprint board states, one key=value per line; tools/flea-metrics-gate diffs it. metrics() above stays positional for tests/ui.sh.
102
+ function tokens(): string { return Theme.tokens() }
103
+ function cursor(): int { return root.pane.cursorIndex }
104
+ function gridColumns(): int { return root.pane.cursorStride }
105
+ function gridCaptionState(i: int): string {
106
+ var item = root.pane.visibleItemFor(i)
107
+ if (!item || !item.captionItem) return "{}"
108
+ var caption = item.captionItem
109
+ return JSON.stringify({name: caption.text, wrapAt: caption.text.indexOf("\n"), broken: !caption.truncated && caption.lineCount === caption.text.split("\n").length, lines: caption.lineCount, truncated: caption.truncated,
110
+ textHeight: caption.contentHeight, slotHeight: caption.height,
111
+ bottom: caption.y + caption.height, tileHeight: item.height})
112
+ }
113
+ function drawnCount(): int { return root.pane.listArea.count }
114
+ function total(): int { return root.pane.total }
115
+ function selectionCount(): int { return root.pane.selectionCount() }
116
+ function favouritesSaving(): bool { return Favourites.busy || Favourites.operationActive }
117
+ function selectedIndices(): string { return root.pane.selectedIndices().join(",") }
118
+ function fileClipboard(): string { return JSON.stringify(root.pane.clipboard) }
119
+ function rowClipMark(i: int): string { var row = root.pane.itemFor(i); return row ? row.clipMark : "unavailable" }
120
+ function focusView(): string { return root.pane.focusView }
121
+ function focusStop(): string { var p = root.pane; return p.focusView === "chrome" ? "chrome:" + p.chromeBar.focusedGlyph : p.focusView === "strip" ? "strip:" + p.tabStrip.focusedIndex : p.focusView }
122
+ function keyDeliveryState(): string {
123
+ var pane = root.pane
124
+ var window = pane.Window.window
125
+ return JSON.stringify({keySequence: pane.keySequence, keySequenceIdentity: pane.keySequenceIdentity,
126
+ trashArmedAt: pane.trashArmedAt, clipboard: {paths: pane.clipboard.paths, cut: pane.clipboard.moving},
127
+ searchMode: pane.searchMode, filterTyping: pane.filterTyping, filterQuery: pane.filterQuery,
128
+ searchRunning: pane.searchRunning, searchQuery: pane.searchQuery,
129
+ searchScanned: pane.searchScanned, searchCancelled: pane.searchCancelled,
130
+ paneFocus: pane.activeFocus, listFocus: pane.listArea.activeFocus,
131
+ activeFocusItem: window ? String(window.activeFocusItem) : "",
132
+ preview: {index: pane.previewIndex, focused: !!pane.previewColumnItem && pane.previewColumnItem.activeFocus},
133
+ history: {back: pane.history.map(e => typeof e === "string" ? e : e.path), forward: pane.forwardHistory.map(e => typeof e === "string" ? e : e.path)}})
134
+ }
135
+ function visibleRowName(i: int): string {
136
+ var item = root.pane.visibleItemFor(i)
137
+ return item && item.visible && item.row ? item.row.n : ""
138
+ }
139
+ // Sample output: 33252, the st_mode the held row carries, -1 for a row that is not drawn.
140
+ function visibleRowMode(i: int): int {
141
+ var item = root.pane.visibleItemFor(i)
142
+ return item && item.visible && item.row ? Number(item.row.p) : -1
143
+ }
144
+ function railCursor(): int { return root.pane.railCursor }
145
+ function railCount(): int { return root.pane.railCount }
146
+ function railState(): string { return JSON.stringify({hidden: root.pane.railHidden, width: root.pane.sidebarWidth, inset: root.pane.railInset, pane: root.pane.width, revealed: root.pane.railRevealed, x: root.pane.railX}) }
147
+ function path(): string { return root.pane.path }
148
+ function dualState(): string {
149
+ return JSON.stringify({active: ViewState.state.view === "dual",
150
+ focused: root.panes.indexOf(root.pane), panes: root.panes.map(function(pane) {
151
+ return pane ? {path: pane.path, total: pane.total, cursor: pane.cursorIndex,
152
+ loading: pane.listInFlight, selected: pane.selectedIndices(),
153
+ focused: pane.activeFocus || pane.listArea.activeFocus,
154
+ listRequests: pane.backend.listRequests} : null
155
+ })})
156
+ }
157
+ function lastMessage(): string { return root.bar.transient_ }
158
+ function statusPrimary(): string { return root.bar.centreText() }
159
+ function statusColor(): string { return String(root.bar.centreColor()) }
160
+ function statusSecondary(): string { return root.bar.secondaryText }
161
+ function statusError(): bool { return root.bar.transientIsError }
162
+ function statusDetail(): string { return root.bar.errorDetail }
163
+ function statusActivityState(): string {
164
+ var card = root.bar.transferCard
165
+ return JSON.stringify({activities: root.bar.activities.map(function(activity) {
166
+ var owner = activity.owner === root.bar.dragFeedbackOwner ? activity.owner.host : activity.owner
167
+ return {id: activity.transfer.id, text: activity.text, running: activity.transfer.running,
168
+ cancelling: activity.cancelling, ownerPath: owner.path, ownerFocused: owner.paneFocused}
169
+ }), errors: root.bar.errors.length, notice: root.bar.notice,
170
+ undoAvailable: root.bar.hasUndo,
171
+ transferCard: {visible: !!card && card.visible, cancelling: !!card && card.cancelling, byteLine: card ? card.byteText : "", headline: card ? card.headlineText : "", writing: !!card && !!card.shown && card.shown.writing === true,
172
+ rect: root.cardRectOf(card), cancel: root.controlState("Cancel", card ? card.cancelItem : null), undo: root.controlState("z undoes", root.bar.undoItem)}})
173
+ }
174
+ function statusFooterState(): string {
175
+ // Every zone's x in the strip's own coordinates: the centre pair sits in a Row inside the middle slot, so its own x is relative to that Row and says nothing about zone order.
176
+ function textState(item) {
177
+ return {text: item.text, visible: item.visible, width: item.width, implicitWidth: item.implicitWidth,
178
+ x: Math.round(item.mapToItem(root.bar.stripItem, 0, 0).x), truncated: item.truncated,
179
+ color: String(item.color), fontSize: item.font.pixelSize}
180
+ }
181
+ return JSON.stringify({path: root.bar.path, total: root.bar.total, selected: root.bar.selectionCount,
182
+ listingState: root.bar.listingState, filesystem: root.bar.fsText(), counts: root.bar.countText(),
183
+ frame: root.fleaWindow.rectOf(root.bar.stripItem), borderWidth: root.bar.stripItem.border.width,
184
+ zoneWidth: root.bar.zoneWidth, hintWidth: root.bar.hintWidth,
185
+ left: textState(root.bar.countsItem), centre: textState(root.bar.primaryItem),
186
+ secondary: textState(root.bar.secondaryItem), undo: textState(root.bar.undoItem),
187
+ disk: textState(root.bar.diskItem), lane: {x: root.bar.centreItem.mapToItem(root.bar.stripItem, 0, 0).x, width: root.bar.centreItem.width}})
188
+ }
189
+ function selectionBandState(): string {
190
+ var band = root.pane.selectionBand
191
+ return JSON.stringify(band ? {tracking: band.tracking, active: band.bandActive,
192
+ columns: band.columns, contentY: band.flickable.contentY, scrollRate: band.scrollRate,
193
+ anchor: {x: band.anchor.x, y: band.anchor.y}, end: {x: band.end.x, y: band.end.y}}
194
+ : {tracking: false, active: false})
195
+ }
196
+ function listingWindowState(): string {
197
+ return JSON.stringify({held: root.pane.held, loaded: root.pane.rows.length,
198
+ windowSize: root.pane.windowSize, total: root.pane.total, shownTotal: root.pane.shownTotal})
199
+ }
200
+ function menuState(): string { return root.menuProbe.state() }
201
+ function contextMenuModel(): string { return JSON.stringify(root.pane.contextMenu().entries) }
202
+ function providerState(): string {
203
+ var pane = root.pane, actions = pane.menuActions, taildrop = pane.taildropService, dropbox = pane.dropboxService
204
+ return JSON.stringify({facts: pane.backend.providers, formatsRequests: pane.backend.formatsToken,
205
+ refreshing: actions.providersRefreshing,
206
+ pendingActivation: actions.pendingActivation, menuFocus: pane.contextMenu().keyboardFocused,
207
+ listFocus: pane.listArea.activeFocus, path: pane.path, cursor: pane.cursorIndex,
208
+ cursorPath: pane.cursorRow ? pane.join(Search.baseOf(pane), pane.cursorRow.n) : "",
209
+ selected: pane.selectedIndices(), selectedPaths: pane.selectedIndices().map(function(index) {
210
+ var row = pane.rowFor(index)
211
+ return row ? pane.join(Search.baseOf(pane), row.n) : null
212
+ }),
213
+ taildrop: {checking: taildrop.checking, reason: taildrop.reason, peers: taildrop.peers,
214
+ timeoutSeconds: taildrop.statusTimeoutSeconds},
215
+ dropbox: dropbox ? {checking: dropbox.dropboxChecking, ready: dropbox.dropboxReady,
216
+ metadataBusy: dropbox._dropboxMetadataRequest !== 0 || dropbox._dropboxMetadataAgain,
217
+ reason: dropbox.dropboxReason, path: dropbox.dropboxPath,
218
+ timeoutSeconds: dropbox.dropboxStatusTimeoutSeconds} : null})
219
+ }
220
+ function contextMenuSubmenuRowCentre(index: int): string { return root.fleaWindow.centreOf(root.pane.contextMenu().submenuItemFor(index)) }
221
+ function menuDialogState(): string {
222
+ var dialog = root.pane.menuActions.item
223
+ if (!dialog) return JSON.stringify({opened: false})
224
+ // Two dialogs load into that one Loader: the application rows belong to Open with alone, the live count lines to the other.
225
+ var applications = dialog.applications !== undefined ? dialog.applications : []
226
+ var liveLines = dialog.liveLines !== undefined ? dialog.liveLines : {}
227
+ var cursor = dialog.cursor !== undefined ? dialog.cursor : 0
228
+ return JSON.stringify({opened: dialog.opened, action: dialog.action, busy: dialog.busy,
229
+ committing: dialog.committing, error: dialog.errorText, facts: dialog.facts, liveLines: liveLines,
230
+ applications: applications, cursor: cursor, rect: root.cardRectOf(dialog.cardItem),
231
+ controls: [root.controlState("Cancel", dialog.closeItem),
232
+ Object.assign(root.controlState("Submit", dialog.submitItem), {enabled: dialog.canSubmit}),
233
+ root.controlState("Field", dialog.fieldItem), root.controlState("Applications", dialog.applicationsItem)],
234
+ confirmation: root.confirmationState(dialog.confirmationItem)})
235
+ }
236
+ // The paste-collision card: what it asks, the names it lists, which button Enter takes, and that no label wrapped.
237
+ function collideState(): string {
238
+ var card = root.pane.collide ? root.pane.collide.item : null
239
+ return JSON.stringify(card ? {opened: card.opened, title: card.titleText, titleTruncated: card.titleTruncated,
240
+ names: card.names.map(function (n) { return n.n }), more: card.moreText, explain: card.explainText, focus: card.focusName,
241
+ buttonsFit: card.buttonsFit, explainLines: card.explainLines, rect: root.cardRectOf(card.cardItem),
242
+ buttons: ["cancel", "skip", "keep", "merge", "replace"].map(function (b) { return root.controlState(b, card.buttonItem(b)) })} : {opened: false})
243
+ }
244
+ // The run card: whether it asks, what it names, which buttons it draws and where the ring sits.
245
+ function runCardState(): string {
246
+ var card = root.pane && root.pane.overlayParent ? root.pane.overlayParent.runCard : null
247
+ if (!card || !card.opened) return JSON.stringify({opened: false})
248
+ return JSON.stringify({opened: true, title: card.titleText, kind: card.kind, buttons: card.buttons, focus: card.focusName, default: card.defaultName,
249
+ rect: root.cardRectOf(card.cardItem),
250
+ controls: card.buttons.map(function (b) { var it = card.buttonItem(b); return {name: b, label: it.label, primary: !!it.primary, focused: !!it.focused, hovered: !!it.hovered, pressed: !!it.pressed, centre: root.fleaWindow.centreOf(it)} })})
251
+ }
252
+ // OpenWith.html's own card: the two groups it draws, the seat the cursor holds, and the
253
+ // geometry a matched-size check measures against the board.
254
+ function openWithState(): string {
255
+ var dialog = root.pane.menuActions.item
256
+ if (!dialog || dialog.action !== "openWith") return JSON.stringify({opened: false})
257
+ return JSON.stringify({opened: dialog.opened, busy: dialog.busy, committing: dialog.committing,
258
+ kind: dialog.kind, mime: dialog.mime, name: dialog.name, always: dialog.always,
259
+ error: dialog.errorText, search: dialog.fieldItem.text, cursor: dialog.cursor,
260
+ rows: dialog.rows.map(function (row) {
261
+ return row.eyebrow !== undefined ? {eyebrow: row.eyebrow, rule: row.rule === true}
262
+ : {id: row.id, label: row.label, icon: row.icon, isDefault: row.default === true}
263
+ }),
264
+ rect: root.cardRectOf(dialog.cardItem),
265
+ rowHeight: Theme.rowHeight, eyebrowHeight: dialog.eyebrowHeight,
266
+ titleRuleRect: root.fleaWindow.rectOf(dialog.titleRuleItem), searchRect: root.fleaWindow.rectOf(dialog.searchItem),
267
+ gap: Theme.spacing.gap, searchTrail: dialog.searchTrail,
268
+ listRect: root.fleaWindow.rectOf(dialog.applicationsItem),
269
+ rowRect: root.fleaWindow.rectOf(dialog.applicationItem(dialog.cursor)),
270
+ controls: [root.controlState("Field", dialog.fieldItem), root.controlState("Applications", dialog.applicationsItem),
271
+ root.controlState("Always", dialog.alwaysItem), root.controlState("Cancel", dialog.closeItem),
272
+ Object.assign(root.controlState("Open", dialog.submitItem), {enabled: dialog.canSubmit})]})
273
+ }
274
+ function permissionsState(): string {
275
+ var probe = root, dialog = probe.permissionsDialog, inputReady = probe.pane.listArea.activeFocus && !probe.pane.listInFlight
276
+ if (!dialog) return JSON.stringify({opened: false, inputReady: inputReady})
277
+ return JSON.stringify({opened: dialog.opened, inputReady: !dialog.opened && !dialog.visible && inputReady, facts: dialog.facts, path: dialog.path, mode: dialog.modeText,
278
+ title: root.firstVisibleText(dialog.cardItem), paths: dialog.multiPaths,
279
+ displayedError: dialog.displayedError, displayedSummary: dialog.displayedSummary,
280
+ bodyRect: root.cardRectOf(dialog.bodyItem), octalFrame: root.roleOf(dialog.octalFrame.border.color),
281
+ editable: dialog.editable, busy: dialog.busy, error: dialog.errorText, rect: root.cardRectOf(dialog.cardItem),
282
+ controls: dialog.controls().map(function(control) {
283
+ var box = control.item.children.find(function(child) { return typeof child.value === "string" }), point = typeof control.item.hovered === "boolean" ? control.item : control.item.children.find(function(child) { return typeof child.hovered === "boolean" })
284
+ return Object.assign(root.controlState(control.name, control.item), {checked: control.checked, bit: control.bit,
285
+ value: box ? box.value : undefined, hovered: !!point && point.hovered, pressed: !!point && point.pressed,
286
+ ring: !!control.item.ringItem && control.item.ringItem.visible})
287
+ })})
288
+ }
289
+ function trashState(): string {
290
+ var view = root.pane.trash.item
291
+ var rail = root.trashRail()
292
+ if (!view) return JSON.stringify({opened: false, count: root.pane.sidebar.trashCount, rail: rail})
293
+ return JSON.stringify({opened: view.opened, total: view.total, count: root.pane.sidebar.trashCount, countText: view.countText, busy: view.busy,
294
+ operationActive: view.operationActive, cursor: view.cursor, first: view.first,
295
+ selectedCount: view.selectedCount, selectionToken: view.selectionToken, selectionCount: view.selectionCount,
296
+ headerLabels: view.headerLabels, upEnabled: view.upItem.enabled, rail: rail,
297
+ rows: view.rows.map(function(row) { return Object.assign({}, row, {selected: view.isSelected(row.uri)}) }),
298
+ confirmation: root.confirmationState(view.confirmationItem)})
299
+ }
300
+ function trashRowCentre(index: int): string { return root.pane.trash.item ? root.fleaWindow.centreOf(root.pane.trash.item.rowItemFor(index)) : "" }
301
+ function trashControlCentre(name: string): string {
302
+ var view = root.pane.trash.item
303
+ if (!view) return ""
304
+ var item = ({back: view.backItem, up: view.upItem, empty: view.emptyItem, cancel: view.confirmationItem.cancelItem, danger: view.confirmationItem.dangerItem})[name]
305
+ return item ? root.fleaWindow.centreOf(item) : ""
306
+ }
307
+
308
+ // The sticky slot an operation holds while it runs, so a test can name the verb in flight.
309
+ function stickyMessage(): string { return root.bar.sticky }
310
+ // Whether the busy mark walks while the GVFS bridge's Starting line stands; tests/js/status.js pins that the sentence alone does not light it.
311
+ function statusBusy(): string { return String(root.bar.busy) }
312
+ function firstRowsAt(): string { return String(root.backend.firstRowsAt) }
313
+ // Per-keystroke stopwatch for the newest search gen, read-only after a run.
314
+ function searchStamps(): string { return JSON.stringify(Search.stamps(root.pane)) }
315
+ // Both instants in one read, so no IPC call of a harness ever lands inside the interval it is timing.
316
+ function inputToRows(): string { return String(root.pane.inputAt) + " " + String(root.pane.rowsAt) }
317
+ // The filter stopwatch: the newest keystroke's view and when its narrowing reached the shown model.
318
+ function filterStamps(): string {
319
+ return JSON.stringify({view: root.pane.viewMode, keyAt: root.pane.filterKeyAt, narrowedAt: root.pane.filterNarrowedAt})
320
+ }
321
+ function mode(): string { return "browse" }
322
+ function state(): string { return root.pane.listingState }
323
+ function listInFlight(): bool { return root.pane.listInFlight }
324
+ function stateMessage(): string { return root.pane.stateMessage }
325
+ function contextMenuVisible(): bool { return root.pane.menuVisible }
326
+ function showHidden(): bool { return root.pane.showHidden }
327
+ // One label per current menu row, joined so a test can assert contents without OCR. A
328
+ // separator has no label of its own and reads as "-", which is what makes the grouping assertable.
329
+ function contextMenuEntries(): string {
330
+ var entries = root.pane.contextMenu().entries
331
+ var out = []
332
+ for (var i = 0; i < entries.length; i++) {
333
+ out.push(entries[i].separator === true ? "-" : entries[i].label)
334
+ }
335
+ return out.join("|")
336
+ }
337
+ // The key each row prints beside its label, in the same order and read off the drawn rows,
338
+ // so an unbound row's empty slot is assertable and not only the map behind it.
339
+ function contextMenuHints(): string {
340
+ var menu = root.pane.contextMenu()
341
+ var out = []
342
+ for (var i = 0; i < menu.entries.length; i++) {
343
+ var item = menu.itemFor(i)
344
+ out.push(item ? String(item.hint) : "")
345
+ }
346
+ return out.join("|")
347
+ }
348
+ // The glyph each row draws, in the same order, so the "every row is marked" rule is assertable.
349
+ function contextMenuGlyphs(): string {
350
+ var entries = root.pane.contextMenu().entries
351
+ var out = []
352
+ for (var i = 0; i < entries.length; i++) {
353
+ // A brand mark has no glyph name, so the reader names the mark instead; a row draws
354
+ // exactly one of the two and a test asserts the same string either way.
355
+ out.push(entries[i].separator === true ? "-"
356
+ : (entries[i].mark || entries[i].glyph || ""))
357
+ }
358
+ return out.join("|")
359
+ }
360
+ // A peer row names a machine and an archive row a file, so the flyout's mark is its own.
361
+ function contextMenuSubmenuGlyphs(): string { return root.pane.contextMenu().submenuGlyphs() }
362
+ // A peer is named by whoever is on the tailnet, so a test reads the name here rather than
363
+ // knowing it. No separator branch: every flyout in the tree builds {id, label} rows only,
364
+ // whether it came from Archive.formatEntries, Menu.sortEntries or the Taildrop peer list.
365
+ function contextMenuSubmenuEntries(): string {
366
+ return root.pane.contextMenu().submenuEntries.map(function (e) { return e.label }).join("|")
367
+ }
368
+ function contextMenuCursor(): int { return root.pane.menuCursor }
369
+ function settingsOpen(): bool { return root.settingsPanel ? root.settingsPanel.opened : false }
370
+ function settingsSection(): string { return root.settingsPanel ? root.settingsPanel.section : "" }
371
+ function settingsSide(): string { return root.settingsPanel ? root.settingsPanel.side : "" }
372
+ function settingsCursor(): int { return root.settingsPanel ? root.settingsPanel.cursor : -1 }
373
+ // One row per line, kind|label|value, so a test reads what the panel draws without OCR and
374
+ // the stored value behind each control is assertable from the same string.
375
+ function settingsRows(): string { return root.settingsPanel ? root.settingsPanel.rowsText() : "" }
376
+ function settingsModel(): string { return root.settingsPanel ? JSON.stringify(root.settingsPanel.rows) : "[]" }
377
+ function settingsSections(): string { return root.settingsPanel ? root.settingsPanel.sectionsText() : "[]" }
378
+ function settingsRowCentre(id: string): string { return root.settingsPanel ? root.fleaWindow.centreOf(root.settingsPanel.rowItemForId(id)) : "" }
379
+ function settingsFavouriteControlCentre(id: string, part: string): string {
380
+ var row = root.settingsPanel ? root.settingsPanel.rowItemForId(id) : null
381
+ if (!row || !row.isFavourite) return ""
382
+ var item = part === "drag" ? row.favouriteItem.dragItem
383
+ : part === "remove" ? row.favouriteItem.removeItem : null
384
+ return item && item.visible ? root.fleaWindow.centreOf(item) : ""
385
+ }
386
+ function uiSettings(): string { return JSON.stringify(ViewState.state) }
387
+ function keymapPreset(): string { return ViewState.keysPreset }
388
+ function railEntries(): string { return JSON.stringify(root.pane.sidebar.entries) }
389
+ function railDetails(): string {
390
+ var sidebar = root.pane.sidebar
391
+ function box(item) {
392
+ var point = item.mapToItem(null, 0, 0)
393
+ return {x: point.x, y: point.y, width: item.width, height: item.height}
394
+ }
395
+ return JSON.stringify({rowGap: Style.spacing.rowGap, headers: sidebar.headingItems().map(function(item) {
396
+ return {text: item.text, visible: item.visible, rect: box(item),
397
+ topPadding: item.topPadding, fontSize: item.font.pixelSize}
398
+ }), rows: sidebar.entries.map(function(entry, index) {
399
+ var row = sidebar.railItemFor(index)
400
+ if (!row) return {index: index, missing: true}
401
+ var detail = row.detailItem
402
+ return {index: index, label: entry.label, kind: entry.kind, group: entry.group,
403
+ size: entry.size, detail: detail.text, detailColor: String(detail.color), labelWidth: Math.round(row.labelItem.width), labelNeeds: Math.round(row.labelItem.implicitWidth), elided: row.labelItem.truncated, elide: row.labelItem.elide,
404
+ fontSize: detail.font.pixelSize, tabular: detail.font.features.tnum === 1,
405
+ rect: box(row), detailRect: box(detail), labelRect: box(row.labelItem),
406
+ indicatorRect: box(row.indicatorSlot), indicatorVisible: row.indicatorVisible}
407
+ })})
408
+ }
409
+
410
+ // The release mark's own hover state, so the hover leg fails when the pointer misses its mark.
411
+ function railMarkHovered(i: int): bool {
412
+ var row = root.pane.sidebar.railItemFor(i)
413
+ if (!row || !row.indicatorSlot) return false
414
+ var kids = row.indicatorSlot.children
415
+ for (var k = 0; k < kids.length; k++) {
416
+ var it = kids[k].item
417
+ if (it && it.hovered === true) return true
418
+ }
419
+ return false
420
+ }
421
+
422
+ // The panel's own title, a spot on the card with no control under it: a click there must leave the panel open.
423
+ function settingsTitleCentre(): string { return root.settingsPanel ? root.fleaWindow.centreOf(root.settingsPanel.titleItem) : "" }
424
+ // A rail row's centre, clicked over the list by tests/ui.sh clickthrough to prove the press stops at the panel.
425
+ function settingsRailRowCentre(id: string): string { return root.settingsPanel ? root.fleaWindow.centreOf(root.settingsPanel.railItemFor(id)) : "" }
426
+ // Observe the compact card's actual content and viewport without changing its scroll position.
427
+ function settingsScroll(): string { return root.settingsPanel ? root.settingsPanel.paneScroll() : "" }
428
+ function settingsScrollState(): string { return root.settingsPanel ? root.settingsPanel.scrollState() : "{}" }
429
+ // Each card's rectangle, so the window-size battery asserts every overlay stays on screen.
430
+ function settingsCardRect(): string { return root.settingsPanel ? root.cardRectOf(root.settingsPanel.cardItem) : "" }
431
+ function networkCardRect(): string { return root.networkDialog ? root.cardRectOf(root.networkDialog.cardItem) : "" }
432
+ function networkScroll(): string { return root.networkDialog ? root.networkDialog.bodyScroll() : "" }
433
+ // The clipping viewport inside the card, so a field's on-screen check is against what the body shows.
434
+ function networkBodyRect(): string { return root.networkDialog ? root.cardRectOf(root.networkDialog.bodyItem) : "" }
435
+ function keymapCardRect(): string { return root.keymapSheet ? root.cardRectOf(root.keymapSheet.cardItem) : "" }
436
+ function convertCardRect(): string { return root.convertDialog ? root.cardRectOf(root.convertDialog.cardItem) : "" }
437
+ // A menu row's own centre, so a driven click lands on the row a test named rather than on a
438
+ // pixel derived from a row count the Menus settings section can change under it.
439
+ function contextMenuRowCentre(i: int): string { return root.fleaWindow.centreOf(root.pane.contextMenu().itemFor(i)) }
440
+ function contextMenuRect(): string { return root.fleaWindow.rectOf(root.pane.contextMenu().frameItem) }
441
+ function contextMenuFocusState(): string {
442
+ var menu = root.pane.contextMenu()
443
+ var window = menu.Window.window
444
+ return JSON.stringify({opened: menu.opened, view: root.pane.focusView,
445
+ menu: menu.keyboardFocused, pane: root.pane.activeFocus, list: root.pane.listArea.activeFocus,
446
+ origin: !!menu.focusHolder && menu.focusHolder.activeFocus,
447
+ activeItem: window ? String(window.activeFocusItem) : ""})
448
+ }
449
+ function contextMenuRowProbe(i: int): string { var item = root.pane.contextMenu().itemFor(i); return item ? item.probe() : "" }
450
+ // Where a driven right click reaches the background menu: the centre of the surface that
451
+ // answers for the directory being shown, which in the columns view is the pane's own column
452
+ // and not the peek beside it. An empty directory has no row to aim from, and it is the case
453
+ // that menu matters most in, so no reader here may derive the point from a row.
454
+ function listingBackgroundCentre(): string {
455
+ var area = root.pane.viewMode === "columns" ? root.pane.columnsArea.activeColumn()
456
+ : root.pane.listArea
457
+ return root.fleaWindow.centreOf(area)
458
+ }
459
+ // The row that is its own rename editor, or -1; drives the States artboard's inline rename.
460
+ function renamingIndex(): int { return root.pane.renamingIndex }
461
+ function renameEditorLive(): bool { return root.pane.renameEditor() !== null }
462
+ function renameEditorText(): string { var e = root.pane.renameEditor(); return e ? e.editorText : "" }
463
+ function renameState(): string {
464
+ var row = root.pane.renameEditor()
465
+ var editor = row ? row.editorField : null
466
+ var current = root.pane.visibleItemFor(root.pane.cursorIndex)
467
+ var area = root.pane.viewMode === "columns" ? root.pane.columnsArea.activeColumn() : root.pane.listArea
468
+ var point = editor ? editor.mapToItem(area, 0, 0) : null
469
+ return JSON.stringify({index: root.pane.renamingIndex, pending: root.pane.renamePending,
470
+ error: root.pane.renameError, text: editor ? editor.current : "",
471
+ cursor: root.pane.cursorIndex, cursorName: root.pane.cursorRow ? root.pane.cursorRow.n : "",
472
+ loading: root.pane.listInFlight, listingState: root.pane.listingState, currentRowHeight: current ? current.height : 0,
473
+ focused: !!editor && editor.inputItem.activeFocus,
474
+ rowHeight: row ? row.height : 0, normalRowHeight: Theme.fileRowHeight,
475
+ fieldHeight: editor ? editor.fieldHeight : 0, errorHeight: editor ? editor.errorHeight : 0,
476
+ editorTop: point ? point.y : 0, editorBottom: point ? point.y + editor.height : 0, viewportHeight: area.height,
477
+ selectedText: editor ? editor.inputItem.selectedText : "",
478
+ centre: editor ? root.fleaWindow.centreOf(editor.inputItem) : ""})
479
+ }
480
+ function railRenameEditorLive(): bool { return root.pane.sidebar.renameEditor() !== null }
481
+ function railRenameEditorText(): string { var e = root.pane.sidebar.renameEditor(); return e ? e.editorText : "" }
482
+ function railRenameFieldShown(): bool { var e = root.pane.sidebar.renameEditor(); return e ? e.editorShown : false }
483
+ function previewOpen(): bool { return root.previewReaders.previewOpen() }
484
+ function columnMarkdownView(): string { return root.previewReaders.columnMarkdownView() }
485
+ function previewKind(): string { return root.previewReaders.previewKind() }
486
+ function previewState(): string { return root.previewReaders.previewState() }
487
+ function compareState(): string { return root.previewReaders.compareState() }
488
+ function previewTextState(column: bool): string { return root.previewReaders.previewTextState(column) }
489
+ function previewFigures(): string { return root.previewReaders.previewFigures() }
490
+ function previewBlockKinds(column: bool): string { return root.previewReaders.previewBlockKinds(column) }
491
+ function previewPosition(): int { return root.previewReaders.previewPosition() }
492
+ function previewDuration(): int { return root.previewReaders.previewDuration() }
493
+ function previewStrip(): string { return root.previewReaders.previewStrip() }
494
+ function previewPdfPage(): int { return root.previewReaders.previewPdfPage() }
495
+ function previewPdfZoom(): string { return root.previewReaders.previewPdfZoom() }
496
+ function previewPdfFocus(): int { return root.previewReaders.previewPdfFocus() }
497
+ function pdfState(overlay: bool): string { return root.previewReaders.pdfState(overlay) }
498
+ function previewExpanded(): string { return root.previewReaders.previewExpanded() }
499
+ function previewSwapState(): string { return root.previewReaders.previewSwapState() }
500
+ function previewSelectionState(): string {
501
+ var column = root.pane.previewColumnItem
502
+ return JSON.stringify({view: root.pane.viewMode, width: root.pane.listSlot.width,
503
+ available: root.pane.width - root.pane.railInset,
504
+ inlineVisible: column !== null && column.visible,
505
+ index: root.pane.previewIndex, path: column ? column.path : "",
506
+ focused: column !== null && column.activeFocus,
507
+ pending: column ? column.pendingToken : 0})
508
+ }
509
+ function rowNameColor(i: int): string {
510
+ var item = root.pane.itemFor(i)
511
+ return item ? String(item.nameColor()) : ""
512
+ }
513
+ function rowCellColor(i: int): string {
514
+ var item = root.pane.itemFor(i)
515
+ return item ? String(item.cellInk) : ""
516
+ }
517
+ // The date cell's drawn ink, which Highlight today's dates can lift above rowCellColor.
518
+ function rowDateColor(i: int): string {
519
+ var item = root.pane.itemFor(i)
520
+ var cell = item ? item.cell("date") : null
521
+ return cell ? String(cell.color) : ""
522
+ }
523
+ // Binds the actual defect: an eliding cell's content stays inside width; a broken one does not.
524
+ function rowCellOverflow(i: int): string {
525
+ var item = root.pane.itemFor(i)
526
+ if (!item)
527
+ return ""
528
+ var keys = ["mode", "size", "date", "kind"]
529
+ var flags = []
530
+ for (var k = 0; k < keys.length; k++) {
531
+ var cell = item.cell(keys[k])
532
+ flags.push(cell && cell.contentWidth > cell.width ? "1" : "0")
533
+ }
534
+ return flags.join("|")
535
+ }
536
+ // The rendered Size cell text; describe() keeps the row's raw s so existing rowAt assertions keep their meaning.
537
+ function rowSizeText(i: int): string {
538
+ var item = root.pane.itemFor(i)
539
+ var cell = item ? item.cell("size") : null
540
+ return cell ? cell.text : ""
541
+ }
542
+ function headerTitles(): string { return root.pane.header.titles() }
543
+ function sortMark(): string { return root.pane.header.sortBy + ":" + (root.pane.header.sortDesc ? "desc" : "asc") }
544
+ // Four siblings share one parent, so plain x/width already agree; itemRect reads a Text's painted bounds, not its anchored box.
545
+ function headerCellRect(name: string): string {
546
+ var item = root.pane.header.cell(name)
547
+ if (!item)
548
+ return ""
549
+ return Math.round(item.x) + "|" + Math.round(item.width)
550
+ }
551
+ // ListColumns040 board: the pointer's own target for an edge drag or double click.
552
+ function headerCellCentre(name: string): string {
553
+ var item = root.pane.header.cell(name)
554
+ return item ? root.fleaWindow.centreOf(item) : ""
555
+ }
556
+ // The last drag the header saw, "startX|lastX|startWidth|preview", kept after release
557
+ // until the next press, so a test asserts the column followed the pointer as the app saw it.
558
+ function headerDragTrace(): string {
559
+ var h = root.pane.header
560
+ return h.dragStartX + "|" + h.dragLastX + "|" + h.dragStartWidth + "|" + h.dragPreview
561
+ }
562
+ // The header's drawn columns beside a row's. Both resolve theirs from their own width
563
+ // through Theme.columns, so a disagreement shows up here rather than as a stray column.
564
+ function columnSet(i: int): string {
565
+ var item = root.pane.itemFor(i)
566
+ return root.pane.header.columnSet() + "|" + (item ? item.columnSet() : "")
567
+ }
568
+ // The three below read the view on screen, where rowIcon and rowAt read the list's own delegates whatever the view.
569
+ function rowHovered(i: int): bool { var item = root.pane.visibleItemFor(i); return item ? item.hovered === true : false }
570
+ function rowThumb(i: int): string { var item = root.pane.visibleItemFor(i); return item && item.thumb !== undefined ? item.thumb : "" }
571
+ function thumbnailPolicyState(): string {
572
+ var pane = root.pane, column = pane.previewColumnItem
573
+ return JSON.stringify({view: pane.viewMode, mode: ViewState.thumbnailMode, files: pane.thumbState.file,
574
+ pending: Object.keys(pane.thumbState.file).filter(function(index) { var v = pane.thumbState.file[index]; return v === null || v === "cache-asked" }).map(Number),
575
+ contentY: pane.viewMode === "columns" && root.columns ? root.columns.activeContentY() : pane.listArea.contentY,
576
+ cursor: pane.cursorIndex, previewIndex: pane.previewIndex, previewPath: column ? column.path : "",
577
+ previewReady: column !== null && column.frameStatus === Image.Ready})
578
+ }
579
+ function viewContentY(): int { return Math.round(root.pane.viewMode === "columns" && root.columns ? root.columns.activeContentY() : root.pane.listArea.contentY) }
580
+ // The true end of travel in contentY terms: the column view or the list area, origin plus content minus viewport, whole pixels.
581
+ function viewEndY(): int { var v = root.pane.viewMode === "columns" && root.columns ? root.columns.activeColumn().viewport : root.pane.listArea; return Math.round(v.originY + v.contentHeight - v.height) }
582
+ function listAreaRect(): string { return root.fleaWindow.rectOf(root.pane.listArea) }
583
+ function listingDropActive(): bool { return root.previewReaders.listingDropActive() }
584
+ function rowRect(i: int): string { return root.fleaWindow.rectOf(root.pane.visibleItemFor(i)) }
585
+ function dragPaneGeometry(side: int, index: int): string {
586
+ var pane = side >= 0 && side < root.panes.length ? root.panes[side] : null
587
+ if (!pane || index < 0 || index >= pane.total) return "{}"
588
+ var folder = pane.rowFor(index), last = pane.rowFor(pane.total - 1)
589
+ return JSON.stringify({side: side, active: ViewState.state.view === "dual", focused: root.pane === pane,
590
+ path: pane.path, view: pane.viewMode, loading: pane.listInFlight, total: pane.total,
591
+ area: root.fleaWindow.rectOf(pane.listArea),
592
+ folder: {index: index, name: folder ? folder.n : "", directory: !!folder && folder.d === true,
593
+ rect: root.fleaWindow.rectOf(pane.visibleItemFor(index))},
594
+ last: {index: pane.total - 1, name: last ? last.n : "",
595
+ rect: root.fleaWindow.rectOf(pane.visibleItemFor(pane.total - 1))}})
596
+ }
597
+ // Ready is the decoded image on screen; a path alone is not a thumbnail, see GridTile.thumbDrawn.
598
+ function rowThumbReady(i: int): bool { var item = root.pane.visibleItemFor(i); return item && item.iconStatus !== undefined ? item.iconStatus === Image.Ready : false }
599
+ function columnPlayerLoaded(): bool { return root.columns ? root.columns.playerLoaded() : false }
600
+ function columnThumbShown(): bool { return root.columns ? root.columns.thumbShown() : false }
601
+ function columnFrameReady(): bool { return root.columns ? root.columns.frameReady() : false }
602
+ function columnTextLines(): string { return root.columns ? root.columns.textLines() : "" }
603
+ function columnMarkdownText(): string { return root.columns ? root.columns.markdownText() : "" }
604
+ function htmlState(column: bool): string { return root.previewReaders.htmlState(column) }
605
+ function columnLinesRect(): string { return root.columns ? root.fleaWindow.rectOf(root.columns.linesItem()) : "" }
606
+ function columnArchiveRect(): string { return root.columns ? root.fleaWindow.rectOf(root.columns.archiveItem()) : "" }
607
+ function previewSurfaceRect(): string { return root.previewReaders.previewSurfaceRect() }
608
+ function previewTable(): string { return root.previewReaders.previewTable() }
609
+ function previewPictureRect(): string { return root.previewReaders.previewPictureRect() }
610
+ function previewMediaLoaded(): bool { return root.previewReaders.previewMediaLoaded() }
611
+ function previewText(): string { return root.previewReaders.previewText() }
612
+ function previewMarkdownView(): string { return root.previewReaders.previewMarkdownView() }
613
+ function previewCloseState(): string { return root.previewReaders.previewCloseState() }
614
+ function previewEndGap(): int { return root.previewReaders.previewEndGap() }
615
+ function previewScrollY(): int { return root.previewReaders.previewScrollY() }
616
+ function previewTextLength(): int { return root.previewReaders.previewTextLength() }
617
+ function previewTextTail(n: int): string { return root.previewReaders.previewTextTail(n) }
618
+ function previewArchiveNames(): string { return root.previewReaders.previewArchiveNames() }
619
+ function columnArchiveNames(): string { return root.columns ? root.columns.archiveNames() : "" }
620
+ function columnFailure(): string { return root.columns ? root.columns.failureText() : "" }
621
+ function rowThumbRect(i: int): string { var item = root.pane.visibleItemFor(i); return item && item.thumbItem ? root.fleaWindow.rectOf(item.thumbItem) : "" }
622
+ function columnFrameRect(): string { return root.columns ? root.fleaWindow.rectOf(root.columns.frameItem()) : "" }
623
+ function columnPictureRect(): string { return root.columns ? root.fleaWindow.rectOf(root.columns.pictureItem()) : "" }
624
+ function columnChildEmpty(): string { var e = root.columns ? root.columns.childEmptyItem() : null; return e ? e.visible + " " + e.opacity.toFixed(2) + " " + e.markItem.opacity.toFixed(2) : "" }
625
+ function columnChildMarkRect(): string { var e = root.columns ? root.columns.childEmptyItem() : null; return e ? root.fleaWindow.rectOf(e.markItem) : "" }
626
+ function columnChildRowCentre(i: int): string { return root.columns ? root.fleaWindow.centreOf(root.columns.childItemAt(i)) : "" }
627
+ function columnChildRowName(i: int): string { var item = root.columns ? root.columns.childItemAt(i) : null; return item && item.row ? item.row.n : "" }
628
+ function columnActiveRect(): string { return root.columns ? root.fleaWindow.rectOf(root.columns.activeColumn()) : "" }
629
+ function columnParentRowCentre(i: int): string { return root.columns ? root.fleaWindow.centreOf(root.columns.parentItemAt(i)) : "" }
630
+ // ColumnsWidth board: the live count and the deeper ancestors a wide window shows.
631
+ function columnCount(): int { return root.columns ? root.columns.columnCount : 0 }
632
+ function columnGrandparentRowCentre(i: int): string { return root.columns ? root.fleaWindow.centreOf(root.columns.grandparentItemAt(i)) : "" }
633
+ function columnGreatGrandparentRowCentre(i: int): string { return root.columns ? root.fleaWindow.centreOf(root.columns.greatGrandparentItemAt(i)) : "" }
634
+ // ListColumns040 board: the stored edge widths beside the drawn header and row sets.
635
+ function columnWidths(): string { return JSON.stringify(ViewState.state.columnWidths || {}) }
636
+ function rowIcon(i: int): string {
637
+ var item = root.pane.itemFor(i)
638
+ return item ? String(item.iconUrl) : ""
639
+ }
640
+ function rowIconStatus(i: int): int {
641
+ var item = root.pane.itemFor(i)
642
+ return item ? item.iconStatus : -1
643
+ }
644
+ // The name cell's drawn text: the row name plus the surface's own decorations, where
645
+ // describe() keeps the raw n so every existing rowAt assertion keeps its meaning.
646
+ function rowNameText(i: int): string {
647
+ var item = root.pane.itemFor(i)
648
+ return item ? item.decoratedName : ""
649
+ }
650
+ function rowGlyph(i: int): string {
651
+ var item = root.pane.itemFor(i)
652
+ return item ? String(item.glyphName) : ""
653
+ }
654
+ // The empty-directory mark's own visibility, off the same listingState the overlay binds to.
655
+ function emptyShown(): bool { return root.pane.listingState === "empty" }
656
+ function stateLayers(): string {
657
+ return JSON.stringify({empty: root.pane.emptyState.visible, message: root.pane.stateMessageItem.visible})
658
+ }
659
+ // "x y width height" of the empty mark in window pixels, for a painted-pixel count: the state
660
+ // flag above cannot see a mark drawn under its own parent's paint.
661
+ function emptyMarkRect(): string { return root.emptyState ? root.fleaWindow.rectOf(root.emptyState.markItem) : "" }
662
+ function emptyHeroState(): string {
663
+ var empty = root.emptyState
664
+ if (!empty) return "{}"
665
+ return JSON.stringify({visible: empty.visible, settled: empty.markItem.settled && empty.opacity === 1,
666
+ opacity: empty.opacity, offset: empty.captionItem.parent.anchors.verticalCenterOffset,
667
+ caption: empty.captionItem.text, captionOpacity: empty.captionItem.opacity,
668
+ captionColor: String(empty.captionItem.color), markColor: String(empty.markItem.color),
669
+ foreground: String(Theme.color.foreground), muted: String(Theme.color.muted),
670
+ reducedMotion: Theme.reducedMotion, rotateMs: empty.rotateMs})
671
+ }
672
+ // The whole hero box, so a test can hold it to the listing slot exactly rather than merely inside it.
673
+ function emptyStateRect(): string { return root.emptyState ? root.fleaWindow.rectOf(root.emptyState) : "" }
674
+ function rowAt(i: int): string {
675
+ var item = root.pane.itemFor(i)
676
+ return item ? item.describe() : "loading"
677
+ }
678
+ function visibleRows(): int { return root.pane.visibleRows }
679
+ // The list's scroll position and the platform's lines per notch, for tests/ui.sh scroll.
680
+ function listContentY(): int { return Math.round(root.pane.listArea.contentY) }
681
+ function scrollbarState(): string { var bar = root.pane.listArea.scrollBar; return bar ? JSON.stringify({visible: bar.visible, shown: bar.shown, knob: bar.knobWidth, rect: root.fleaWindow.rectOf(bar), knobRect: root.fleaWindow.rectOf(bar.knobItem), handle: bar.handleLength, offset: bar.handleOffset, content: bar.contentLength, viewport: bar.viewportLength}) : "{}" }
682
+ function wheelLines(): int { return Application.styleHints.wheelScrollLines }
683
+ function thumbRequests(): int { return root.backend.thumbRequests }
684
+ function dirSizeRequests(): int { return root.backend.dirSizeRequests }
685
+ function listRequests(): int { return root.backend.listRequests }
686
+ // The listing swap's record, ui/PaneSwap.qml describe(): what is held and every frame drawn with no row.
687
+ function swapState(): string { return JSON.stringify(root.pane.swap.describe()) }
688
+ function thumbFile(i: int): string { return root.pane.thumbFor(i) }
689
+ function rowCentre(i: int): string { return root.pane.rowFor(i) ? root.fleaWindow.centreOf(root.pane.visibleItemFor(i)) : "" }
690
+ function rowNameCentre(i: int): string {
691
+ var label = root.rowNameItem(i)
692
+ if (!label) return ""
693
+ var width = Math.min(label.width, label.contentWidth === undefined ? label.implicitWidth : label.contentWidth)
694
+ var height = Math.min(label.height, label.contentHeight === undefined ? label.implicitHeight : label.contentHeight)
695
+ var x = root.pane.viewMode === "grid" ? label.width / 2 : width / 2
696
+ var point = label.mapToItem(null, x, height / 2)
697
+ return Math.round(point.x) + " " + Math.round(point.y)
698
+ }
699
+ function rowNameRect(i: int): string { return root.fleaWindow.rectOf(root.rowNameItem(i)) }
700
+ function previewSliderCentre(): string { return root.previewReaders.previewSliderCentre() }
701
+ // The same lookup as rowCentre, but for a rail row: the rail has no ListView, so Sidebar.railItemFor(i) walks its own Repeaters instead.
702
+ function railRowCentre(i: int): string { return root.fleaWindow.centreOf(root.pane.sidebar.railItemFor(i)) }
703
+ function railLabel(i: int): string { var item = root.pane.sidebar.railItemFor(i); return item ? item.modelData.label : "" }
704
+ function railLabels(): string { var out = []; for (var i = 0; i < root.pane.railCount; i++) { var item = root.pane.sidebar.railItemFor(i); out.push(item ? item.modelData.label : "") } return out.join("|") }
705
+ // The sidebar pushes the row and the header right by its own width, so a pixel-crop test needs this rather than assuming x=0.
706
+ function rowLeft(i: int): string {
707
+ var item = root.pane.itemFor(i)
708
+ if (!item || !root.pane.rowFor(i))
709
+ return ""
710
+ return String(Math.round(root.fleaWindow.itemRect(item).x))
711
+ }
712
+ function headerLeft(): string { return String(Math.round(root.fleaWindow.itemRect(root.pane.header).x)) }
713
+ function viewMode(): string { return root.pane.viewMode }
714
+ // What this box probed: the compress submenu is exactly this and never a fixed list.
715
+ function archiveFormats(): string { return root.backend.archiveFormats.join("|") }
716
+ // The two password cards, so a test waits on the open card and its error rather than a sleep.
717
+ function archiveCardsState(): string {
718
+ var cards = root.archiveCards
719
+ if (!cards) return JSON.stringify({opened: false})
720
+ var compress = cards.compressItem, extract = cards.extractItem
721
+ return JSON.stringify({opened: true,
722
+ compressOpened: !!compress && compress.opened === true,
723
+ extractOpened: !!extract && extract.opened === true,
724
+ compressCount: compress ? compress.count : 0, compressFormat: compress ? compress.format : "",
725
+ compressError: compress ? compress.errorText : "", compressFocus: compress ? compress.focusPart : -1,
726
+ canCompress: !!compress && compress.canCompress === true, compressCapture: compress ? compress.captureState() : null,
727
+ extractLeaf: extract ? extract.leaf : "", extractError: extract ? extract.errorText : "",
728
+ extractFocus: extract ? extract.focusPart : -1, extractCapture: extract ? extract.captureState() : null,
729
+ canExtract: !!extract && extract.canExtract === true})
730
+ }
731
+ function canConvert(): bool { return root.backend.canConvert }
732
+ // The one popup in the design, so a test can assert it opened and what it would write.
733
+ function convertOpen(): bool { return root.convertDialog ? root.convertDialog.opened : false }
734
+ function keymapSheetOpen(): bool { return root.keymapSheet ? root.keymapSheet.opened : false }
735
+ // One row per line, "<cap> <wording>", so a test asserts the sheet without OCR.
736
+ function keymapSheetRows(): string { return root.keymapSheet ? root.keymapSheet.rows() : "" }
737
+ // The result rows as JSON with keys, label, where (the drawn muted text) and disabled, so a test asserts the where on screen.
738
+ function keymapSheetResults(): string { return root.keymapSheet ? root.keymapSheet.resultState() : "[]" }
739
+ // Sidebar040: the query the typed keys narrowed the sheet to, "" at rest.
740
+ function keymapQuery(): string { return root.keymapSheet ? root.keymapSheet.query : "" }
741
+ // Sidebar040: the main window's Recent place, "" off and "results" once the history answered.
742
+ function recentMode(): string { return root.pane.recentMode }
743
+ function recentFrom(): string { return root.pane.recentFrom }
744
+ function convertFormat(): string { return root.convertDialog ? root.convertDialog.format : "" }
745
+ function convertStrip(): bool { return root.convertDialog ? root.convertDialog.strip : false }
746
+ function convertState(): string {
747
+ var dialog = root.convertDialog
748
+ if (!dialog) return JSON.stringify({opened: false})
749
+ return JSON.stringify({opened: dialog.opened, source: dialog.source, format: dialog.format, strip: dialog.strip,
750
+ checking: dialog.checking, busy: dialog.busy, unavailable: dialog.unavailable, collision: dialog.collision, error: dialog.errorText,
751
+ output: dialog.outputPath, outputText: dialog.outputItem.text, outputLines: dialog.outputItem.lineCount,
752
+ outputRect: root.fleaWindow.rectOf(dialog.outputItem),
753
+ bodyRect: root.cardRectOf(dialog.bodyItem), scrollY: dialog.bodyItem.contentY,
754
+ requestId: dialog.requestId, operationId: dialog.operationId, cursor: dialog.cursor, focusPart: dialog.focusPart, canConvert: dialog.canConvert,
755
+ rect: root.cardRectOf(dialog.cardItem),
756
+ formats: dialog.formats.map(function(format, index) {
757
+ return Object.assign(root.controlState(format, dialog.formatItem(index)), {selected: dialog.format === format,
758
+ current: dialog.formatItem(index).current, labelColor: String(dialog.formatItem(index).labelColor),
759
+ markColor: String(dialog.formatItem(index).markColor), pointerProbe: dialog.formatItem(index).probe()})
760
+ }), controls: [root.controlState("Remove metadata", dialog.metadataItem),
761
+ root.controlState("Cancel", dialog.cancelItem), root.controlState("Convert", dialog.submitItem)]})
762
+ }
763
+ function convertTitleCentre(): string { return root.convertDialog ? root.fleaWindow.centreOf(root.convertDialog.titleItem) : "" }
764
+ // Bulk rename card state for the controller's 1:1 capture beside its board.
765
+ function bulkRenameState(): string {
766
+ var dialog = root.bulkRenameDialog
767
+ if (!dialog) return JSON.stringify({opened: false})
768
+ return JSON.stringify({opened: dialog.opened, footer: dialog.footerText, rename: dialog.renameText,
769
+ canRename: dialog.canRename, count: dialog.oldNames.length, focus: dialog.focusedControl,
770
+ rect: root.cardRectOf(dialog.cardItem)})
771
+ }
772
+ // The preview column's own table and state, so a test asserts the canvas's rows without OCR.
773
+ function previewFacts(): string { return root.columns ? root.columns.factsLine() : "" }
774
+ function previewColumnState(): string { return root.columns ? root.columns.previewStateName() : "" }
775
+ // ExtThumbs: the directory class beside the fsinfo line, and whether the column holds manual.
776
+ function storageClass(): string { return root.pane.storageClass }
777
+ function columnManualHold(): bool { var c = root.pane.previewColumnItem; return c ? c.manualHold === true : false }
778
+ // The preview column's transport, so a test can prove it plays rather than eyeball a glyph.
779
+ function columnMediaPlaying(): bool { return root.columns ? root.columns.mediaPlaying() : false }
780
+ function columnMediaPosition(): int { return root.columns ? root.columns.mediaPosition() : -1 }
781
+ function columnPlayCentre(): string {
782
+ var strip = root.columns ? root.columns.mediaStrip() : null
783
+ return strip ? root.fleaWindow.centreOf(strip.playItem) : ""
784
+ }
785
+ function columnStripCentre(): string { return root.columns ? root.fleaWindow.centreOf(root.columns.mediaStrip()) : "" }
786
+ // The preview column's PDF page position, so a test proves a page turned rather than
787
+ // eyeballing a render. Both readers are pure, like every other one on this handler.
788
+ function columnPdfPage(): int { return root.columns ? root.columns.pdfPage() : -1 }
789
+ function columnPdfPages(): int { return root.columns ? root.columns.pdfPages() : -1 }
790
+ function columnPdfLoaded(): bool { return root.columns ? root.columns.pdfLoaded() : false }
791
+ function chromeHeight(): int { return Math.round(Theme.chromeHeight) }
792
+ function tabCount(): int { return Tabs.count(root.pane) }
793
+ function tabIndex(): int { return Tabs.currentIndex(root.pane) }
794
+ function tabLabels(): string { return Tabs.labels(root.pane).join("|") }
795
+ function tabBarVisible(): bool { return root.tabBar ? root.tabBar.visible : false }
796
+ function tabCentre(i: int): string {
797
+ if (!root.tabBar)
798
+ return ""
799
+ return root.fleaWindow.centreOf(root.tabBar.itemAt(i))
800
+ }
801
+ // The chrome's buttons carry a glyph and no text, so a test reaches one by name and clicks
802
+ // its centre, exactly the way rowCentre already works for a row.
803
+ function chromeButtonCentre(glyph: string): string { return root.fleaWindow.centreOf(root.chrome.buttonFor(glyph)) }
804
+ function chromeButtonState(glyph: string): string {
805
+ return JSON.stringify(root.controlState(glyph, root.chrome.buttonFor(glyph)))
806
+ }
807
+ // The path bar: whether it has the keyboard, what it is holding, and the box a double click
808
+ // opens it on, which is the pointer's half of ":" and Ctrl+L.
809
+ function pathBarOpen(): bool { return root.chrome.editing }
810
+ function pathBarText(): string { return String(root.chrome.editText) }
811
+ // How many folder jump rows the dropdown draws, a clipped one included; 0 while it shows none.
812
+ function pathSuggestionRows(): int { return root.chrome.jump !== null ? root.chrome.jump.drawnRows : 0 }
813
+ // The highlighted folder row's index, 0 for the first row and -1 while no popup stands.
814
+ function pathSuggestionCursor(): int { return root.chrome.jump !== null ? root.chrome.jump.cursor : -1 }
815
+ // The last pointer point the open suggestion popup has seen, in popup coordinates; "none" while it has seen none.
816
+ function pathSuggestionPointer(): string {
817
+ if (root.chrome.jump === null || root.chrome.jump.dropItem === null) return "none"
818
+ var at = root.chrome.jump.pointerGlobal
819
+ if (at.x < 0) return "none"
820
+ var frame = root.fleaWindow.rectOf(root.chrome.jump.dropItem).split(" ")
821
+ return Math.round(at.x - Number(frame[0])) + " " + Math.round(at.y - Number(frame[1]))
822
+ }
823
+ function pathCentre(): string { return root.fleaWindow.centreOf(root.chrome.pathArea) }
824
+ // The open path field's frame, "x y width height", the accent hairline a native capture is read against.
825
+ function pathFrameRect(): string { return root.fleaWindow.rectOf(root.chrome.pathFrame) }
826
+ // The elision marker, or "" while the whole path fits: the one spot the crumbs slide under.
827
+ function elisionCentre(): string {
828
+ return root.chrome.elisionMarker ? root.fleaWindow.centreOf(root.chrome.elisionMarker) : ""
829
+ }
830
+ // Issue 45's segments, reached the way tabCentre reaches a tab: a driven press on a real
831
+ // crumb is the only thing that can tell a bound TapHandler from an unbound one.
832
+ function crumbCount(): int { return root.chrome.crumbItems.count }
833
+ function crumbText(): string { return root.chrome.crumbItems.model.map(function (c) { return c.text }).join("") }
834
+ // Measured: with the bar open the slot is hidden and a crumb's box is still there, and a path
835
+ // too long for the bar slides its head clean off the left, so a bare centre aims a driven
836
+ // click at the desktop. Answering "" for both is what stops a test pressing nothing at all.
837
+ function crumbCentre(i: int): string {
838
+ var item = root.chrome.crumbItems.itemAt(i)
839
+ if (!item || !item.visible)
840
+ return ""
841
+ var box = item.mapToItem(root.chrome.pathArea, 0, 0)
842
+ var inside = box.x >= 0 && box.x + item.width <= root.chrome.pathArea.width
843
+ return inside ? root.fleaWindow.centreOf(item) : ""
844
+ }
845
+ // A dual pane's own path, side 0 or 1, answered the way crumbCount and crumbCentre answer the chrome's.
846
+ function paneCrumbCount(side: int): int { return root.panes[side] ? root.panes[side].pathCrumbs.count : 0 }
847
+ function paneCrumbText(side: int): string { var p = root.panes[side]; return p ? p.pathCrumbs.model.map(function (c) { return c.text }).join("") : "" }
848
+ function paneCrumbCentre(side: int, i: int): string {
849
+ var pane = root.panes[side]
850
+ var item = pane ? pane.pathCrumbs.itemAt(i) : null
851
+ if (!item || !item.visible || !pane.pathSlot.visible)
852
+ return ""
853
+ var box = item.mapToItem(pane.pathSlot, 0, 0)
854
+ var inside = box.x >= 0 && box.x + item.width <= pane.pathSlot.width
855
+ return inside ? root.fleaWindow.centreOf(item) : ""
856
+ }
857
+ // The whole of that pane's path strip, "x y width height", so a test can press where no crumb is.
858
+ function panePathRect(side: int): string {
859
+ var pane = root.panes[side]
860
+ return pane && pane.pathStrip.visible ? root.fleaWindow.rectOf(pane.pathStrip) : ""
861
+ }
862
+ // The button's painted box as "WxH": the mark is Theme.chromeMarkSize wide and the hit area is the whole strip tall.
863
+ function chromeButtonSize(glyph: string): string {
864
+ var item = root.chrome.buttonFor(glyph)
865
+ if (!item)
866
+ return ""
867
+ var rect = root.fleaWindow.itemRect(item)
868
+ return Math.round(rect.width) + "x" + Math.round(rect.height)
869
+ }
870
+ function headerTop(): string { return String(Math.round(root.fleaWindow.itemRect(root.pane.header).y)) }
871
+ function railRenamingIndex(): int { return root.pane.sidebar.renamingIndex }
872
+ function dialogOpen(): bool { return root.networkDialog ? root.networkDialog.opened : false }
873
+ // The network form's own state, so a test asserts the protocol swap and the URI it built.
874
+ function networkProtocol(): string { return root.networkDialog ? root.networkDialog.formProtocol() : "" }
875
+ function networkPort(): string { return root.networkDialog ? root.networkDialog.formPort() : "" }
876
+ function networkUri(): string { return root.networkDialog ? root.networkDialog.formUri() : "" }
877
+ function networkPathLabel(): string { return root.networkDialog ? root.networkDialog.formPathLabel() : "" }
878
+ function networkTitle(): string { return root.networkDialog ? root.networkDialog.dialogTitle : "" }
879
+ function networkFields(): string { return root.networkDialog ? root.networkDialog.formFields() : "" }
880
+ function networkFocus(): string { return root.networkDialog ? root.networkDialog.formFocus() : "" }
881
+ function networkFocusState(): string {
882
+ var dialog = root.networkDialog
883
+ var window = dialog ? dialog.Window.window : null
884
+ var item = window ? window.activeFocusItem : null
885
+ var inside = false
886
+ for (var parent = item; parent; parent = parent.parent) {
887
+ if (parent === dialog) { inside = true; break }
888
+ }
889
+ return JSON.stringify({inside: inside, busy: !!dialog && dialog.busy,
890
+ activeItem: item ? String(item) : "", field: dialog ? dialog.formFocus() : ""})
891
+ }
892
+ // The cohesion captures' reader over the window, see ui/IpcControls.qml and ui/js/ControlReader.js.
893
+ function cohesionControls(): string {
894
+ var window = root.pane.Window.window
895
+ var kinds = /^(DialogButton|ChromeButton|CheckBox|DialogField|ProtocolChip|Crumb|MenuRow|SidebarRow|SettingsSegment|SettingsRow|TabBar|PathEntry|RenameField)_QML/
896
+ return Qt.createComponent("IpcControls.qml").createObject(null).read(window, window ? window.contentItem : null, kinds, {})
897
+ }
898
+ function networkHostPortWidths(): string { return root.networkDialog ? root.networkDialog.formHostPortWidths() : "" }
899
+ // Mask state and presence only: the seam never returns password content.
900
+ function networkPasswordState(): string { return root.networkDialog ? root.networkDialog.formPasswordState() : "" }
901
+ function networkPasswordEyeCentre(): string { return root.networkDialog ? root.networkDialog.formPasswordEyeCentre() : "" }
902
+ function networkNote(): string { return root.networkDialog ? root.networkDialog.formNote() : "" }
903
+ function networkPasswordHint(): string { return root.networkDialog ? root.networkDialog.formPasswordHint() : "" }
904
+ function networkAction(): string { return root.networkDialog ? root.networkDialog.formAction() : "" }
905
+ function networkStatus(): string { return root.networkDialog ? root.networkDialog.statusText : "" }
906
+ function networkDialogMetrics(): string { return root.networkDialog ? root.networkDialog.formMetrics() : "" }
907
+ function networkDialogMetricTargets(): string { return root.networkDialog ? root.networkDialog.formMetricTargets() : "" }
908
+ // Durable and non-secret, unlike the four-second status-bar transient. The host
909
+ // outlives the rail, so this reads the host first and the rail's method only while it stands.
910
+ function networkResult(): string {
911
+ if (root.pane.networkService) return root.pane.networkService.result
912
+ var sidebar = root.pane.sidebar
913
+ return sidebar ? sidebar.networkResult() : ""
914
+ }
915
+ // The "+" ink, its hit target and the rail's own indicator dot, each "x width centre" in window
916
+ // coordinates. Three measured rectangles, because a computed slot only restates the anchoring.
917
+ function networkMarkGeometry(): string {
918
+ var items = root.pane.sidebar.networkMarkItems()
919
+ if (!items[0] || !items[1] || !items[2])
920
+ return ""
921
+ return [root.fleaWindow.boxOf(items[0]), root.fleaWindow.boxOf(items[1]),
922
+ root.fleaWindow.boxOf(items[2])].join("|")
923
+ }
924
+ // Where a click probe aims: the hit target's own middle, so the probe varies only x.
925
+ function networkMarkCentre(): string { return root.fleaWindow.centreOf(root.pane.sidebar.networkMarkItems()[1]) }
926
+ // A protocol chip carries a label and no tree, so a test clicks its centre the way it does a row.
927
+ function networkChipCentre(name: string): string { return root.networkDialog ? root.fleaWindow.centreOf(root.networkDialog.formChip(name)) : "" }
928
+ function shareBrowserOpen(): bool { return root.shareBrowser ? root.shareBrowser.active : false }
929
+ // One share name per line, in cursor order; empty when the overlay is shut.
930
+ function shareBrowserEntries(): string { return root.shareBrowser ? root.shareBrowser.shares.join("\n") : "" }
931
+ function shareBrowserCursor(): int { return root.shareBrowser ? root.shareBrowser.cursorIndex : -1 }
932
+ function shareBrowserRect(): string { return root.shareBrowser ? root.fleaWindow.rectOf(root.shareBrowser) : "" }
933
+ function shareBrowserState(): string {
934
+ var browser = root.panes[0] ? root.panes[0].shareBrowser : null
935
+ return JSON.stringify({active: !!browser && browser.active,
936
+ owner: browser && browser.owner ? root.panes.indexOf(browser.owner) : -1,
937
+ rect: browser ? root.fleaWindow.rectOf(browser) : "",
938
+ baseUri: root.shareBrowser ? root.shareBrowser.baseUri : "",
939
+ cursor: root.shareBrowser ? root.shareBrowser.cursorIndex : -1,
940
+ paneRects: root.panes.map(function(pane) { return pane ? root.fleaWindow.rectOf(pane.listSlot) : "" })})
941
+ }
942
+ // One line per entry, "label|group|kind|mounted", so a test can assert count and shape without a screenshot.
943
+ function networkEntries(): string {
944
+ var out = []
945
+ var entries = root.pane.sidebar.networkEntries
946
+ for (var i = 0; i < entries.length; i++) {
947
+ var e = entries[i]
948
+ out.push(e.label + "|" + e.group + "|" + e.kind + "|" + e.mounted)
949
+ }
950
+ return out.join("\n")
951
+ }
952
+ function networkStartIndex(): int { return root.pane.sidebar.placesEntries.length }
953
+ // True once the Network group was rebuilt after a gio listing and a mountinfo read, so an empty networkEntries is an answer and not the unbuilt state.
954
+ function networkBuilt(): bool { var s = root.pane.sidebar; return s.railGate.showNetwork === true && s.service !== null && s.service._listedOnce === true && String(s.service._lastMountinfo).length > 0 }
955
+ // Tests feed mountinfo text through the rail service's own rebuild seam.
956
+ function networkFeedMountinfo(text: string): string { if (root.pane.sidebar.service) { root.pane.sidebar.service.rebuild(text); return "fed" } return "no service" }
957
+
958
+ // The eject chain's guard state beside the rail, so a failed eject names its guard.
959
+ function deviceEjectState(): string { return root.pane.sidebar.ejectChainState() }
960
+ // One line per entry, "label|group|kind|mounted", the same shape networkEntries answers.
961
+ function deviceEntries(): string {
962
+ var out = []
963
+ var entries = root.pane.sidebar.deviceEntries
964
+ for (var i = 0; i < entries.length; i++) {
965
+ var e = entries[i]
966
+ out.push(e.label + "|" + e.group + "|" + e.kind + "|" + e.mounted)
967
+ }
968
+ return out.join("\n")
969
+ }
970
+ // The Unlock card flea --unlock opens, so a capture waits on its phase instead of sleeping.
971
+ function unlockState(): string {
972
+ var unlockDialog = root.unlockHost ? root.unlockHost.dialog : null
973
+ if (!unlockDialog || !unlockDialog.opened) return JSON.stringify({opened: false})
974
+ return JSON.stringify({opened: true, phase: String(unlockDialog.phase), note: String(unlockDialog.noteText),
975
+ noteIsError: unlockDialog.noteIsError === true, device: String(unlockDialog.device), label: String(unlockDialog.driveLabel),
976
+ subtitle: String(unlockDialog.subtitleText), focus: unlockDialog.focusPart(), masked: unlockDialog.masked === true,
977
+ rect: root.cardRectOf(unlockDialog.cardItem),
978
+ controls: ["cancel", "unlock"].map(function (b) { return root.controlState(b, unlockDialog.buttonItem(b)) }),
979
+ revealed: unlockDialog.fieldItem.revealed, eye: unlockDialog.fieldItem.eyeCentre()})
980
+ }
981
+ function trashFocusEmpty(): bool { var view = root.pane.trash.item; if (!view) return false; view.emptyItem.forceActiveFocus(Qt.TabFocusReason); return view.emptyItem.activeFocus }
982
+ // open() leaves the keyboard on the view itself; forcing a focus scope alone returns it to its last child, so the button lets go first.
983
+ function trashFocusListing(): bool { var view = root.pane.trash.item; if (!view) return false; view.emptyItem.focus = false; view.forceActiveFocus(); return view.activeFocus && !view.emptyItem.activeFocus }
984
+ // "hovered|focused|pressed|available" read from the strip button's own handlers and properties, one word each.
985
+ function trashEmptyState(): string { var view = root.pane.trash.item; if (!view) return ""; var item = view.emptyItem; var hover = item.data.find(function(o) { return o.hovered !== undefined }); var tap = item.data.find(function(o) { return o.pressed !== undefined }); return [hover.hovered, item.focused, tap.pressed, item.available].join("|") }
986
+ function columnNames(slot: string): string { return root.columns ? root.columns.drawnNames(slot) : "" }
987
+ function columnPeekNames(path: string): string { return root.columns ? root.columns.peekNames(path) : "" }
988
+ function flashState(): string {
989
+ var host = root.flashHost()
990
+ return host ? host.describe() : "{\"opened\":false}"
991
+ }
992
+ function flashControlCentre(name: string): string {
993
+ var host = root.flashHost()
994
+ var item = host ? host.controlItem(name) : null
995
+ return item ? root.fleaWindow.centreOf(item) : ""
996
+ }
997
+ }
998
+ // Flash image's bodies live on the window's host, so each entry point here stays one call.
999
+ function flashHost() { return root.pane.overlayParent ? root.pane.overlayParent.flashHost : null }
1000
+ function rowNameItem(i) {
1001
+ var item = root.pane.rowFor(i) ? root.pane.visibleItemFor(i) : null
1002
+ var label = item ? (item.captionItem || item.nameItem()) : null
1003
+ return label && item.visible && label.visible ? label : null
1004
+ }
1005
+ }