TableControlPage.qml 26 KB

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