FanItem.qml 18 KB

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