FanItem.qml 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. /*
  2. * Copyright (C) 2015 Malte Veerman <malte.veerman@gmail.com>
  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: if (!!fan && fan.name != text) fan.name = text
  72. Connections {
  73. target: fan
  74. onNameChanged: if (fan.name != nameField.text) nameField.text = fan.name
  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 int verticalScalaCount: 6
  87. property var horIntervals: MoreMath.intervals(root.convertedMinTemp, root.convertedMaxTemp, 10)
  88. anchors {
  89. left: parent.left
  90. right: parent.right
  91. top: nameField.bottom
  92. bottom: settingsArea.top
  93. }
  94. visible: background.height > 0 && background.width > 0
  95. Item {
  96. id: verticalScala
  97. anchors {
  98. top: background.top
  99. bottom: background.bottom
  100. left: parent.left
  101. }
  102. width: MoreMath.maxWidth(children) + graph.fontSize
  103. Repeater {
  104. id: verticalRepeater
  105. model: graph.verticalScalaCount
  106. Label {
  107. x: verticalScala.width - implicitWidth - graph.fontSize / 3
  108. y: background.height - background.height / (graph.verticalScalaCount - 1) * index - graph.fontSize / 2
  109. horizontalAlignment: Text.AlignRight
  110. color: graph.pal.text
  111. text: i18n("%1\%", index * (100 / (graph.verticalScalaCount - 1)))
  112. font.pixelSize: graph.fontSize
  113. }
  114. }
  115. }
  116. Item {
  117. id: horizontalScala
  118. anchors {
  119. right: background.right
  120. bottom: parent.bottom
  121. left: background.left
  122. }
  123. height: graph.fontSize * 2
  124. Repeater {
  125. model: graph.horIntervals.length;
  126. Label {
  127. x: background.scaleX(Units.toCelsius(graph.horIntervals[index], unit)) - width/2
  128. y: horizontalScala.height / 2 - implicitHeight / 2
  129. color: graph.pal.text
  130. text: i18n("%1" + unit, graph.horIntervals[index])
  131. font.pixelSize: graph.fontSize
  132. }
  133. }
  134. }
  135. Rectangle {
  136. id: background
  137. property alias pal: graph.pal
  138. color: pal.light
  139. border.color: pal.text
  140. border.width: 2
  141. radius: 1
  142. anchors {
  143. top: parent.top
  144. left: verticalScala.right
  145. bottom: horizontalScala.top
  146. right: parent.right
  147. topMargin: parent.fontSize
  148. bottomMargin: 0
  149. rightMargin: parent.fontSize * 2
  150. leftMargin: 0
  151. }
  152. function scaleX(temp) {
  153. return (temp - minTemp) * width / (maxTemp - minTemp);
  154. }
  155. function scaleY(pwm) {
  156. return height - pwm * height / 255;
  157. }
  158. function scaleTemp(x) {
  159. return x / width * (maxTemp - minTemp) + minTemp;
  160. }
  161. function scalePwm(y) {
  162. return 255 - y / height * 255;
  163. }
  164. Canvas {
  165. id: bgCanvas
  166. anchors.fill: parent
  167. anchors.margins: parent.border.width
  168. renderStrategy: Canvas.Cooperative
  169. property alias pal: background.pal
  170. onPaint: {
  171. var c = bgCanvas.getContext("2d");
  172. c.clearRect(0, 0, width, height);
  173. var gradient = c.createLinearGradient(0, 0, width, 0);
  174. gradient.addColorStop(0, "rgb(0, 0, 255)");
  175. gradient.addColorStop(1, "rgb(255, 0, 0)");
  176. c.fillStyle = gradient;
  177. c.lineWidth = graph.fontSize / 3;
  178. c.strokeStyle = gradient;
  179. c.lineJoin = "round";
  180. c.beginPath();
  181. if (fanOffCheckBox.checked) {
  182. c.moveTo(stopPoint.centerX, height);
  183. } else {
  184. c.moveTo(0, stopPoint.centerY);
  185. c.lineTo(stopPoint.centerX, stopPoint.centerY);
  186. }
  187. c.lineTo(stopPoint.centerX, stopPoint.centerY);
  188. c.lineTo(maxPoint.centerX, maxPoint.centerY);
  189. c.lineTo(width, maxPoint.centerY);
  190. c.stroke();
  191. c.lineTo(width, height);
  192. if (fanOffCheckBox.checked) {
  193. c.lineTo(stopPoint.centerX, height);
  194. } else {
  195. c.lineTo(0, height);
  196. }
  197. c.fill();
  198. //blend background
  199. gradient = c.createLinearGradient(0, 0, 0, height);
  200. gradient.addColorStop(0, Colors.setAlpha(background.color, 0.5));
  201. gradient.addColorStop(1, Colors.setAlpha(background.color, 0.9));
  202. c.fillStyle = gradient;
  203. c.fill();
  204. }
  205. }
  206. Canvas {
  207. id: meshCanvas
  208. anchors.fill: parent
  209. anchors.margins: parent.border.width
  210. renderStrategy: Canvas.Cooperative
  211. property alias pal: background.pal
  212. onPaint: {
  213. var c = meshCanvas.getContext("2d");
  214. c.clearRect(0, 0, width, height);
  215. //draw mesh
  216. c.beginPath();
  217. c.strokeStyle = Colors.setAlpha(pal.text, 0.3);
  218. //horizontal lines
  219. for (var i=0; i<=100; i+=20) {
  220. var y = background.scaleY(i*2.55);
  221. if (i != 0 && i != 100) {
  222. for (var j=0; j<=width; j+=15) {
  223. c.moveTo(j, y);
  224. c.lineTo(Math.min(j+5, width), y);
  225. }
  226. }
  227. }
  228. //vertical lines
  229. if (graph.horIntervals.length > 1) {
  230. for (var i=1; i<graph.horIntervals.length; i++) {
  231. var x = background.scaleX(Units.toCelsius(graph.horIntervals[i], unit));
  232. for (var j=0; j<=height; j+=20) {
  233. c.moveTo(x, j);
  234. c.lineTo(x, Math.min(j+5, height));
  235. }
  236. }
  237. }
  238. c.stroke();
  239. }
  240. }
  241. StatusPoint {
  242. id: currentPwm
  243. size: graph.fontSize
  244. visible: background.contains(center) && !!fan && fan.hasTemp
  245. fan: root.fan
  246. }
  247. PwmPoint {
  248. id: stopPoint
  249. color: !!fan ? fan.hasTemp ? "blue" : Qt.tint(graph.pal.light, Qt.rgba(0, 0, 1, 0.5)) : "transparent"
  250. size: graph.fontSize
  251. enabled: !!fan ? fan.hasTemp : false
  252. drag.maximumX: Math.min(background.scaleX(background.scaleTemp(maxPoint.x)-1), maxPoint.x-1)
  253. drag.minimumY: Math.max(background.scaleY(background.scalePwm(maxPoint.y)-1), maxPoint.y+1)
  254. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(root.minTemp, fan.minTemp, root.maxTemp)) - width/2 : -width/2
  255. y: !!fan && fan.hasTemp ? background.scaleY(fan.minStop) - height/2 : -height/2
  256. temp: !!fan && fan.hasTemp ? drag.active ? background.scaleTemp(centerX) : fan.minTemp : root.minTemp
  257. pwm: !!fan && fan.hasTemp ? drag.active ? background.scalePwm(centerY) : fan.minStop : 255
  258. drag.onActiveChanged: {
  259. if (!drag.active) {
  260. fan.minStop = Math.round(background.scalePwm(centerY));
  261. fan.minTemp = Math.round(background.scaleTemp(centerX));
  262. if (!fanOffCheckBox.checked) fan.minPwm = fan.minStop;
  263. }
  264. }
  265. onPositionChanged: {
  266. var left = fanOffCheckBox.checked ? x : 0;
  267. var width = maxPoint.x - left;
  268. var height = y - maxPoint.y;
  269. bgCanvas.markDirty(Qt.rect(left, maxPoint.y, width, height));
  270. }
  271. }
  272. PwmPoint {
  273. id: maxPoint
  274. color: !!fan ? fan.hasTemp ? "red" : Qt.tint(graph.pal.light, Qt.rgba(1, 0, 0, 0.5)) : "transparent"
  275. size: graph.fontSize
  276. enabled: !!fan ? fan.hasTemp : false
  277. drag.minimumX: Math.max(background.scaleX(background.scaleTemp(stopPoint.x)+1), stopPoint.x+1)
  278. drag.maximumY: Math.min(background.scaleY(background.scalePwm(stopPoint.y)+1), stopPoint.y-1)
  279. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(root.minTemp, fan.maxTemp, root.maxTemp)) - width/2 : background.width - width/2
  280. y: !!fan && fan.hasTemp ? background.scaleY(fan.maxPwm) - height/2 : -height/2
  281. temp: !!fan && fan.hasTemp ? drag.active ? background.scaleTemp(centerX) : fan.maxTemp : root.maxTemp
  282. pwm: !!fan && fan.hasTemp ? drag.active ? background.scalePwm(centerY) : fan.maxPwm : 255
  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: 0
  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. text: !!fan ? fan.testing ? i18n("Abort test") : i18n("Test start and stop values") : ""
  407. iconName: "dialog-password"
  408. anchors.right: parent.right
  409. onClicked: {
  410. if (fan.testing) {
  411. fan.abortTest();
  412. } else {
  413. minStartInput.value = Qt.binding(function() { return Math.round(fan.minStart / 2.55) });
  414. fan.test();
  415. }
  416. }
  417. }
  418. }
  419. }
  420. }