1
0

ExecutionPage.qml 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. import QtQuick 2.15
  2. import QtQuick.Controls 2.15
  3. import QtQuick.Layouts 1.15
  4. import Qt.labs.folderlistmodel 2.15
  5. import "../components"
  6. Page {
  7. id: page
  8. property var backend: null
  9. property var stackView: null
  10. property string patternName: ""
  11. property string patternPreview: ""
  12. // Get current pattern info from backend
  13. property string currentPattern: backend ? backend.currentFile : ""
  14. property string currentPreviewPath: ""
  15. property var allPossiblePaths: []
  16. property int currentPathIndex: 0
  17. property string activeImageSource: "" // Separate property to avoid binding loop
  18. property string repoRoot: "" // Will hold the absolute path to repository root
  19. property bool imageRetryInProgress: false // Prevent multiple retry attempts
  20. // Debug backend connection
  21. onBackendChanged: {
  22. console.log("ExecutionPage: backend changed to", backend)
  23. if (backend) {
  24. console.log("ExecutionPage: backend.serialConnected =", backend.serialConnected)
  25. console.log("ExecutionPage: backend.isConnected =", backend.isConnected)
  26. }
  27. }
  28. Component.onCompleted: {
  29. console.log("ExecutionPage: Component completed, backend =", backend)
  30. if (backend) {
  31. console.log("ExecutionPage: initial serialConnected =", backend.serialConnected)
  32. }
  33. // Find repository root directory
  34. findRepoRoot()
  35. }
  36. // Direct connection to backend signals
  37. Connections {
  38. target: backend
  39. function onSerialConnectionChanged(connected) {
  40. console.log("ExecutionPage: received serialConnectionChanged signal:", connected)
  41. }
  42. function onConnectionChanged() {
  43. console.log("ExecutionPage: received connectionChanged signal")
  44. if (backend) {
  45. console.log("ExecutionPage: after connectionChanged, serialConnected =", backend.serialConnected)
  46. }
  47. }
  48. }
  49. onCurrentPatternChanged: {
  50. if (currentPattern) {
  51. // Generate preview path from current pattern
  52. updatePreviewPath()
  53. }
  54. }
  55. function updatePreviewPath() {
  56. if (!currentPattern) {
  57. console.log("🔍 No current pattern, clearing preview path")
  58. currentPreviewPath = ""
  59. return
  60. }
  61. console.log("🔍 Updating preview for pattern:", currentPattern)
  62. // Extract just the filename from the path
  63. var fileName = currentPattern.split('/').pop() // Get last part of path
  64. var baseName = fileName.replace(".thr", "")
  65. console.log("🔍 File name:", fileName, "Base name:", baseName)
  66. // Use absolute paths based on discovered repository root
  67. var possibleBasePaths = []
  68. if (repoRoot) {
  69. // Use the discovered repository root
  70. possibleBasePaths = [
  71. "file://" + repoRoot + "/patterns/cached_images/"
  72. ]
  73. console.log("🎯 Using repository root for paths:", repoRoot)
  74. } else {
  75. console.log("⚠️ Repository root not found, using fallback relative paths")
  76. // Fallback to relative paths if repo root discovery failed
  77. possibleBasePaths = [
  78. "../../../patterns/cached_images/", // Three levels up from QML file location
  79. "../../patterns/cached_images/", // Two levels up (backup)
  80. "../../../../patterns/cached_images/" // Four levels up (backup)
  81. ]
  82. }
  83. var possiblePaths = []
  84. // Build paths using all possible base paths
  85. // Prioritize PNG format since WebP is not supported on this system
  86. for (var i = 0; i < possibleBasePaths.length; i++) {
  87. var basePath = possibleBasePaths[i]
  88. // First try with .thr suffix (e.g., pattern.thr.png) - PNG first since WebP failed
  89. possiblePaths.push(basePath + fileName + ".png")
  90. possiblePaths.push(basePath + fileName + ".jpg")
  91. possiblePaths.push(basePath + fileName + ".jpeg")
  92. // Then try without .thr suffix (e.g., pattern.png)
  93. possiblePaths.push(basePath + baseName + ".png")
  94. possiblePaths.push(basePath + baseName + ".jpg")
  95. possiblePaths.push(basePath + baseName + ".jpeg")
  96. }
  97. console.log("🔍 Possible preview paths:", JSON.stringify(possiblePaths))
  98. // Store all possible paths for fallback mechanism
  99. allPossiblePaths = possiblePaths
  100. currentPathIndex = 0
  101. // Set the active image source to avoid binding loops
  102. if (possiblePaths.length > 0) {
  103. currentPreviewPath = possiblePaths[0]
  104. activeImageSource = possiblePaths[0]
  105. console.log("🎯 Setting preview path to:", currentPreviewPath)
  106. console.log("🎯 Setting active image source to:", activeImageSource)
  107. } else {
  108. console.log("❌ No possible paths found")
  109. currentPreviewPath = ""
  110. activeImageSource = ""
  111. }
  112. }
  113. function tryNextPreviewPath() {
  114. if (allPossiblePaths.length === 0) {
  115. console.log("❌ No more paths to try")
  116. return false
  117. }
  118. currentPathIndex++
  119. if (currentPathIndex >= allPossiblePaths.length) {
  120. console.log("❌ All paths exhausted")
  121. return false
  122. }
  123. currentPreviewPath = allPossiblePaths[currentPathIndex]
  124. activeImageSource = allPossiblePaths[currentPathIndex]
  125. console.log("🔄 Trying next preview path:", currentPreviewPath)
  126. console.log("🔄 Setting active image source to:", activeImageSource)
  127. return true
  128. }
  129. function findRepoRoot() {
  130. // Start from the current QML file location and work our way up
  131. var currentPath = Qt.resolvedUrl(".").toString()
  132. console.log("🔍 Starting search from QML file location:", currentPath)
  133. // Remove file:// prefix and get directory parts
  134. if (currentPath.startsWith("file://")) {
  135. currentPath = currentPath.substring(7)
  136. }
  137. var pathParts = currentPath.split("/")
  138. console.log("🔍 Path parts:", JSON.stringify(pathParts))
  139. // Look for the dune-weaver directory by going up the path
  140. for (var i = pathParts.length - 1; i >= 0; i--) {
  141. if (pathParts[i] === "dune-weaver" || pathParts[i] === "dune-weaver-touch") {
  142. // Found it! Build the repo root path
  143. var rootPath = "/" + pathParts.slice(1, i + (pathParts[i] === "dune-weaver" ? 1 : 0)).join("/")
  144. if (pathParts[i] === "dune-weaver-touch") {
  145. // We need to go up one more level to get to dune-weaver
  146. rootPath = "/" + pathParts.slice(1, i).join("/")
  147. }
  148. repoRoot = rootPath
  149. console.log("🎯 Found repository root:", repoRoot)
  150. return
  151. }
  152. }
  153. console.log("❌ Could not find repository root")
  154. }
  155. // Timer to handle image retry without causing binding loops
  156. Timer {
  157. id: imageRetryTimer
  158. interval: 100 // Small delay to break the binding cycle
  159. onTriggered: {
  160. if (tryNextPreviewPath()) {
  161. console.log("🔄 Retrying with new path after timer...")
  162. }
  163. imageRetryInProgress = false
  164. }
  165. }
  166. Rectangle {
  167. anchors.fill: parent
  168. color: "#f5f5f5"
  169. }
  170. ColumnLayout {
  171. anchors.fill: parent
  172. spacing: 0
  173. // Header (consistent with other pages)
  174. Rectangle {
  175. Layout.fillWidth: true
  176. Layout.preferredHeight: 50
  177. color: "white"
  178. // Bottom border
  179. Rectangle {
  180. anchors.bottom: parent.bottom
  181. width: parent.width
  182. height: 1
  183. color: "#e5e7eb"
  184. }
  185. RowLayout {
  186. anchors.fill: parent
  187. anchors.leftMargin: 15
  188. anchors.rightMargin: 10
  189. ConnectionStatus {
  190. backend: page.backend
  191. Layout.rightMargin: 8
  192. }
  193. Label {
  194. text: "Pattern Execution"
  195. font.pixelSize: 18
  196. font.bold: true
  197. color: "#333"
  198. }
  199. Item {
  200. Layout.fillWidth: true
  201. }
  202. }
  203. }
  204. // Content - Side by side layout
  205. Item {
  206. Layout.fillWidth: true
  207. Layout.fillHeight: true
  208. Row {
  209. anchors.fill: parent
  210. spacing: 0
  211. // Left side - Pattern Preview (60% of width)
  212. Rectangle {
  213. width: parent.width * 0.6
  214. height: parent.height
  215. color: "#ffffff"
  216. Image {
  217. anchors.fill: parent
  218. anchors.margins: 10
  219. source: {
  220. var finalSource = ""
  221. // Try different sources in priority order
  222. if (patternPreview) {
  223. finalSource = "file:///" + patternPreview
  224. console.log("🖼️ Using patternPreview:", finalSource)
  225. } else if (activeImageSource) {
  226. // Use the activeImageSource to avoid binding loops
  227. finalSource = activeImageSource
  228. console.log("🖼️ Using activeImageSource:", finalSource)
  229. } else {
  230. console.log("🖼️ No preview source available")
  231. }
  232. return finalSource
  233. }
  234. fillMode: Image.PreserveAspectFit
  235. onStatusChanged: {
  236. console.log("📷 Image status:", status, "for source:", source)
  237. if (status === Image.Error) {
  238. console.log("❌ Image failed to load:", source)
  239. // Use timer to avoid binding loop
  240. if (!imageRetryInProgress) {
  241. imageRetryInProgress = true
  242. imageRetryTimer.start()
  243. }
  244. } else if (status === Image.Ready) {
  245. console.log("✅ Image loaded successfully:", source)
  246. imageRetryInProgress = false // Reset on successful load
  247. } else if (status === Image.Loading) {
  248. console.log("🔄 Image loading:", source)
  249. }
  250. }
  251. onSourceChanged: {
  252. console.log("🔄 Image source changed to:", source)
  253. }
  254. Rectangle {
  255. anchors.fill: parent
  256. color: "#f0f0f0"
  257. visible: parent.status === Image.Error || parent.source == ""
  258. Column {
  259. anchors.centerIn: parent
  260. spacing: 10
  261. Text {
  262. text: "⚙️"
  263. font.pixelSize: 48
  264. color: "#ccc"
  265. anchors.horizontalCenter: parent.horizontalCenter
  266. }
  267. Text {
  268. text: "Pattern Preview"
  269. color: "#999"
  270. font.pixelSize: 14
  271. anchors.horizontalCenter: parent.horizontalCenter
  272. }
  273. }
  274. }
  275. }
  276. }
  277. // Divider
  278. Rectangle {
  279. width: 1
  280. height: parent.height
  281. color: "#e5e7eb"
  282. }
  283. // Right side - Controls (40% of width)
  284. Rectangle {
  285. width: parent.width * 0.4 - 1
  286. height: parent.height
  287. color: "white"
  288. Column {
  289. anchors.left: parent.left
  290. anchors.right: parent.right
  291. anchors.top: parent.top
  292. anchors.margins: 10
  293. spacing: 15
  294. // Pattern Name
  295. Rectangle {
  296. width: parent.width
  297. height: 50
  298. radius: 8
  299. color: "#f8f9fa"
  300. border.color: "#e5e7eb"
  301. border.width: 1
  302. Column {
  303. anchors.centerIn: parent
  304. spacing: 4
  305. Label {
  306. text: "Current Pattern"
  307. font.pixelSize: 10
  308. color: "#666"
  309. anchors.horizontalCenter: parent.horizontalCenter
  310. }
  311. Label {
  312. text: {
  313. // Use WebSocket current pattern first, then fallback to passed parameter
  314. var displayName = ""
  315. if (backend && backend.currentFile) displayName = backend.currentFile
  316. else if (patternName) displayName = patternName
  317. else return "No pattern running"
  318. // Clean up the name for display
  319. var parts = displayName.split('/')
  320. displayName = parts[parts.length - 1]
  321. displayName = displayName.replace('.thr', '')
  322. return displayName
  323. }
  324. font.pixelSize: 12
  325. font.bold: true
  326. color: "#333"
  327. anchors.horizontalCenter: parent.horizontalCenter
  328. width: parent.parent.width - 20
  329. elide: Text.ElideMiddle
  330. horizontalAlignment: Text.AlignHCenter
  331. }
  332. }
  333. }
  334. // Progress
  335. Rectangle {
  336. width: parent.width
  337. height: 70
  338. radius: 8
  339. color: "#f8f9fa"
  340. border.color: "#e5e7eb"
  341. border.width: 1
  342. Column {
  343. anchors.fill: parent
  344. anchors.margins: 10
  345. spacing: 8
  346. Label {
  347. text: "Progress"
  348. font.pixelSize: 12
  349. font.bold: true
  350. color: "#333"
  351. }
  352. ProgressBar {
  353. width: parent.width
  354. height: 8
  355. value: backend ? backend.progress / 100 : 0
  356. }
  357. Label {
  358. text: backend ? Math.round(backend.progress) + "%" : "0%"
  359. anchors.horizontalCenter: parent.horizontalCenter
  360. font.pixelSize: 14
  361. font.bold: true
  362. color: "#333"
  363. }
  364. }
  365. }
  366. // Control Buttons
  367. Rectangle {
  368. width: parent.width
  369. height: 180
  370. radius: 8
  371. color: "#f8f9fa"
  372. border.color: "#e5e7eb"
  373. border.width: 1
  374. Column {
  375. anchors.fill: parent
  376. anchors.margins: 10
  377. spacing: 10
  378. Label {
  379. text: "Controls"
  380. font.pixelSize: 12
  381. font.bold: true
  382. color: "#333"
  383. }
  384. // Pause/Resume button
  385. Rectangle {
  386. width: parent.width
  387. height: 35
  388. radius: 6
  389. color: pauseMouseArea.pressed ? "#1e40af" : (backend && backend.currentFile !== "" ? "#2563eb" : "#9ca3af")
  390. Text {
  391. anchors.centerIn: parent
  392. text: (backend && backend.isRunning) ? "⏸ Pause" : "▶ Resume"
  393. color: "white"
  394. font.pixelSize: 12
  395. font.bold: true
  396. }
  397. MouseArea {
  398. id: pauseMouseArea
  399. anchors.fill: parent
  400. enabled: backend && backend.currentFile !== ""
  401. onClicked: {
  402. if (backend) {
  403. if (backend.isRunning) {
  404. backend.pauseExecution()
  405. } else {
  406. backend.resumeExecution()
  407. }
  408. }
  409. }
  410. }
  411. }
  412. // Stop button
  413. Rectangle {
  414. width: parent.width
  415. height: 35
  416. radius: 6
  417. color: stopMouseArea.pressed ? "#b91c1c" : (backend && backend.currentFile !== "" ? "#dc2626" : "#9ca3af")
  418. Text {
  419. anchors.centerIn: parent
  420. text: "⏹ Stop"
  421. color: "white"
  422. font.pixelSize: 12
  423. font.bold: true
  424. }
  425. MouseArea {
  426. id: stopMouseArea
  427. anchors.fill: parent
  428. enabled: backend && backend.currentFile !== ""
  429. onClicked: {
  430. if (backend) {
  431. backend.stopExecution()
  432. }
  433. }
  434. }
  435. }
  436. // Skip button
  437. Rectangle {
  438. width: parent.width
  439. height: 35
  440. radius: 6
  441. color: skipMouseArea.pressed ? "#525252" : (backend && backend.currentFile !== "" ? "#6b7280" : "#9ca3af")
  442. Text {
  443. anchors.centerIn: parent
  444. text: "⏭ Skip"
  445. color: "white"
  446. font.pixelSize: 12
  447. font.bold: true
  448. }
  449. MouseArea {
  450. id: skipMouseArea
  451. anchors.fill: parent
  452. enabled: backend && backend.currentFile !== ""
  453. onClicked: {
  454. if (backend) {
  455. backend.skipPattern()
  456. }
  457. }
  458. }
  459. }
  460. }
  461. }
  462. }
  463. }
  464. }
  465. }
  466. }
  467. }