TableControlPage.qml 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. import QtQuick 2.15
  2. import QtQuick.Controls 2.15
  3. import QtQuick.Layouts 1.15
  4. import QtQuick.Effects
  5. import "../components"
  6. Page {
  7. id: page
  8. property var backend: null
  9. property var serialPorts: []
  10. property string selectedPort: ""
  11. property bool isSerialConnected: false
  12. property int currentSpeed: 130
  13. property bool autoPlayOnBoot: false
  14. // Backend signal connections
  15. Connections {
  16. target: backend
  17. function onSerialPortsUpdated(ports) {
  18. console.log("Serial ports updated:", ports)
  19. serialPorts = ports
  20. }
  21. function onSerialConnectionChanged(connected) {
  22. console.log("Serial connection changed:", connected)
  23. isSerialConnected = connected
  24. }
  25. function onCurrentPortChanged(port) {
  26. console.log("Current port changed:", port)
  27. if (port) {
  28. selectedPort = port
  29. }
  30. }
  31. function onSpeedChanged(speed) {
  32. console.log("Speed changed:", speed)
  33. currentSpeed = speed
  34. }
  35. function onSettingsLoaded() {
  36. console.log("Settings loaded")
  37. if (backend) {
  38. autoPlayOnBoot = backend.autoPlayOnBoot
  39. currentSpeed = backend.currentSpeed
  40. isSerialConnected = backend.serialConnected
  41. // Screen timeout is now managed by button selection, no need to convert
  42. if (backend.currentPort) {
  43. selectedPort = backend.currentPort
  44. }
  45. }
  46. }
  47. }
  48. // Refresh serial ports on page load
  49. Component.onCompleted: {
  50. refreshSerialPorts()
  51. loadSettings()
  52. }
  53. function refreshSerialPorts() {
  54. if (backend) {
  55. backend.refreshSerialPorts()
  56. }
  57. }
  58. function loadSettings() {
  59. if (backend) {
  60. backend.loadControlSettings()
  61. }
  62. }
  63. Rectangle {
  64. anchors.fill: parent
  65. color: "#f5f5f5"
  66. }
  67. ColumnLayout {
  68. anchors.fill: parent
  69. spacing: 0
  70. // Header
  71. Rectangle {
  72. Layout.fillWidth: true
  73. Layout.preferredHeight: 50
  74. color: "white"
  75. Rectangle {
  76. anchors.bottom: parent.bottom
  77. width: parent.width
  78. height: 1
  79. color: "#e5e7eb"
  80. }
  81. RowLayout {
  82. anchors.fill: parent
  83. anchors.leftMargin: 15
  84. anchors.rightMargin: 10
  85. ConnectionStatus {
  86. backend: page.backend
  87. Layout.rightMargin: 8
  88. }
  89. Label {
  90. text: "Table Control"
  91. font.pixelSize: 18
  92. font.bold: true
  93. color: "#333"
  94. }
  95. Item {
  96. Layout.fillWidth: true
  97. }
  98. }
  99. }
  100. // Main Content
  101. ScrollView {
  102. Layout.fillWidth: true
  103. Layout.fillHeight: true
  104. contentWidth: availableWidth
  105. ColumnLayout {
  106. width: parent.width
  107. anchors.margins: 10
  108. spacing: 10
  109. // Serial Connection Section
  110. Rectangle {
  111. Layout.fillWidth: true
  112. Layout.preferredHeight: 160
  113. Layout.margins: 10
  114. radius: 8
  115. color: "white"
  116. ColumnLayout {
  117. anchors.fill: parent
  118. anchors.margins: 15
  119. spacing: 10
  120. Label {
  121. text: "Serial Connection"
  122. font.pixelSize: 14
  123. font.bold: true
  124. color: "#333"
  125. }
  126. RowLayout {
  127. Layout.fillWidth: true
  128. spacing: 10
  129. Rectangle {
  130. Layout.fillWidth: true
  131. Layout.preferredHeight: 40
  132. radius: 6
  133. color: isSerialConnected ? "#e8f5e8" : "#f8f9fa"
  134. border.color: isSerialConnected ? "#4CAF50" : "#e5e7eb"
  135. border.width: 1
  136. RowLayout {
  137. anchors.fill: parent
  138. anchors.margins: 8
  139. Label {
  140. text: isSerialConnected ?
  141. (selectedPort ? `Connected: ${selectedPort}` : "Connected") :
  142. (selectedPort || "No port selected")
  143. color: isSerialConnected ? "#2e7d32" : (selectedPort ? "#333" : "#999")
  144. font.pixelSize: 12
  145. font.bold: isSerialConnected
  146. Layout.fillWidth: true
  147. }
  148. Text {
  149. text: "▼"
  150. color: "#666"
  151. font.pixelSize: 10
  152. visible: !isSerialConnected
  153. }
  154. }
  155. MouseArea {
  156. anchors.fill: parent
  157. enabled: !isSerialConnected
  158. onClicked: portMenu.open()
  159. }
  160. Menu {
  161. id: portMenu
  162. y: parent.height
  163. Repeater {
  164. model: serialPorts
  165. MenuItem {
  166. text: modelData
  167. onTriggered: {
  168. selectedPort = modelData
  169. }
  170. }
  171. }
  172. MenuSeparator {}
  173. MenuItem {
  174. text: "Refresh Ports"
  175. onTriggered: refreshSerialPorts()
  176. }
  177. }
  178. }
  179. ModernControlButton {
  180. Layout.preferredWidth: 150
  181. Layout.preferredHeight: 40
  182. text: isSerialConnected ? "Disconnect" : "Connect"
  183. icon: isSerialConnected ? "🔌" : "🔗"
  184. buttonColor: isSerialConnected ? "#dc2626" : "#059669"
  185. fontSize: 11
  186. enabled: isSerialConnected || selectedPort !== ""
  187. onClicked: {
  188. if (backend) {
  189. if (isSerialConnected) {
  190. backend.disconnectSerial()
  191. } else {
  192. backend.connectSerial(selectedPort)
  193. }
  194. }
  195. }
  196. }
  197. }
  198. RowLayout {
  199. Layout.fillWidth: true
  200. spacing: 8
  201. visible: !isSerialConnected
  202. ModernControlButton {
  203. Layout.fillWidth: true
  204. Layout.preferredHeight: 35
  205. text: "Refresh Ports"
  206. icon: "🔄"
  207. buttonColor: "#6b7280"
  208. fontSize: 10
  209. onClicked: refreshSerialPorts()
  210. }
  211. }
  212. }
  213. }
  214. // Hardware Movement Section
  215. Rectangle {
  216. Layout.fillWidth: true
  217. Layout.preferredHeight: 180
  218. Layout.margins: 10
  219. radius: 8
  220. color: "white"
  221. ColumnLayout {
  222. anchors.fill: parent
  223. anchors.margins: 15
  224. spacing: 10
  225. Label {
  226. text: "Table Movement"
  227. font.pixelSize: 14
  228. font.bold: true
  229. color: "#333"
  230. }
  231. GridLayout {
  232. Layout.fillWidth: true
  233. columns: 3
  234. rowSpacing: 8
  235. columnSpacing: 8
  236. ModernControlButton {
  237. Layout.fillWidth: true
  238. Layout.preferredHeight: 45
  239. text: "Home"
  240. icon: "🏠"
  241. buttonColor: "#2563eb"
  242. fontSize: 12
  243. enabled: isSerialConnected
  244. onClicked: {
  245. if (backend) backend.sendHome()
  246. }
  247. }
  248. ModernControlButton {
  249. Layout.fillWidth: true
  250. Layout.preferredHeight: 45
  251. text: "Center"
  252. icon: "🎯"
  253. buttonColor: "#2563eb"
  254. fontSize: 12
  255. enabled: isSerialConnected
  256. onClicked: {
  257. if (backend) backend.moveToCenter()
  258. }
  259. }
  260. ModernControlButton {
  261. Layout.fillWidth: true
  262. Layout.preferredHeight: 45
  263. text: "Perimeter"
  264. icon: "⭕"
  265. buttonColor: "#2563eb"
  266. fontSize: 12
  267. enabled: isSerialConnected
  268. onClicked: {
  269. if (backend) backend.moveToPerimeter()
  270. }
  271. }
  272. }
  273. }
  274. }
  275. // Speed Control Section
  276. Rectangle {
  277. Layout.fillWidth: true
  278. Layout.preferredHeight: 120 // Reduced from original for single row layout
  279. Layout.margins: 10
  280. radius: 8
  281. color: "white"
  282. ColumnLayout {
  283. anchors.fill: parent
  284. anchors.margins: 15
  285. spacing: 15
  286. Label {
  287. text: "Speed:"
  288. font.pixelSize: 14
  289. font.bold: true
  290. color: "#333"
  291. Layout.alignment: Qt.AlignLeft
  292. }
  293. // Touch-friendly button row for speed options
  294. RowLayout {
  295. id: speedGrid
  296. Layout.fillWidth: true
  297. spacing: 8
  298. property string currentSelection: backend ? backend.getCurrentSpeedOption() : "200"
  299. // 50 button
  300. Rectangle {
  301. Layout.preferredWidth: 100
  302. Layout.preferredHeight: 50
  303. color: speedGrid.currentSelection === "50" ? "#2196F3" : "#f0f0f0"
  304. border.color: speedGrid.currentSelection === "50" ? "#1976D2" : "#ccc"
  305. border.width: 2
  306. radius: 8
  307. Label {
  308. anchors.centerIn: parent
  309. text: "50"
  310. font.pixelSize: 14
  311. font.bold: true
  312. color: speedGrid.currentSelection === "50" ? "white" : "#333"
  313. }
  314. MouseArea {
  315. anchors.fill: parent
  316. onClicked: {
  317. if (backend) {
  318. backend.setSpeedByOption("50")
  319. speedGrid.currentSelection = "50"
  320. }
  321. }
  322. }
  323. }
  324. // 100 button
  325. Rectangle {
  326. Layout.preferredWidth: 100
  327. Layout.preferredHeight: 50
  328. color: speedGrid.currentSelection === "100" ? "#2196F3" : "#f0f0f0"
  329. border.color: speedGrid.currentSelection === "100" ? "#1976D2" : "#ccc"
  330. border.width: 2
  331. radius: 8
  332. Label {
  333. anchors.centerIn: parent
  334. text: "100"
  335. font.pixelSize: 14
  336. font.bold: true
  337. color: speedGrid.currentSelection === "100" ? "white" : "#333"
  338. }
  339. MouseArea {
  340. anchors.fill: parent
  341. onClicked: {
  342. if (backend) {
  343. backend.setSpeedByOption("100")
  344. speedGrid.currentSelection = "100"
  345. }
  346. }
  347. }
  348. }
  349. // 200 button
  350. Rectangle {
  351. Layout.preferredWidth: 100
  352. Layout.preferredHeight: 50
  353. color: speedGrid.currentSelection === "200" ? "#2196F3" : "#f0f0f0"
  354. border.color: speedGrid.currentSelection === "200" ? "#1976D2" : "#ccc"
  355. border.width: 2
  356. radius: 8
  357. Label {
  358. anchors.centerIn: parent
  359. text: "200"
  360. font.pixelSize: 14
  361. font.bold: true
  362. color: speedGrid.currentSelection === "200" ? "white" : "#333"
  363. }
  364. MouseArea {
  365. anchors.fill: parent
  366. onClicked: {
  367. if (backend) {
  368. backend.setSpeedByOption("200")
  369. speedGrid.currentSelection = "200"
  370. }
  371. }
  372. }
  373. }
  374. // 300 button
  375. Rectangle {
  376. Layout.preferredWidth: 100
  377. Layout.preferredHeight: 50
  378. color: speedGrid.currentSelection === "300" ? "#2196F3" : "#f0f0f0"
  379. border.color: speedGrid.currentSelection === "300" ? "#1976D2" : "#ccc"
  380. border.width: 2
  381. radius: 8
  382. Label {
  383. anchors.centerIn: parent
  384. text: "300"
  385. font.pixelSize: 14
  386. font.bold: true
  387. color: speedGrid.currentSelection === "300" ? "white" : "#333"
  388. }
  389. MouseArea {
  390. anchors.fill: parent
  391. onClicked: {
  392. if (backend) {
  393. backend.setSpeedByOption("300")
  394. speedGrid.currentSelection = "300"
  395. }
  396. }
  397. }
  398. }
  399. // 500 button
  400. Rectangle {
  401. Layout.preferredWidth: 100
  402. Layout.preferredHeight: 50
  403. color: speedGrid.currentSelection === "500" ? "#2196F3" : "#f0f0f0"
  404. border.color: speedGrid.currentSelection === "500" ? "#1976D2" : "#ccc"
  405. border.width: 2
  406. radius: 8
  407. Label {
  408. anchors.centerIn: parent
  409. text: "500"
  410. font.pixelSize: 14
  411. font.bold: true
  412. color: speedGrid.currentSelection === "500" ? "white" : "#333"
  413. }
  414. MouseArea {
  415. anchors.fill: parent
  416. onClicked: {
  417. if (backend) {
  418. backend.setSpeedByOption("500")
  419. speedGrid.currentSelection = "500"
  420. }
  421. }
  422. }
  423. }
  424. // Update selection when backend changes
  425. Connections {
  426. target: backend
  427. function onSpeedChanged(speed) {
  428. if (backend) {
  429. speedGrid.currentSelection = backend.getCurrentSpeedOption()
  430. }
  431. }
  432. }
  433. }
  434. }
  435. }
  436. // Auto Play on Boot Section
  437. Rectangle {
  438. Layout.fillWidth: true
  439. Layout.preferredHeight: 200 // Reduced from 280 for single row layout
  440. Layout.margins: 10
  441. radius: 8
  442. color: "white"
  443. ColumnLayout {
  444. anchors.fill: parent
  445. anchors.margins: 15
  446. spacing: 10
  447. Label {
  448. text: "Auto Play Settings"
  449. font.pixelSize: 14
  450. font.bold: true
  451. color: "#333"
  452. }
  453. RowLayout {
  454. Layout.fillWidth: true
  455. spacing: 10
  456. Label {
  457. text: "Auto play on boot:"
  458. font.pixelSize: 12
  459. color: "#666"
  460. Layout.fillWidth: true
  461. }
  462. Switch {
  463. id: autoPlaySwitch
  464. checked: autoPlayOnBoot
  465. onToggled: {
  466. autoPlayOnBoot = checked
  467. if (backend) {
  468. backend.setAutoPlayOnBoot(checked)
  469. }
  470. }
  471. }
  472. }
  473. ColumnLayout {
  474. Layout.fillWidth: true
  475. spacing: 15
  476. Label {
  477. text: "Screen timeout:"
  478. font.pixelSize: 14
  479. font.bold: true
  480. color: "#333"
  481. Layout.alignment: Qt.AlignLeft
  482. }
  483. // Touch-friendly button row for timeout options
  484. RowLayout {
  485. id: timeoutGrid
  486. Layout.fillWidth: true
  487. spacing: 8
  488. property string currentSelection: backend ? backend.getCurrentScreenTimeoutOption() : "5 minutes"
  489. // 30 seconds button
  490. Rectangle {
  491. Layout.preferredWidth: 100
  492. Layout.preferredHeight: 50
  493. color: timeoutGrid.currentSelection === "30 seconds" ? "#2196F3" : "#f0f0f0"
  494. border.color: timeoutGrid.currentSelection === "30 seconds" ? "#1976D2" : "#ccc"
  495. border.width: 2
  496. radius: 8
  497. Label {
  498. anchors.centerIn: parent
  499. text: "30s"
  500. font.pixelSize: 14
  501. font.bold: true
  502. color: timeoutGrid.currentSelection === "30 seconds" ? "white" : "#333"
  503. }
  504. MouseArea {
  505. anchors.fill: parent
  506. onClicked: {
  507. if (backend) {
  508. backend.setScreenTimeoutByOption("30 seconds")
  509. timeoutGrid.currentSelection = "30 seconds"
  510. }
  511. }
  512. }
  513. }
  514. // 1 minute button
  515. Rectangle {
  516. Layout.preferredWidth: 100
  517. Layout.preferredHeight: 50
  518. color: timeoutGrid.currentSelection === "1 minute" ? "#2196F3" : "#f0f0f0"
  519. border.color: timeoutGrid.currentSelection === "1 minute" ? "#1976D2" : "#ccc"
  520. border.width: 2
  521. radius: 8
  522. Label {
  523. anchors.centerIn: parent
  524. text: "1min"
  525. font.pixelSize: 14
  526. font.bold: true
  527. color: timeoutGrid.currentSelection === "1 minute" ? "white" : "#333"
  528. }
  529. MouseArea {
  530. anchors.fill: parent
  531. onClicked: {
  532. if (backend) {
  533. backend.setScreenTimeoutByOption("1 minute")
  534. timeoutGrid.currentSelection = "1 minute"
  535. }
  536. }
  537. }
  538. }
  539. // 5 minutes button
  540. Rectangle {
  541. Layout.preferredWidth: 100
  542. Layout.preferredHeight: 50
  543. color: timeoutGrid.currentSelection === "5 minutes" ? "#2196F3" : "#f0f0f0"
  544. border.color: timeoutGrid.currentSelection === "5 minutes" ? "#1976D2" : "#ccc"
  545. border.width: 2
  546. radius: 8
  547. Label {
  548. anchors.centerIn: parent
  549. text: "5min"
  550. font.pixelSize: 14
  551. font.bold: true
  552. color: timeoutGrid.currentSelection === "5 minutes" ? "white" : "#333"
  553. }
  554. MouseArea {
  555. anchors.fill: parent
  556. onClicked: {
  557. if (backend) {
  558. backend.setScreenTimeoutByOption("5 minutes")
  559. timeoutGrid.currentSelection = "5 minutes"
  560. }
  561. }
  562. }
  563. }
  564. // 10 minutes button
  565. Rectangle {
  566. Layout.preferredWidth: 100
  567. Layout.preferredHeight: 50
  568. color: timeoutGrid.currentSelection === "10 minutes" ? "#2196F3" : "#f0f0f0"
  569. border.color: timeoutGrid.currentSelection === "10 minutes" ? "#1976D2" : "#ccc"
  570. border.width: 2
  571. radius: 8
  572. Label {
  573. anchors.centerIn: parent
  574. text: "10min"
  575. font.pixelSize: 14
  576. font.bold: true
  577. color: timeoutGrid.currentSelection === "10 minutes" ? "white" : "#333"
  578. }
  579. MouseArea {
  580. anchors.fill: parent
  581. onClicked: {
  582. if (backend) {
  583. backend.setScreenTimeoutByOption("10 minutes")
  584. timeoutGrid.currentSelection = "10 minutes"
  585. }
  586. }
  587. }
  588. }
  589. // Never button
  590. Rectangle {
  591. Layout.preferredWidth: 100
  592. Layout.preferredHeight: 50
  593. color: timeoutGrid.currentSelection === "Never" ? "#FF9800" : "#f0f0f0"
  594. border.color: timeoutGrid.currentSelection === "Never" ? "#F57C00" : "#ccc"
  595. border.width: 2
  596. radius: 8
  597. Label {
  598. anchors.centerIn: parent
  599. text: "Never"
  600. font.pixelSize: 14
  601. font.bold: true
  602. color: timeoutGrid.currentSelection === "Never" ? "white" : "#333"
  603. }
  604. MouseArea {
  605. anchors.fill: parent
  606. onClicked: {
  607. if (backend) {
  608. backend.setScreenTimeoutByOption("Never")
  609. timeoutGrid.currentSelection = "Never"
  610. }
  611. }
  612. }
  613. }
  614. // Update selection when backend changes
  615. Connections {
  616. target: backend
  617. function onScreenTimeoutChanged() {
  618. if (backend) {
  619. timeoutGrid.currentSelection = backend.getCurrentScreenTimeoutOption()
  620. }
  621. }
  622. }
  623. }
  624. }
  625. }
  626. }
  627. // Add some bottom spacing for better scrolling
  628. Item {
  629. Layout.preferredHeight: 20
  630. }
  631. }
  632. }
  633. }
  634. }