FanItem.qml 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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 int unit: 0
  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.requestRepaint()
  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. MouseArea {
  77. anchors.fill: parent
  78. cursorShape: Qt.IBeamCursor
  79. acceptedButtons: Qt.NoButton
  80. }
  81. }
  82. Item {
  83. id: graph
  84. property int fontSize: MoreMath.bound(8, height / 20 + 1, 16)
  85. property QtObject pal: !!fan ? fan.hasTemp ? palette : disabledPalette : disabledPalette
  86. property string suffix: (root.unit == 0) ? "°C" : (root.unit == 1) ? "K" : "°F"
  87. property int verticalScalaCount: 6
  88. property var horIntervals: MoreMath.intervals(root.convertedMinTemp, root.convertedMaxTemp, 10)
  89. anchors {
  90. left: parent.left
  91. right: parent.right
  92. top: nameField.bottom
  93. bottom: settingsArea.top
  94. }
  95. visible: background.height > 0 && background.width > 0
  96. Item {
  97. id: verticalScala
  98. anchors {
  99. top: background.top
  100. bottom: background.bottom
  101. left: parent.left
  102. }
  103. width: MoreMath.maxWidth(children) + graph.fontSize
  104. Repeater {
  105. id: verticalRepeater
  106. model: graph.verticalScalaCount
  107. Label {
  108. x: verticalScala.width - implicitWidth - graph.fontSize / 3
  109. y: background.height - background.height / (graph.verticalScalaCount - 1) * index - graph.fontSize / 2
  110. horizontalAlignment: Text.AlignRight
  111. color: graph.pal.text
  112. text: index * (100 / (graph.verticalScalaCount - 1)) + "%"
  113. font.pixelSize: graph.fontSize
  114. }
  115. }
  116. }
  117. Item {
  118. id: horizontalScala
  119. anchors {
  120. right: background.right
  121. bottom: parent.bottom
  122. left: background.left
  123. }
  124. height: graph.fontSize * 2
  125. Repeater {
  126. model: graph.horIntervals.length;
  127. Label {
  128. x: Math.min(horizontalScala.width, background.width / (graph.horIntervals.length - 1) * index) - width / 2
  129. y: horizontalScala.height / 2 - implicitHeight / 2
  130. color: graph.pal.text
  131. text: graph.horIntervals[index] + graph.suffix
  132. font.pixelSize: graph.fontSize
  133. }
  134. }
  135. }
  136. Rectangle {
  137. id: background
  138. property alias pal: graph.pal
  139. color: pal.light
  140. border.color: pal.text
  141. border.width: 2
  142. radius: 1
  143. anchors {
  144. top: parent.top
  145. left: verticalScala.right
  146. bottom: horizontalScala.top
  147. right: parent.right
  148. topMargin: parent.fontSize
  149. bottomMargin: 0
  150. rightMargin: parent.fontSize * 2
  151. leftMargin: 0
  152. }
  153. function scaleX(temp) {
  154. return (temp - minTemp) * width / (maxTemp - minTemp);
  155. }
  156. function scaleY(pwm) {
  157. return height - pwm * height / 255;
  158. }
  159. function scaleTemp(x) {
  160. return x / width * (maxTemp - minTemp) + minTemp;
  161. }
  162. function scalePwm(y) {
  163. return 255 - y / height * 255;
  164. }
  165. Canvas {
  166. id: bgCanvas
  167. anchors.fill: parent
  168. anchors.margins: parent.border.width
  169. renderStrategy: Canvas.Cooperative
  170. property alias pal: background.pal
  171. onPaint: {
  172. var c = bgCanvas.getContext("2d");
  173. c.clearRect(0, 0, width, height);
  174. var gradient = c.createLinearGradient(0, 0, width, 0);
  175. gradient.addColorStop(0, "rgb(0, 0, 255)");
  176. gradient.addColorStop(1, "rgb(255, 0, 0)");
  177. c.fillStyle = gradient;
  178. c.lineWidth = graph.fontSize / 3;
  179. c.strokeStyle = gradient;
  180. c.lineJoin = "round";
  181. c.beginPath();
  182. if (fanOffCheckBox.checked) {
  183. c.moveTo(stopPoint.centerX, height);
  184. } else {
  185. c.moveTo(0, stopPoint.centerY);
  186. c.lineTo(stopPoint.centerX, stopPoint.centerY);
  187. }
  188. c.lineTo(stopPoint.centerX, stopPoint.centerY);
  189. c.lineTo(maxPoint.centerX, maxPoint.centerY);
  190. c.lineTo(width, maxPoint.centerY);
  191. c.stroke();
  192. c.lineTo(width, height);
  193. if (fanOffCheckBox.checked) {
  194. c.lineTo(stopPoint.centerX, height);
  195. } else {
  196. c.lineTo(0, height);
  197. }
  198. c.fill();
  199. //blend background
  200. gradient = c.createLinearGradient(0, 0, 0, height);
  201. gradient.addColorStop(0, Colors.setAlpha(background.color, 0.5));
  202. gradient.addColorStop(1, Colors.setAlpha(background.color, 0.9));
  203. c.fillStyle = gradient;
  204. c.fill();
  205. }
  206. }
  207. Canvas {
  208. id: meshCanvas
  209. anchors.fill: parent
  210. anchors.margins: parent.border.width
  211. renderStrategy: Canvas.Cooperative
  212. property alias pal: background.pal
  213. onPaint: {
  214. var c = meshCanvas.getContext("2d");
  215. c.clearRect(0, 0, width, height);
  216. //draw mesh
  217. c.beginPath();
  218. c.strokeStyle = Colors.setAlpha(pal.text, 0.3);
  219. //horizontal lines
  220. for (var i=0; i<=100; i+=20) {
  221. var y = background.scaleY(i*2.55);
  222. if (i != 0 && i != 100) {
  223. for (var j=0; j<=width; j+=15) {
  224. c.moveTo(j, y);
  225. c.lineTo(Math.min(j+5, width), y);
  226. }
  227. }
  228. }
  229. //vertical lines
  230. if (graph.horIntervals.length > 1) {
  231. for (var i=1; i<graph.horIntervals.length; i++) {
  232. var x = background.scaleX(Units.toCelsius(graph.horIntervals[i], unit));
  233. for (var j=0; j<=height; j+=20) {
  234. c.moveTo(x, j);
  235. c.lineTo(x, Math.min(j+5, height));
  236. }
  237. }
  238. }
  239. c.stroke();
  240. }
  241. }
  242. StatusPoint {
  243. id: currentPwm
  244. size: graph.fontSize
  245. visible: background.contains(center) && !!fan && fan.hasTemp
  246. fan: root.fan
  247. unit: root.unit
  248. }
  249. PwmPoint {
  250. id: stopPoint
  251. color: !!fan ? fan.hasTemp ? "blue" : Qt.tint(graph.pal.light, Qt.rgba(0, 0, 1, 0.5)) : "transparent"
  252. size: graph.fontSize
  253. unit: root.unit
  254. enabled: !!fan ? fan.hasTemp : false
  255. drag.maximumX: Math.min(background.scaleX(background.scaleTemp(maxPoint.x)-1), maxPoint.x-1)
  256. drag.minimumY: Math.max(background.scaleY(background.scalePwm(maxPoint.y)-1), maxPoint.y+1)
  257. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(minTemp, fan.minTemp, maxTemp)) - width/2 : -width/2
  258. y: !!fan && fan.hasTemp ? background.scaleY(fan.minStop) - height/2 : -height/2
  259. drag.onActiveChanged: {
  260. if (!drag.active && !!fan) {
  261. fan.minStop = Math.round(background.scalePwm(centerY));
  262. fan.minTemp = Math.round(background.scaleTemp(centerX));
  263. if (!fanOffCheckBox.checked) fan.minPwm = fan.minStop;
  264. }
  265. }
  266. onPositionChanged: {
  267. var left = fanOffCheckBox.checked ? x : 0;
  268. var width = maxPoint.x - left;
  269. var height = y - maxPoint.y;
  270. bgCanvas.markDirty(Qt.rect(left, maxPoint.y, width, height));
  271. }
  272. }
  273. PwmPoint {
  274. id: maxPoint
  275. color: !!fan ? fan.hasTemp ? "red" : Qt.tint(graph.pal.light, Qt.rgba(1, 0, 0, 0.5)) : "transparent"
  276. size: graph.fontSize
  277. unit: root.unit
  278. enabled: !!fan ? fan.hasTemp : false
  279. drag.minimumX: Math.max(background.scaleX(background.scaleTemp(stopPoint.x)+1), stopPoint.x+1)
  280. drag.maximumY: Math.min(background.scaleY(background.scalePwm(stopPoint.y)+1), stopPoint.y-1)
  281. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(minTemp, fan.maxTemp, maxTemp)) - width/2 : background.width - width/2
  282. y: !!fan && fan.hasTemp ? background.scaleY(fan.maxPwm) - height/2 : -height/2
  283. drag.onActiveChanged: {
  284. if (!drag.active) {
  285. fan.maxPwm = Math.round(background.scalePwm(centerY));
  286. fan.maxTemp = Math.round(background.scaleTemp(centerX));
  287. }
  288. }
  289. onPositionChanged: {
  290. var width = x - stopPoint.x;
  291. var height = stopPoint.y - y;
  292. bgCanvas.markDirty(Qt.rect(stopPoint.x, y, width, height));
  293. }
  294. }
  295. }
  296. }
  297. ColumnLayout {
  298. property int padding: 10
  299. id: settingsArea
  300. anchors {
  301. left: parent.left
  302. leftMargin: padding
  303. right: parent.right
  304. rightMargin: padding
  305. bottom: parent.bottom
  306. bottomMargin: padding
  307. }
  308. visible: root.height >= nameField.height + height + 2*margin
  309. clip: true
  310. spacing: 2
  311. RowLayout {
  312. CheckBox {
  313. id: hasTempCheckBox
  314. text: i18n("Controlled by:")
  315. checked: !!fan ? fan.hasTemp : false
  316. Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
  317. onCheckedChanged: {
  318. if (!!fan) {
  319. fan.hasTemp = checked;
  320. if (checked && !!tempModel.temps[tempBox.currentIndex]) {
  321. fan.temp = tempModel.temps[tempBox.currentIndex];
  322. }
  323. bgCanvas.requestPaint();
  324. }
  325. }
  326. Connections {
  327. target: root
  328. onFanChanged: hasTempCheckBox.checked = !!fan ? fan.hasTemp : false
  329. }
  330. Connections {
  331. target: fan
  332. onHasTempChanged: hasTempCheckBox.checked = fan.hasTemp
  333. }
  334. }
  335. RowLayout {
  336. ComboBox {
  337. id: tempBox
  338. Layout.fillWidth: true
  339. model: tempModel
  340. textRole: "display"
  341. enabled: hasTempCheckBox.checked
  342. onCurrentIndexChanged: {
  343. if (hasTempCheckBox.checked)
  344. fan.temp = tempModel.temps[currentIndex];
  345. }
  346. }
  347. Connections {
  348. target: root
  349. onFanChanged: if (!!fan && fan.hasTemp) tempBox.currentIndex = tempModel.temps.indexOf(fan.temp)
  350. }
  351. Connections {
  352. target: fan
  353. onTempChanged: if (fan.hasTemp) tempBox.currentIndex = tempModel.temps.indexOf(fan.temp)
  354. }
  355. }
  356. }
  357. CheckBox {
  358. id: fanOffCheckBox
  359. text: i18n("Turn Fan off if temp < MINTEMP")
  360. enabled: hasTempCheckBox.checked
  361. checked: !!fan ? fan.minPwm == 0 : false
  362. onCheckedChanged: {
  363. if (!!fan) {
  364. fan.minPwm = checked ? 0 : fan.minStop;
  365. bgCanvas.markDirty(Qt.rect(0, 0, stopPoint.x, stopPoint.y));
  366. }
  367. }
  368. Connections {
  369. target: root
  370. onFanChanged: if (!!fan) fanOffCheckBox.checked = fan.minPwm == 0
  371. }
  372. Connections {
  373. target: fan
  374. onMinPwmChanged: fanOffCheckBox.checked = fan.minPwm == 0
  375. }
  376. }
  377. RowLayout {
  378. enabled: fanOffCheckBox.checked && fanOffCheckBox.enabled
  379. Label {
  380. text: i18n("Pwm value for fan to start:")
  381. Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
  382. renderType: Text.NativeRendering
  383. }
  384. SpinBox {
  385. id: minStartInput
  386. Layout.fillWidth: true
  387. minimumValue: 1
  388. maximumValue: 100
  389. decimals: 1
  390. value: !!fan ? Math.round(fan.minStart / 2.55) : 0
  391. suffix: i18n("%")
  392. onValueChanged: {
  393. if (!!fan) {
  394. fan.minStart = Math.round(value * 2.55)
  395. }
  396. }
  397. }
  398. }
  399. RowLayout {
  400. visible: systemdCom
  401. Item {
  402. Layout.fillWidth: true
  403. }
  404. Button {
  405. id: testButton
  406. property bool reactivateAfterTesting
  407. text: !!fan ? fan.testing ? i18n("Abort test") : i18n("Test start and stop values") : ""
  408. iconName: "dialog-password"
  409. anchors.right: parent.right
  410. onClicked: {
  411. if (fan.testing) {
  412. systemdCom.serviceActive = reactivateAfterTesting;
  413. fan.abortTest();
  414. } else {
  415. reactivateAfterTesting = systemdCom.serviceActive;
  416. systemdCom.serviceActive = false;
  417. minStartInput.value = Qt.binding(function() { return Math.round(fan.minStart / 2.55) });
  418. fan.test();
  419. }
  420. }
  421. Connections {
  422. target: fan
  423. onTestStatusChanged: if (fan.testStatus === Fancontrol.PwmFan.Finished && testButton.reactivateAfterTesting) systemdCom.serviceActive = true
  424. }
  425. }
  426. }
  427. }
  428. }