FanItem.qml 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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: Fancontrol.base.minTemp
  31. property real maxTemp: Fancontrol.base.maxTemp
  32. property int margin: 5
  33. property string unit: Fancontrol.base.unit
  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: background.scaleX(Units.toCelsius(graph.horIntervals[index], unit)) - 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. }
  251. PwmPoint {
  252. id: stopPoint
  253. color: !!fan ? fan.hasTemp ? "blue" : Qt.tint(graph.pal.light, Qt.rgba(0, 0, 1, 0.5)) : "transparent"
  254. size: graph.fontSize
  255. enabled: !!fan ? fan.hasTemp : false
  256. drag.maximumX: Math.min(background.scaleX(background.scaleTemp(maxPoint.x)-1), maxPoint.x-1)
  257. drag.minimumY: Math.max(background.scaleY(background.scalePwm(maxPoint.y)-1), maxPoint.y+1)
  258. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(root.minTemp, fan.minTemp, root.maxTemp)) - width/2 : -width/2
  259. y: !!fan && fan.hasTemp ? background.scaleY(fan.minStop) - height/2 : -height/2
  260. temp: !!fan && fan.hasTemp ? drag.active ? background.scaleTemp(centerX) : fan.minTemp : root.minTemp
  261. pwm: !!fan && fan.hasTemp ? drag.active ? background.scalePwm(centerY) : fan.minStop : 255
  262. drag.onActiveChanged: {
  263. if (!drag.active) {
  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. enabled: !!fan ? fan.hasTemp : false
  281. drag.minimumX: Math.max(background.scaleX(background.scaleTemp(stopPoint.x)+1), stopPoint.x+1)
  282. drag.maximumY: Math.min(background.scaleY(background.scalePwm(stopPoint.y)+1), stopPoint.y-1)
  283. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(root.minTemp, fan.maxTemp, root.maxTemp)) - width/2 : background.width - width/2
  284. y: !!fan && fan.hasTemp ? background.scaleY(fan.maxPwm) - height/2 : -height/2
  285. temp: !!fan && fan.hasTemp ? drag.active ? background.scaleTemp(centerX) : fan.maxTemp : root.maxTemp
  286. pwm: !!fan && fan.hasTemp ? drag.active ? background.scalePwm(centerY) : fan.maxPwm : 255
  287. drag.onActiveChanged: {
  288. if (!drag.active) {
  289. fan.maxPwm = Math.round(background.scalePwm(centerY));
  290. fan.maxTemp = Math.round(background.scaleTemp(centerX));
  291. }
  292. }
  293. onPositionChanged: {
  294. var width = x - stopPoint.x;
  295. var height = stopPoint.y - y;
  296. bgCanvas.markDirty(Qt.rect(stopPoint.x, y, width, height));
  297. }
  298. }
  299. }
  300. }
  301. ColumnLayout {
  302. property int padding: 10
  303. id: settingsArea
  304. anchors {
  305. left: parent.left
  306. leftMargin: padding
  307. right: parent.right
  308. rightMargin: padding
  309. bottom: parent.bottom
  310. bottomMargin: padding
  311. }
  312. visible: root.height >= nameField.height + height + 2*margin
  313. clip: true
  314. spacing: 2
  315. RowLayout {
  316. CheckBox {
  317. id: hasTempCheckBox
  318. text: i18n("Controlled by:")
  319. checked: !!fan ? fan.hasTemp : false
  320. Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
  321. onCheckedChanged: {
  322. if (!!fan) {
  323. fan.hasTemp = checked;
  324. if (checked && !!tempModel.temps[tempBox.currentIndex]) {
  325. fan.temp = tempModel.temps[tempBox.currentIndex];
  326. }
  327. bgCanvas.requestPaint();
  328. }
  329. }
  330. Connections {
  331. target: root
  332. onFanChanged: hasTempCheckBox.checked = !!fan ? fan.hasTemp : false
  333. }
  334. Connections {
  335. target: fan
  336. onHasTempChanged: hasTempCheckBox.checked = fan.hasTemp
  337. }
  338. }
  339. RowLayout {
  340. ComboBox {
  341. id: tempBox
  342. Layout.fillWidth: true
  343. model: tempModel
  344. textRole: "display"
  345. enabled: hasTempCheckBox.checked
  346. onCurrentIndexChanged: {
  347. if (hasTempCheckBox.checked)
  348. fan.temp = tempModel.temps[currentIndex];
  349. }
  350. }
  351. Connections {
  352. target: root
  353. onFanChanged: if (!!fan && fan.hasTemp) tempBox.currentIndex = tempModel.temps.indexOf(fan.temp)
  354. }
  355. Connections {
  356. target: fan
  357. onTempChanged: if (fan.hasTemp) tempBox.currentIndex = tempModel.temps.indexOf(fan.temp)
  358. }
  359. }
  360. }
  361. CheckBox {
  362. id: fanOffCheckBox
  363. text: i18n("Turn Fan off if temp < MINTEMP")
  364. enabled: hasTempCheckBox.checked
  365. checked: !!fan ? fan.minPwm == 0 : false
  366. onCheckedChanged: {
  367. if (!!fan) {
  368. fan.minPwm = checked ? 0 : fan.minStop;
  369. bgCanvas.markDirty(Qt.rect(0, 0, stopPoint.x, stopPoint.y));
  370. }
  371. }
  372. Connections {
  373. target: root
  374. onFanChanged: if (!!fan) fanOffCheckBox.checked = fan.minPwm == 0
  375. }
  376. Connections {
  377. target: fan
  378. onMinPwmChanged: fanOffCheckBox.checked = fan.minPwm == 0
  379. }
  380. }
  381. RowLayout {
  382. enabled: fanOffCheckBox.checked && fanOffCheckBox.enabled
  383. Label {
  384. text: i18n("Pwm value for fan to start:")
  385. Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
  386. renderType: Text.NativeRendering
  387. }
  388. SpinBox {
  389. id: minStartInput
  390. Layout.fillWidth: true
  391. minimumValue: 1
  392. maximumValue: 100
  393. decimals: 1
  394. value: !!fan ? Math.round(fan.minStart / 2.55) : 0
  395. suffix: i18n("%")
  396. onValueChanged: {
  397. if (!!fan) {
  398. fan.minStart = Math.round(value * 2.55)
  399. }
  400. }
  401. }
  402. }
  403. RowLayout {
  404. visible: !!systemdCom
  405. Item {
  406. Layout.fillWidth: true
  407. }
  408. Button {
  409. id: testButton
  410. text: !!fan ? fan.testing ? i18n("Abort test") : i18n("Test start and stop values") : ""
  411. iconName: "dialog-password"
  412. anchors.right: parent.right
  413. onClicked: {
  414. if (fan.testing) {
  415. fan.abortTest();
  416. } else {
  417. minStartInput.value = Qt.binding(function() { return Math.round(fan.minStart / 2.55) });
  418. fan.test();
  419. }
  420. }
  421. }
  422. }
  423. }
  424. }