TableControlPage.qml 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  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 bool autoPlayOnBoot: false
  13. // Backend signal connections
  14. Connections {
  15. target: backend
  16. function onSerialPortsUpdated(ports) {
  17. console.log("Serial ports updated:", ports)
  18. serialPorts = ports
  19. }
  20. function onSerialConnectionChanged(connected) {
  21. console.log("Serial connection changed:", connected)
  22. isSerialConnected = connected
  23. }
  24. function onCurrentPortChanged(port) {
  25. console.log("Current port changed:", port)
  26. if (port) {
  27. selectedPort = port
  28. }
  29. }
  30. function onSettingsLoaded() {
  31. console.log("Settings loaded")
  32. if (backend) {
  33. autoPlayOnBoot = backend.autoPlayOnBoot
  34. isSerialConnected = backend.serialConnected
  35. // Screen timeout is now managed by button selection, no need to convert
  36. if (backend.currentPort) {
  37. selectedPort = backend.currentPort
  38. }
  39. }
  40. }
  41. }
  42. // Refresh serial ports on page load
  43. Component.onCompleted: {
  44. refreshSerialPorts()
  45. loadSettings()
  46. }
  47. function refreshSerialPorts() {
  48. if (backend) {
  49. backend.refreshSerialPorts()
  50. }
  51. }
  52. function loadSettings() {
  53. if (backend) {
  54. backend.loadControlSettings()
  55. }
  56. }
  57. Rectangle {
  58. anchors.fill: parent
  59. color: "#f5f5f5"
  60. }
  61. ColumnLayout {
  62. anchors.fill: parent
  63. spacing: 0
  64. // Header
  65. Rectangle {
  66. Layout.fillWidth: true
  67. Layout.preferredHeight: 50
  68. color: "white"
  69. Rectangle {
  70. anchors.bottom: parent.bottom
  71. width: parent.width
  72. height: 1
  73. color: "#e5e7eb"
  74. }
  75. RowLayout {
  76. anchors.fill: parent
  77. anchors.leftMargin: 15
  78. anchors.rightMargin: 10
  79. ConnectionStatus {
  80. backend: page.backend
  81. Layout.rightMargin: 8
  82. }
  83. Label {
  84. text: "Table Control"
  85. font.pixelSize: 18
  86. font.bold: true
  87. color: "#333"
  88. }
  89. Item {
  90. Layout.fillWidth: true
  91. }
  92. }
  93. }
  94. // Main Content
  95. ScrollView {
  96. Layout.fillWidth: true
  97. Layout.fillHeight: true
  98. contentWidth: availableWidth
  99. ColumnLayout {
  100. width: parent.width
  101. anchors.margins: 5
  102. spacing: 2
  103. // Serial Connection Section
  104. Rectangle {
  105. Layout.fillWidth: true
  106. Layout.preferredHeight: 160
  107. Layout.margins: 5
  108. radius: 8
  109. color: "white"
  110. ColumnLayout {
  111. anchors.fill: parent
  112. anchors.margins: 15
  113. spacing: 10
  114. Label {
  115. text: "Serial Connection"
  116. font.pixelSize: 14
  117. font.bold: true
  118. color: "#333"
  119. }
  120. RowLayout {
  121. Layout.fillWidth: true
  122. spacing: 10
  123. Rectangle {
  124. Layout.fillWidth: true
  125. Layout.preferredHeight: 40
  126. radius: 6
  127. color: isSerialConnected ? "#e8f5e8" : "#f8f9fa"
  128. border.color: isSerialConnected ? "#4CAF50" : "#e5e7eb"
  129. border.width: 1
  130. RowLayout {
  131. anchors.fill: parent
  132. anchors.margins: 8
  133. Label {
  134. text: isSerialConnected ?
  135. (selectedPort ? `Connected: ${selectedPort}` : "Connected") :
  136. (selectedPort || "No port selected")
  137. color: isSerialConnected ? "#2e7d32" : (selectedPort ? "#333" : "#999")
  138. font.pixelSize: 12
  139. font.bold: isSerialConnected
  140. Layout.fillWidth: true
  141. }
  142. Text {
  143. text: "▼"
  144. color: "#666"
  145. font.pixelSize: 10
  146. visible: !isSerialConnected
  147. }
  148. }
  149. MouseArea {
  150. anchors.fill: parent
  151. enabled: !isSerialConnected
  152. onClicked: portMenu.open()
  153. }
  154. Menu {
  155. id: portMenu
  156. y: parent.height
  157. // Counter-rotate menu content to fix upside-down issue
  158. delegate: MenuItem {
  159. transform: Rotation {
  160. origin.x: width / 2
  161. origin.y: height / 2
  162. angle: 180
  163. }
  164. }
  165. // Rotate the background/container
  166. background: Rectangle {
  167. color: "white"
  168. border.color: "#ddd"
  169. border.width: 1
  170. radius: 4
  171. transform: Rotation {
  172. origin.x: width / 2
  173. origin.y: height / 2
  174. angle: 180
  175. }
  176. }
  177. Repeater {
  178. model: serialPorts
  179. MenuItem {
  180. text: modelData
  181. onTriggered: {
  182. selectedPort = modelData
  183. }
  184. }
  185. }
  186. MenuSeparator {}
  187. MenuItem {
  188. text: "Refresh Ports"
  189. onTriggered: refreshSerialPorts()
  190. }
  191. }
  192. }
  193. ModernControlButton {
  194. Layout.preferredWidth: 150
  195. Layout.preferredHeight: 40
  196. text: isSerialConnected ? "Disconnect" : "Connect"
  197. icon: isSerialConnected ? "◉" : "○"
  198. buttonColor: isSerialConnected ? "#dc2626" : "#059669"
  199. fontSize: 11
  200. enabled: isSerialConnected || selectedPort !== ""
  201. onClicked: {
  202. if (backend) {
  203. if (isSerialConnected) {
  204. backend.disconnectSerial()
  205. } else {
  206. backend.connectSerial(selectedPort)
  207. }
  208. }
  209. }
  210. }
  211. }
  212. RowLayout {
  213. Layout.fillWidth: true
  214. spacing: 8
  215. visible: !isSerialConnected
  216. ModernControlButton {
  217. Layout.fillWidth: true
  218. Layout.preferredHeight: 35
  219. text: "Refresh Ports"
  220. icon: "↻"
  221. buttonColor: "#6b7280"
  222. fontSize: 10
  223. onClicked: refreshSerialPorts()
  224. }
  225. }
  226. }
  227. }
  228. // Hardware Movement Section
  229. Rectangle {
  230. Layout.fillWidth: true
  231. Layout.preferredHeight: 100
  232. Layout.margins: 5
  233. radius: 8
  234. color: "white"
  235. ColumnLayout {
  236. anchors.fill: parent
  237. anchors.margins: 15
  238. spacing: 10
  239. Label {
  240. text: "Table Movement"
  241. font.pixelSize: 14
  242. font.bold: true
  243. color: "#333"
  244. }
  245. GridLayout {
  246. Layout.fillWidth: true
  247. columns: 3
  248. rowSpacing: 8
  249. columnSpacing: 8
  250. ModernControlButton {
  251. Layout.fillWidth: true
  252. Layout.preferredHeight: 45
  253. text: "Home"
  254. icon: "⌂"
  255. buttonColor: "#2563eb"
  256. fontSize: 12
  257. enabled: isSerialConnected
  258. onClicked: {
  259. if (backend) backend.sendHome()
  260. }
  261. }
  262. ModernControlButton {
  263. Layout.fillWidth: true
  264. Layout.preferredHeight: 45
  265. text: "Center"
  266. icon: "◎"
  267. buttonColor: "#2563eb"
  268. fontSize: 12
  269. enabled: isSerialConnected
  270. onClicked: {
  271. if (backend) backend.moveToCenter()
  272. }
  273. }
  274. ModernControlButton {
  275. Layout.fillWidth: true
  276. Layout.preferredHeight: 45
  277. text: "Perimeter"
  278. icon: "○"
  279. buttonColor: "#2563eb"
  280. fontSize: 12
  281. enabled: isSerialConnected
  282. onClicked: {
  283. if (backend) backend.moveToPerimeter()
  284. }
  285. }
  286. }
  287. }
  288. }
  289. // Auto Play on Boot Section
  290. Rectangle {
  291. Layout.fillWidth: true
  292. Layout.preferredHeight: 200 // Reduced from 280 for single row layout
  293. Layout.margins: 5
  294. radius: 8
  295. color: "white"
  296. ColumnLayout {
  297. anchors.fill: parent
  298. anchors.margins: 15
  299. spacing: 10
  300. Label {
  301. text: "Auto Play Settings"
  302. font.pixelSize: 14
  303. font.bold: true
  304. color: "#333"
  305. }
  306. RowLayout {
  307. Layout.fillWidth: true
  308. spacing: 10
  309. Label {
  310. text: "Auto play on boot:"
  311. font.pixelSize: 12
  312. color: "#666"
  313. Layout.fillWidth: true
  314. }
  315. Switch {
  316. id: autoPlaySwitch
  317. checked: autoPlayOnBoot
  318. onToggled: {
  319. autoPlayOnBoot = checked
  320. if (backend) {
  321. backend.setAutoPlayOnBoot(checked)
  322. }
  323. }
  324. }
  325. }
  326. ColumnLayout {
  327. Layout.fillWidth: true
  328. spacing: 15
  329. Label {
  330. text: "Screen timeout:"
  331. font.pixelSize: 14
  332. font.bold: true
  333. color: "#333"
  334. Layout.alignment: Qt.AlignLeft
  335. }
  336. // Touch-friendly button row for timeout options
  337. RowLayout {
  338. id: timeoutGrid
  339. Layout.fillWidth: true
  340. spacing: 8
  341. property string currentSelection: backend ? backend.getCurrentScreenTimeoutOption() : "5 minutes"
  342. // 30 seconds button
  343. Rectangle {
  344. Layout.preferredWidth: 100
  345. Layout.preferredHeight: 50
  346. color: timeoutGrid.currentSelection === "30 seconds" ? "#2196F3" : "#f0f0f0"
  347. border.color: timeoutGrid.currentSelection === "30 seconds" ? "#1976D2" : "#ccc"
  348. border.width: 2
  349. radius: 8
  350. Label {
  351. anchors.centerIn: parent
  352. text: "30s"
  353. font.pixelSize: 14
  354. font.bold: true
  355. color: timeoutGrid.currentSelection === "30 seconds" ? "white" : "#333"
  356. }
  357. MouseArea {
  358. anchors.fill: parent
  359. onClicked: {
  360. if (backend) {
  361. backend.setScreenTimeoutByOption("30 seconds")
  362. timeoutGrid.currentSelection = "30 seconds"
  363. }
  364. }
  365. }
  366. }
  367. // 1 minute button
  368. Rectangle {
  369. Layout.preferredWidth: 100
  370. Layout.preferredHeight: 50
  371. color: timeoutGrid.currentSelection === "1 minute" ? "#2196F3" : "#f0f0f0"
  372. border.color: timeoutGrid.currentSelection === "1 minute" ? "#1976D2" : "#ccc"
  373. border.width: 2
  374. radius: 8
  375. Label {
  376. anchors.centerIn: parent
  377. text: "1min"
  378. font.pixelSize: 14
  379. font.bold: true
  380. color: timeoutGrid.currentSelection === "1 minute" ? "white" : "#333"
  381. }
  382. MouseArea {
  383. anchors.fill: parent
  384. onClicked: {
  385. if (backend) {
  386. backend.setScreenTimeoutByOption("1 minute")
  387. timeoutGrid.currentSelection = "1 minute"
  388. }
  389. }
  390. }
  391. }
  392. // 5 minutes button
  393. Rectangle {
  394. Layout.preferredWidth: 100
  395. Layout.preferredHeight: 50
  396. color: timeoutGrid.currentSelection === "5 minutes" ? "#2196F3" : "#f0f0f0"
  397. border.color: timeoutGrid.currentSelection === "5 minutes" ? "#1976D2" : "#ccc"
  398. border.width: 2
  399. radius: 8
  400. Label {
  401. anchors.centerIn: parent
  402. text: "5min"
  403. font.pixelSize: 14
  404. font.bold: true
  405. color: timeoutGrid.currentSelection === "5 minutes" ? "white" : "#333"
  406. }
  407. MouseArea {
  408. anchors.fill: parent
  409. onClicked: {
  410. if (backend) {
  411. backend.setScreenTimeoutByOption("5 minutes")
  412. timeoutGrid.currentSelection = "5 minutes"
  413. }
  414. }
  415. }
  416. }
  417. // 10 minutes button
  418. Rectangle {
  419. Layout.preferredWidth: 100
  420. Layout.preferredHeight: 50
  421. color: timeoutGrid.currentSelection === "10 minutes" ? "#2196F3" : "#f0f0f0"
  422. border.color: timeoutGrid.currentSelection === "10 minutes" ? "#1976D2" : "#ccc"
  423. border.width: 2
  424. radius: 8
  425. Label {
  426. anchors.centerIn: parent
  427. text: "10min"
  428. font.pixelSize: 14
  429. font.bold: true
  430. color: timeoutGrid.currentSelection === "10 minutes" ? "white" : "#333"
  431. }
  432. MouseArea {
  433. anchors.fill: parent
  434. onClicked: {
  435. if (backend) {
  436. backend.setScreenTimeoutByOption("10 minutes")
  437. timeoutGrid.currentSelection = "10 minutes"
  438. }
  439. }
  440. }
  441. }
  442. // Never button
  443. Rectangle {
  444. Layout.preferredWidth: 100
  445. Layout.preferredHeight: 50
  446. color: timeoutGrid.currentSelection === "Never" ? "#FF9800" : "#f0f0f0"
  447. border.color: timeoutGrid.currentSelection === "Never" ? "#F57C00" : "#ccc"
  448. border.width: 2
  449. radius: 8
  450. Label {
  451. anchors.centerIn: parent
  452. text: "Never"
  453. font.pixelSize: 14
  454. font.bold: true
  455. color: timeoutGrid.currentSelection === "Never" ? "white" : "#333"
  456. }
  457. MouseArea {
  458. anchors.fill: parent
  459. onClicked: {
  460. if (backend) {
  461. backend.setScreenTimeoutByOption("Never")
  462. timeoutGrid.currentSelection = "Never"
  463. }
  464. }
  465. }
  466. }
  467. // Update selection when backend changes
  468. Connections {
  469. target: backend
  470. function onScreenTimeoutChanged() {
  471. if (backend) {
  472. timeoutGrid.currentSelection = backend.getCurrentScreenTimeoutOption()
  473. }
  474. }
  475. }
  476. }
  477. }
  478. }
  479. }
  480. // Add some bottom spacing for better scrolling
  481. Item {
  482. Layout.preferredHeight: 20
  483. }
  484. }
  485. }
  486. }
  487. }