FanItem.qml 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. /*
  2. * Copyright (C) 2015 Malte Veerman <maldela@halloarsch.de>
  3. *
  4. * This program is free software; you can redistribute it and/or modify
  5. * it under the terms of the GNU Lesser General Public License as published by
  6. * the Free Software Foundation; either version 2 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU Lesser General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Lesser General Public License along
  15. * with this program; if not, write to the Free Software Foundation, Inc.,
  16. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  17. *
  18. */
  19. import QtQuick 2.4
  20. import QtQuick.Controls 1.4
  21. import QtQuick.Layouts 1.1
  22. import Fancontrol.Qml 1.0 as Fancontrol
  23. import "math.js" as MoreMath
  24. import "units.js" as Units
  25. import "colors.js" as Colors
  26. Rectangle {
  27. property QtObject fan
  28. property QtObject systemdCom
  29. property QtObject tempModel
  30. property real minTemp: 40.0
  31. property real maxTemp: 90.0
  32. property int margin: 5
  33. property string unit: "°C"
  34. property real convertedMinTemp: Units.fromCelsius(minTemp, unit)
  35. property real convertedMaxTemp: Units.fromCelsius(maxTemp, unit)
  36. id: root
  37. color: "transparent"
  38. border.color: palette.windowText
  39. border.width: 2
  40. radius: 10
  41. clip: false
  42. onMinTempChanged: if (!!fan) meshCanvas.requestPaint()
  43. onMaxTempChanged: if (!!fan) meshCanvas.requestPaint()
  44. onUnitChanged: if (!!fan) meshCanvas.requestPaint()
  45. SystemPalette {
  46. id: palette
  47. colorGroup: SystemPalette.Active
  48. }
  49. SystemPalette {
  50. id: disabledPalette
  51. colorGroup: SystemPalette.Disabled
  52. }
  53. TextEdit {
  54. id: nameField
  55. anchors {
  56. left: parent.left
  57. leftMargin: margin
  58. right: parent.right
  59. rightMargin: margin
  60. top: parent.top
  61. topMargin: margin
  62. }
  63. visible: root.height >= height + margin*2
  64. text: !!fan ? fan.name : ""
  65. color: palette.text
  66. horizontalAlignment: TextEdit.AlignLeft
  67. wrapMode: TextEdit.Wrap
  68. font.bold: true
  69. font.pointSize: 14
  70. selectByMouse: true
  71. onTextChanged: {
  72. if (!!fan) {
  73. fan.name = text;
  74. }
  75. }
  76. Connections {
  77. target: fan
  78. onNameChanged: if (fan.name != nameField.text) nameField.text = fan.name
  79. }
  80. MouseArea {
  81. anchors.fill: parent
  82. cursorShape: Qt.IBeamCursor
  83. acceptedButtons: Qt.NoButton
  84. }
  85. }
  86. Item {
  87. id: graph
  88. property int fontSize: MoreMath.bound(8, height / 20 + 1, 16)
  89. property QtObject pal: !!fan ? fan.hasTemp ? palette : disabledPalette : disabledPalette
  90. property int verticalScalaCount: 6
  91. property var horIntervals: MoreMath.intervals(root.convertedMinTemp, root.convertedMaxTemp, 10)
  92. anchors {
  93. left: parent.left
  94. right: parent.right
  95. top: nameField.bottom
  96. bottom: settingsArea.top
  97. }
  98. visible: background.height > 0 && background.width > 0
  99. Item {
  100. id: verticalScala
  101. anchors {
  102. top: background.top
  103. bottom: background.bottom
  104. left: parent.left
  105. }
  106. width: MoreMath.maxWidth(children) + graph.fontSize
  107. Repeater {
  108. id: verticalRepeater
  109. model: graph.verticalScalaCount
  110. Label {
  111. x: verticalScala.width - implicitWidth - graph.fontSize / 3
  112. y: background.height - background.height / (graph.verticalScalaCount - 1) * index - graph.fontSize / 2
  113. horizontalAlignment: Text.AlignRight
  114. color: graph.pal.text
  115. text: i18n("%1\%", index * (100 / (graph.verticalScalaCount - 1)))
  116. font.pixelSize: graph.fontSize
  117. }
  118. }
  119. }
  120. Item {
  121. id: horizontalScala
  122. anchors {
  123. right: background.right
  124. bottom: parent.bottom
  125. left: background.left
  126. }
  127. height: graph.fontSize * 2
  128. Repeater {
  129. model: graph.horIntervals.length;
  130. Label {
  131. x: Math.min(horizontalScala.width, background.width / (graph.horIntervals.length - 1) * index) - width / 2
  132. y: horizontalScala.height / 2 - implicitHeight / 2
  133. color: graph.pal.text
  134. text: i18n("%1" + unit, graph.horIntervals[index])
  135. font.pixelSize: graph.fontSize
  136. }
  137. }
  138. }
  139. Rectangle {
  140. id: background
  141. property alias pal: graph.pal
  142. color: pal.light
  143. border.color: pal.text
  144. border.width: 2
  145. radius: 1
  146. anchors {
  147. top: parent.top
  148. left: verticalScala.right
  149. bottom: horizontalScala.top
  150. right: parent.right
  151. topMargin: parent.fontSize
  152. bottomMargin: 0
  153. rightMargin: parent.fontSize * 2
  154. leftMargin: 0
  155. }
  156. function scaleX(temp) {
  157. return (temp - minTemp) * width / (maxTemp - minTemp);
  158. }
  159. function scaleY(pwm) {
  160. return height - pwm * height / 255;
  161. }
  162. function scaleTemp(x) {
  163. return x / width * (maxTemp - minTemp) + minTemp;
  164. }
  165. function scalePwm(y) {
  166. return 255 - y / height * 255;
  167. }
  168. Canvas {
  169. id: bgCanvas
  170. anchors.fill: parent
  171. anchors.margins: parent.border.width
  172. renderStrategy: Canvas.Cooperative
  173. property alias pal: background.pal
  174. onPaint: {
  175. var c = bgCanvas.getContext("2d");
  176. c.clearRect(0, 0, width, height);
  177. var gradient = c.createLinearGradient(0, 0, width, 0);
  178. gradient.addColorStop(0, "rgb(0, 0, 255)");
  179. gradient.addColorStop(1, "rgb(255, 0, 0)");
  180. c.fillStyle = gradient;
  181. c.lineWidth = graph.fontSize / 3;
  182. c.strokeStyle = gradient;
  183. c.lineJoin = "round";
  184. c.beginPath();
  185. if (fanOffCheckBox.checked) {
  186. c.moveTo(stopPoint.centerX, height);
  187. } else {
  188. c.moveTo(0, stopPoint.centerY);
  189. c.lineTo(stopPoint.centerX, stopPoint.centerY);
  190. }
  191. c.lineTo(stopPoint.centerX, stopPoint.centerY);
  192. c.lineTo(maxPoint.centerX, maxPoint.centerY);
  193. c.lineTo(width, maxPoint.centerY);
  194. c.stroke();
  195. c.lineTo(width, height);
  196. if (fanOffCheckBox.checked) {
  197. c.lineTo(stopPoint.centerX, height);
  198. } else {
  199. c.lineTo(0, height);
  200. }
  201. c.fill();
  202. //blend background
  203. gradient = c.createLinearGradient(0, 0, 0, height);
  204. gradient.addColorStop(0, Colors.setAlpha(background.color, 0.5));
  205. gradient.addColorStop(1, Colors.setAlpha(background.color, 0.9));
  206. c.fillStyle = gradient;
  207. c.fill();
  208. }
  209. }
  210. Canvas {
  211. id: meshCanvas
  212. anchors.fill: parent
  213. anchors.margins: parent.border.width
  214. renderStrategy: Canvas.Cooperative
  215. property alias pal: background.pal
  216. onPaint: {
  217. var c = meshCanvas.getContext("2d");
  218. c.clearRect(0, 0, width, height);
  219. //draw mesh
  220. c.beginPath();
  221. c.strokeStyle = Colors.setAlpha(pal.text, 0.3);
  222. //horizontal lines
  223. for (var i=0; i<=100; i+=20) {
  224. var y = background.scaleY(i*2.55);
  225. if (i != 0 && i != 100) {
  226. for (var j=0; j<=width; j+=15) {
  227. c.moveTo(j, y);
  228. c.lineTo(Math.min(j+5, width), y);
  229. }
  230. }
  231. }
  232. //vertical lines
  233. if (graph.horIntervals.length > 1) {
  234. for (var i=1; i<graph.horIntervals.length; i++) {
  235. var x = background.scaleX(Units.toCelsius(graph.horIntervals[i], unit));
  236. for (var j=0; j<=height; j+=20) {
  237. c.moveTo(x, j);
  238. c.lineTo(x, Math.min(j+5, height));
  239. }
  240. }
  241. }
  242. c.stroke();
  243. }
  244. }
  245. StatusPoint {
  246. id: currentPwm
  247. size: graph.fontSize
  248. visible: background.contains(center) && !!fan && fan.hasTemp
  249. fan: root.fan
  250. unit: root.unit
  251. }
  252. PwmPoint {
  253. id: stopPoint
  254. color: !!fan ? fan.hasTemp ? "blue" : Qt.tint(graph.pal.light, Qt.rgba(0, 0, 1, 0.5)) : "transparent"
  255. size: graph.fontSize
  256. unit: root.unit
  257. enabled: !!fan ? fan.hasTemp : false
  258. drag.maximumX: Math.min(background.scaleX(background.scaleTemp(maxPoint.x)-1), maxPoint.x-1)
  259. drag.minimumY: Math.max(background.scaleY(background.scalePwm(maxPoint.y)-1), maxPoint.y+1)
  260. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(minTemp, fan.minTemp, maxTemp)) - width/2 : -width/2
  261. y: !!fan && fan.hasTemp ? background.scaleY(fan.minStop) - height/2 : -height/2
  262. drag.onActiveChanged: {
  263. if (!drag.active && !!fan) {
  264. fan.minStop = Math.round(background.scalePwm(centerY));
  265. fan.minTemp = Math.round(background.scaleTemp(centerX));
  266. if (!fanOffCheckBox.checked) fan.minPwm = fan.minStop;
  267. }
  268. }
  269. onPositionChanged: {
  270. var left = fanOffCheckBox.checked ? x : 0;
  271. var width = maxPoint.x - left;
  272. var height = y - maxPoint.y;
  273. bgCanvas.markDirty(Qt.rect(left, maxPoint.y, width, height));
  274. }
  275. }
  276. PwmPoint {
  277. id: maxPoint
  278. color: !!fan ? fan.hasTemp ? "red" : Qt.tint(graph.pal.light, Qt.rgba(1, 0, 0, 0.5)) : "transparent"
  279. size: graph.fontSize
  280. unit: root.unit
  281. enabled: !!fan ? fan.hasTemp : false
  282. drag.minimumX: Math.max(background.scaleX(background.scaleTemp(stopPoint.x)+1), stopPoint.x+1)
  283. drag.maximumY: Math.min(background.scaleY(background.scalePwm(stopPoint.y)+1), stopPoint.y-1)
  284. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(minTemp, fan.maxTemp, maxTemp)) - width/2 : background.width - width/2
  285. y: !!fan && fan.hasTemp ? background.scaleY(fan.maxPwm) - height/2 : -height/2
  286. drag.onActiveChanged: {
  287. if (!drag.active) {
  288. fan.maxPwm = Math.round(background.scalePwm(centerY));
  289. fan.maxTemp = Math.round(background.scaleTemp(centerX));
  290. }
  291. }
  292. onPositionChanged: {
  293. var width = x - stopPoint.x;
  294. var height = stopPoint.y - y;
  295. bgCanvas.markDirty(Qt.rect(stopPoint.x, y, width, height));
  296. }
  297. }
  298. }
  299. }
  300. ColumnLayout {
  301. property int padding: 10
  302. id: settingsArea
  303. anchors {
  304. left: parent.left
  305. leftMargin: padding
  306. right: parent.right
  307. rightMargin: padding
  308. bottom: parent.bottom
  309. bottomMargin: padding
  310. }
  311. visible: root.height >= nameField.height + height + 2*margin
  312. clip: true
  313. spacing: 2
  314. RowLayout {
  315. CheckBox {
  316. id: hasTempCheckBox
  317. text: i18n("Controlled by:")
  318. checked: !!fan ? fan.hasTemp : false
  319. Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
  320. onCheckedChanged: {
  321. if (!!fan) {
  322. fan.hasTemp = checked;
  323. if (checked && !!tempModel.temps[tempBox.currentIndex]) {
  324. fan.temp = tempModel.temps[tempBox.currentIndex];
  325. }
  326. bgCanvas.requestPaint();
  327. }
  328. }
  329. Connections {
  330. target: root
  331. onFanChanged: hasTempCheckBox.checked = !!fan ? fan.hasTemp : false
  332. }
  333. Connections {
  334. target: fan
  335. onHasTempChanged: hasTempCheckBox.checked = fan.hasTemp
  336. }
  337. }
  338. RowLayout {
  339. ComboBox {
  340. id: tempBox
  341. Layout.fillWidth: true
  342. model: tempModel
  343. textRole: "display"
  344. enabled: hasTempCheckBox.checked
  345. onCurrentIndexChanged: {
  346. if (hasTempCheckBox.checked)
  347. fan.temp = tempModel.temps[currentIndex];
  348. }
  349. }
  350. Connections {
  351. target: root
  352. onFanChanged: if (!!fan && fan.hasTemp) tempBox.currentIndex = tempModel.temps.indexOf(fan.temp)
  353. }
  354. Connections {
  355. target: fan
  356. onTempChanged: if (fan.hasTemp) tempBox.currentIndex = tempModel.temps.indexOf(fan.temp)
  357. }
  358. }
  359. }
  360. CheckBox {
  361. id: fanOffCheckBox
  362. text: i18n("Turn Fan off if temp < MINTEMP")
  363. enabled: hasTempCheckBox.checked
  364. checked: !!fan ? fan.minPwm == 0 : false
  365. onCheckedChanged: {
  366. if (!!fan) {
  367. fan.minPwm = checked ? 0 : fan.minStop;
  368. bgCanvas.markDirty(Qt.rect(0, 0, stopPoint.x, stopPoint.y));
  369. }
  370. }
  371. Connections {
  372. target: root
  373. onFanChanged: if (!!fan) fanOffCheckBox.checked = fan.minPwm == 0
  374. }
  375. Connections {
  376. target: fan
  377. onMinPwmChanged: fanOffCheckBox.checked = fan.minPwm == 0
  378. }
  379. }
  380. RowLayout {
  381. enabled: fanOffCheckBox.checked && fanOffCheckBox.enabled
  382. Label {
  383. text: i18n("Pwm value for fan to start:")
  384. Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
  385. renderType: Text.NativeRendering
  386. }
  387. SpinBox {
  388. id: minStartInput
  389. Layout.fillWidth: true
  390. minimumValue: 1
  391. maximumValue: 100
  392. decimals: 1
  393. value: !!fan ? Math.round(fan.minStart / 2.55) : 0
  394. suffix: i18n("%")
  395. onValueChanged: {
  396. if (!!fan) {
  397. fan.minStart = Math.round(value * 2.55)
  398. }
  399. }
  400. }
  401. }
  402. RowLayout {
  403. visible: !!systemdCom
  404. Item {
  405. Layout.fillWidth: true
  406. }
  407. Button {
  408. id: testButton
  409. text: !!fan ? fan.testing ? i18n("Abort test") : i18n("Test start and stop values") : ""
  410. iconName: "dialog-password"
  411. anchors.right: parent.right
  412. onClicked: {
  413. if (fan.testing) {
  414. fan.abortTest();
  415. } else {
  416. minStartInput.value = Qt.binding(function() { return Math.round(fan.minStart / 2.55) });
  417. fan.test();
  418. }
  419. }
  420. }
  421. }
  422. }
  423. }