FanItem.qml 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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 org.kde.kirigami 2.4 as Kirigami
  23. import Fancontrol.Qml 1.0 as Fancontrol
  24. import "math.js" as MoreMath
  25. import "units.js" as Units
  26. import "colors.js" as Colors
  27. Rectangle {
  28. property QtObject fan
  29. property QtObject systemdCom
  30. property QtObject tempModel
  31. property real minTemp: Fancontrol.Base.minTemp
  32. property real maxTemp: Fancontrol.Base.maxTemp
  33. property int margin: Kirigami.Units.smallSpacing
  34. property string unit: Fancontrol.Base.unit
  35. property real convertedMinTemp: Units.fromCelsius(minTemp, unit)
  36. property real convertedMaxTemp: Units.fromCelsius(maxTemp, unit)
  37. id: root
  38. color: "transparent"
  39. border.color: palette.windowText
  40. border.width: 2
  41. radius: Kirigami.Units.smallSpacing
  42. clip: false
  43. onMinTempChanged: if (!!fan) meshCanvas.requestPaint()
  44. onMaxTempChanged: if (!!fan) meshCanvas.requestPaint()
  45. onUnitChanged: if (!!fan) meshCanvas.requestPaint()
  46. SystemPalette {
  47. id: palette
  48. colorGroup: SystemPalette.Active
  49. }
  50. SystemPalette {
  51. id: disabledPalette
  52. colorGroup: SystemPalette.Disabled
  53. }
  54. TextEdit {
  55. id: nameField
  56. anchors {
  57. left: parent.left
  58. leftMargin: margin
  59. right: parent.right
  60. rightMargin: margin
  61. top: parent.top
  62. topMargin: margin
  63. }
  64. visible: root.height >= height + margin * 2
  65. text: !!fan ? fan.name : ""
  66. color: palette.text
  67. horizontalAlignment: TextEdit.AlignLeft
  68. wrapMode: TextEdit.Wrap
  69. font.bold: true
  70. font.pointSize: 14
  71. selectByMouse: true
  72. onTextChanged: if (!!fan && fan.name != text) fan.name = text
  73. Connections {
  74. target: fan
  75. onNameChanged: if (fan.name != nameField.text) nameField.text = fan.name
  76. }
  77. MouseArea {
  78. anchors.fill: parent
  79. cursorShape: Qt.IBeamCursor
  80. acceptedButtons: Qt.NoButton
  81. }
  82. }
  83. Item {
  84. id: graph
  85. property int fontSize: MoreMath.bound(8, height / 20 + 1, 16)
  86. property QtObject pal: !!fan ? fan.hasTemp ? palette : disabledPalette : disabledPalette
  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: i18n("%1\%", 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: background.scaleX(Units.toCelsius(graph.horIntervals[index], unit)) - width/2
  129. y: horizontalScala.height / 2 - implicitHeight / 2
  130. color: graph.pal.text
  131. text: i18n("%1" + unit, graph.horIntervals[index])
  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. }
  248. PwmPoint {
  249. id: stopPoint
  250. color: !!fan ? fan.hasTemp ? "blue" : Qt.tint(graph.pal.light, Qt.rgba(0, 0, 1, 0.5)) : "transparent"
  251. size: graph.fontSize
  252. enabled: !!fan ? fan.hasTemp : false
  253. drag.maximumX: Math.min(background.scaleX(background.scaleTemp(maxPoint.x)-1), maxPoint.x-1)
  254. drag.minimumY: Math.max(background.scaleY(background.scalePwm(maxPoint.y)-1), maxPoint.y+1)
  255. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(root.minTemp, fan.minTemp, root.maxTemp)) - width/2 : -width/2
  256. y: !!fan && fan.hasTemp ? background.scaleY(fan.minStop) - height/2 : -height/2
  257. temp: !!fan && fan.hasTemp ? drag.active ? background.scaleTemp(centerX) : fan.minTemp : root.minTemp
  258. pwm: !!fan && fan.hasTemp ? drag.active ? background.scalePwm(centerY) : fan.minStop : 255
  259. drag.onActiveChanged: {
  260. if (!drag.active) {
  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. enabled: !!fan ? fan.hasTemp : false
  278. drag.minimumX: Math.max(background.scaleX(background.scaleTemp(stopPoint.x)+1), stopPoint.x+1)
  279. drag.maximumY: Math.min(background.scaleY(background.scalePwm(stopPoint.y)+1), stopPoint.y-1)
  280. x: !!fan && fan.hasTemp ? background.scaleX(MoreMath.bound(root.minTemp, fan.maxTemp, root.maxTemp)) - width/2 : background.width - width/2
  281. y: !!fan && fan.hasTemp ? background.scaleY(fan.maxPwm) - height/2 : -height/2
  282. temp: !!fan && fan.hasTemp ? drag.active ? background.scaleTemp(centerX) : fan.maxTemp : root.maxTemp
  283. pwm: !!fan && fan.hasTemp ? drag.active ? background.scalePwm(centerY) : fan.maxPwm : 255
  284. drag.onActiveChanged: {
  285. if (!drag.active) {
  286. fan.maxPwm = Math.round(background.scalePwm(centerY));
  287. fan.maxTemp = Math.round(background.scaleTemp(centerX));
  288. }
  289. }
  290. onPositionChanged: {
  291. var width = x - stopPoint.x;
  292. var height = stopPoint.y - y;
  293. bgCanvas.markDirty(Qt.rect(stopPoint.x, y, width, height));
  294. }
  295. }
  296. }
  297. }
  298. ColumnLayout {
  299. property int padding: 10
  300. id: settingsArea
  301. anchors {
  302. left: parent.left
  303. leftMargin: padding
  304. right: parent.right
  305. rightMargin: padding
  306. bottom: parent.bottom
  307. bottomMargin: padding
  308. }
  309. visible: root.height >= nameField.height + height + 2*margin
  310. clip: true
  311. spacing: 2
  312. RowLayout {
  313. CheckBox {
  314. id: hasTempCheckBox
  315. text: i18n("Controlled by:")
  316. checked: !!fan ? fan.hasTemp : false
  317. Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
  318. onCheckedChanged: {
  319. if (!!fan) {
  320. fan.hasTemp = checked;
  321. if (checked && !!tempModel.temps[tempBox.currentIndex]) {
  322. fan.temp = tempModel.temps[tempBox.currentIndex];
  323. }
  324. bgCanvas.requestPaint();
  325. }
  326. }
  327. Connections {
  328. target: root
  329. onFanChanged: hasTempCheckBox.checked = !!fan ? fan.hasTemp : false
  330. }
  331. Connections {
  332. target: fan
  333. onHasTempChanged: hasTempCheckBox.checked = fan.hasTemp
  334. }
  335. }
  336. RowLayout {
  337. ComboBox {
  338. id: tempBox
  339. Layout.fillWidth: true
  340. model: tempModel
  341. textRole: "display"
  342. enabled: hasTempCheckBox.checked
  343. onCurrentIndexChanged: {
  344. if (hasTempCheckBox.checked)
  345. fan.temp = tempModel.temps[currentIndex];
  346. }
  347. }
  348. Connections {
  349. target: root
  350. onFanChanged: if (!!fan && fan.hasTemp) tempBox.currentIndex = tempModel.temps.indexOf(fan.temp)
  351. }
  352. Connections {
  353. target: fan
  354. onTempChanged: if (fan.hasTemp) tempBox.currentIndex = tempModel.temps.indexOf(fan.temp)
  355. }
  356. }
  357. }
  358. CheckBox {
  359. id: fanOffCheckBox
  360. text: i18n("Turn Fan off if temp < MINTEMP")
  361. enabled: hasTempCheckBox.checked
  362. checked: !!fan ? fan.minPwm == 0 : false
  363. onCheckedChanged: {
  364. if (!!fan) {
  365. fan.minPwm = checked ? 0 : fan.minStop;
  366. bgCanvas.markDirty(Qt.rect(0, 0, stopPoint.x, stopPoint.y));
  367. }
  368. }
  369. Connections {
  370. target: root
  371. onFanChanged: if (!!fan) fanOffCheckBox.checked = fan.minPwm == 0
  372. }
  373. Connections {
  374. target: fan
  375. onMinPwmChanged: fanOffCheckBox.checked = fan.minPwm == 0
  376. }
  377. }
  378. RowLayout {
  379. enabled: fanOffCheckBox.checked && fanOffCheckBox.enabled
  380. Label {
  381. text: i18n("Pwm value for fan to start:")
  382. Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
  383. renderType: Text.NativeRendering
  384. }
  385. SpinBox {
  386. id: minStartInput
  387. Layout.fillWidth: true
  388. minimumValue: 0
  389. maximumValue: 100
  390. decimals: 1
  391. value: !!fan ? Math.round(fan.minStart / 2.55) : 0
  392. suffix: i18n("%")
  393. onValueChanged: {
  394. if (!!fan) {
  395. fan.minStart = Math.round(value * 2.55)
  396. }
  397. }
  398. }
  399. }
  400. RowLayout {
  401. visible: !!systemdCom
  402. Item {
  403. Layout.fillWidth: true
  404. }
  405. Button {
  406. id: testButton
  407. text: !!fan ? fan.testing ? i18n("Abort test") : i18n("Test start and stop values") : ""
  408. iconName: "dialog-password"
  409. Layout.alignment: Qt.AlignRight
  410. onClicked: {
  411. if (fan.testing) {
  412. fan.abortTest();
  413. } else {
  414. minStartInput.value = Qt.binding(function() { return Math.round(fan.minStart / 2.55) });
  415. fan.test();
  416. }
  417. }
  418. }
  419. }
  420. }
  421. }