Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
14KB

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # Parameter SpinBox, a custom Qt4 widget
  4. # Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com>
  5. #
  6. # This program is free software; you can redistribute it and/or
  7. # modify it under the terms of the GNU General Public License as
  8. # published by the Free Software Foundation; either version 2 of
  9. # the License, or any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # For a full copy of the GNU General Public License see the doc/GPL.txt file.
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. # TODO - SIGNAL, SLOT
  20. from PyQt5.QtCore import pyqtSlot, Qt, QTimer
  21. from PyQt5.QtGui import QCursor
  22. from PyQt5.QtWidgets import QAbstractSpinBox, QApplication, QComboBox, QDialog, QMenu, QProgressBar
  23. from math import isnan
  24. # ------------------------------------------------------------------------------------------------------------
  25. # Imports (Custom)
  26. import ui_inputdialog_value
  27. def fixValue(value, minimum, maximum):
  28. if isnan(value):
  29. print("Parameter is NaN! - %f" % value)
  30. return minimum
  31. if value < minimum:
  32. print("Parameter too low! - %f/%f" % (value, minimum))
  33. return minimum
  34. if value > maximum:
  35. print("Parameter too high! - %f/%f" % (value, maximum))
  36. return maximum
  37. return value
  38. # ------------------------------------------------------------------------------------------------------------
  39. # Custom InputDialog with Scale Points support
  40. class CustomInputDialog(QDialog):
  41. def __init__(self, parent, label, current, minimum, maximum, step, scalePoints):
  42. QDialog.__init__(self, parent)
  43. self.ui = ui_inputdialog_value.Ui_Dialog()
  44. self.ui.setupUi(self)
  45. self.ui.label.setText(label)
  46. self.ui.doubleSpinBox.setMinimum(minimum)
  47. self.ui.doubleSpinBox.setMaximum(maximum)
  48. self.ui.doubleSpinBox.setValue(current)
  49. self.ui.doubleSpinBox.setSingleStep(step)
  50. if not scalePoints:
  51. self.ui.groupBox.setVisible(False)
  52. self.resize(200, 0)
  53. else:
  54. text = "<table>"
  55. for scalePoint in scalePoints:
  56. text += "<tr><td align='right'>%f</td><td align='left'> - %s</td></tr>" % (scalePoint['value'], scalePoint['label'])
  57. text += "</table>"
  58. self.ui.textBrowser.setText(text)
  59. self.resize(200, 300)
  60. self.fRetValue = current
  61. self.connect(self, SIGNAL("accepted()"), SLOT("slot_setReturnValue()"))
  62. def returnValue(self):
  63. return self.fRetValue
  64. @pyqtSlot()
  65. def slot_setReturnValue(self):
  66. self.fRetValue = self.ui.doubleSpinBox.value()
  67. def done(self, r):
  68. QDialog.done(self, r)
  69. self.close()
  70. # ------------------------------------------------------------------------------------------------------------
  71. # ProgressBar used for ParamSpinBox
  72. class ParamProgressBar(QProgressBar):
  73. def __init__(self, parent):
  74. QProgressBar.__init__(self, parent)
  75. self.fLeftClickDown = False
  76. self.fMinimum = 0.0
  77. self.fMaximum = 1.0
  78. self.fRealValue = 0.0
  79. self.fLabel = ""
  80. self.fPreLabel = " "
  81. self.fTextCall = None
  82. self.setFormat("(none)")
  83. # Fake internal value, 10'000 precision
  84. QProgressBar.setMinimum(self, 0)
  85. QProgressBar.setMaximum(self, 10000)
  86. QProgressBar.setValue(self, 0)
  87. def setMinimum(self, value):
  88. self.fMinimum = value
  89. def setMaximum(self, value):
  90. self.fMaximum = value
  91. def setValue(self, value):
  92. self.fRealValue = value
  93. vper = float(value - self.fMinimum) / float(self.fMaximum - self.fMinimum)
  94. QProgressBar.setValue(self, int(vper * 10000))
  95. def setLabel(self, label):
  96. self.fLabel = label.strip()
  97. if self.fLabel == "(coef)":
  98. self.fLabel = ""
  99. self.fPreLabel = "*"
  100. self.update()
  101. def setTextCall(self, textCall):
  102. self.fTextCall = textCall
  103. def handleMouseEventPos(self, pos):
  104. xper = float(pos.x()) / float(self.width())
  105. value = xper * (self.fMaximum - self.fMinimum) + self.fMinimum
  106. if value < self.fMinimum:
  107. value = self.fMinimum
  108. elif value > self.fMaximum:
  109. value = self.fMaximum
  110. self.emit(SIGNAL("valueChanged(double)"), value)
  111. def mousePressEvent(self, event):
  112. if event.button() == Qt.LeftButton:
  113. self.handleMouseEventPos(event.pos())
  114. self.fLeftClickDown = True
  115. else:
  116. self.fLeftClickDown = False
  117. QProgressBar.mousePressEvent(self, event)
  118. def mouseMoveEvent(self, event):
  119. if self.fLeftClickDown:
  120. self.handleMouseEventPos(event.pos())
  121. QProgressBar.mouseMoveEvent(self, event)
  122. def mouseReleaseEvent(self, event):
  123. self.fLeftClickDown = False
  124. QProgressBar.mouseReleaseEvent(self, event)
  125. def paintEvent(self, event):
  126. if self.fTextCall is not None:
  127. self.setFormat("%s %s %s" % (self.fPreLabel, self.fTextCall(), self.fLabel))
  128. else:
  129. self.setFormat("%s %f %s" % (self.fPreLabel, self.fRealValue, self.fLabel))
  130. QProgressBar.paintEvent(self, event)
  131. # ------------------------------------------------------------------------------------------------------------
  132. # Special SpinBox used for parameters
  133. class ParamSpinBox(QAbstractSpinBox):
  134. def __init__(self, parent):
  135. QAbstractSpinBox.__init__(self, parent)
  136. self.fMinimum = 0.0
  137. self.fMaximum = 1.0
  138. self.fDefault = 0.0
  139. self.fValue = None
  140. self.fStep = 0.0
  141. self.fStepSmall = 0.0
  142. self.fStepLarge = 0.0
  143. self.fReadOnly = False
  144. self.fScalePoints = None
  145. self.fHaveScalePoints = False
  146. self.fBar = ParamProgressBar(self)
  147. self.fBar.setContextMenuPolicy(Qt.NoContextMenu)
  148. self.fBar.show()
  149. self.fName = ""
  150. self.lineEdit().setVisible(False)
  151. self.connect(self, SIGNAL("customContextMenuRequested(QPoint)"), SLOT("slot_showCustomMenu()"))
  152. self.connect(self.fBar, SIGNAL("valueChanged(double)"), SLOT("slot_progressBarValueChanged(double)"))
  153. QTimer.singleShot(0, self, SLOT("slot_updateProgressBarGeometry()"))
  154. def setDefault(self, value):
  155. value = fixValue(value, self.fMinimum, self.fMaximum)
  156. self.fDefault = value
  157. def setMinimum(self, value):
  158. self.fMinimum = value
  159. self.fBar.setMinimum(value)
  160. def setMaximum(self, value):
  161. self.fMaximum = value
  162. self.fBar.setMaximum(value)
  163. def setValue(self, value, send=True):
  164. value = fixValue(value, self.fMinimum, self.fMaximum)
  165. if self.fValue == value:
  166. return False
  167. self.fValue = value
  168. self.fBar.setValue(value)
  169. if self.fHaveScalePoints:
  170. self._setScalePointValue(value)
  171. if send:
  172. self.emit(SIGNAL("valueChanged(double)"), value)
  173. self.update()
  174. return True
  175. def setStep(self, value):
  176. if value == 0.0:
  177. self.fStep = 0.001
  178. else:
  179. self.fStep = value
  180. if self.fStepSmall > value:
  181. self.fStepSmall = value
  182. if self.fStepLarge < value:
  183. self.fStepLarge = value
  184. def setStepSmall(self, value):
  185. if value == 0.0:
  186. self.fStepSmall = 0.0001
  187. elif value > self.fStep:
  188. self.fStepSmall = self.fStep
  189. else:
  190. self.fStepSmall = value
  191. def setStepLarge(self, value):
  192. if value == 0.0:
  193. self.fStepLarge = 0.1
  194. elif value < self.fStep:
  195. self.fStepLarge = self.fStep
  196. else:
  197. self.fStepLarge = value
  198. def setLabel(self, label):
  199. self.fBar.setLabel(label)
  200. def setName(self, name):
  201. self.fName = name
  202. def setTextCallback(self, textCall):
  203. self.fBar.setTextCall(textCall)
  204. def setReadOnly(self, yesNo):
  205. self.setButtonSymbols(QAbstractSpinBox.UpDownArrows if yesNo else QAbstractSpinBox.NoButtons)
  206. self.fReadOnly = yesNo
  207. QAbstractSpinBox.setReadOnly(self, yesNo)
  208. def setEnabled(self, yesNo):
  209. self.fBar.setEnabled(yesNo)
  210. QAbstractSpinBox.setEnabled(self, yesNo)
  211. def setScalePoints(self, scalePoints, useScalePoints):
  212. if len(scalePoints) == 0:
  213. self.fScalePoints = None
  214. self.fHaveScalePoints = False
  215. return
  216. self.fScalePoints = scalePoints
  217. self.fHaveScalePoints = useScalePoints
  218. if useScalePoints:
  219. # Hide ProgressBar and create a ComboBox
  220. self.fBar.close()
  221. self.fBox = QComboBox(self)
  222. self.fBox.setContextMenuPolicy(Qt.NoContextMenu)
  223. self.fBox.show()
  224. self.slot_updateProgressBarGeometry()
  225. # Add items, sorted
  226. boxItemValues = []
  227. for scalePoint in scalePoints:
  228. value = scalePoint['value']
  229. label = "%f - %s" % (value, scalePoint['label'])
  230. if len(boxItemValues) == 0:
  231. self.fBox.addItem(label)
  232. boxItemValues.append(value)
  233. else:
  234. if value < boxItemValues[0]:
  235. self.fBox.insertItem(0, label)
  236. boxItemValues.insert(0, value)
  237. elif value > boxItemValues[-1]:
  238. self.fBox.addItem(label)
  239. boxItemValues.append(value)
  240. else:
  241. for index in range(len(boxItemValues)):
  242. if value >= boxItemValues[index]:
  243. self.fBox.insertItem(index+1, label)
  244. boxItemValues.insert(index+1, value)
  245. break
  246. if self.fValue != None:
  247. self._setScalePointValue(self.fValue)
  248. self.connect(self.fBox, SIGNAL("currentIndexChanged(QString)"), SLOT("slot_comboBoxIndexChanged(QString)"))
  249. def stepBy(self, steps):
  250. if steps == 0 or self.fValue is None:
  251. return
  252. value = self.fValue + (self.fStep * steps)
  253. if value < self.fMinimum:
  254. value = self.fMinimum
  255. elif value > self.fMaximum:
  256. value = self.fMaximum
  257. self.setValue(value)
  258. def stepEnabled(self):
  259. if self.fReadOnly or self.fValue is None:
  260. return QAbstractSpinBox.StepNone
  261. if self.fValue <= self.fMinimum:
  262. return QAbstractSpinBox.StepUpEnabled
  263. if self.fValue >= self.fMaximum:
  264. return QAbstractSpinBox.StepDownEnabled
  265. return (QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled)
  266. def updateAll(self):
  267. self.update()
  268. self.fBar.update()
  269. if self.fHaveScalePoints:
  270. self.fBox.update()
  271. def resizeEvent(self, event):
  272. QTimer.singleShot(0, self, SLOT("slot_updateProgressBarGeometry()"))
  273. QAbstractSpinBox.resizeEvent(self, event)
  274. @pyqtSlot(str)
  275. def slot_comboBoxIndexChanged(self, boxText):
  276. if self.fReadOnly:
  277. return
  278. value = float(boxText.split(" - ", 1)[0])
  279. lastScaleValue = self.fScalePoints[-1]["value"]
  280. if value == lastScaleValue:
  281. value = self.fMaximum
  282. self.setValue(value)
  283. @pyqtSlot(float)
  284. def slot_progressBarValueChanged(self, value):
  285. if self.fReadOnly:
  286. return
  287. step = int((value - self.fMinimum) / self.fStep + 0.5)
  288. realValue = self.fMinimum + (step * self.fStep)
  289. self.setValue(realValue)
  290. @pyqtSlot()
  291. def slot_showCustomMenu(self):
  292. menu = QMenu(self)
  293. actReset = menu.addAction(self.tr("Reset (%f)" % self.fDefault))
  294. menu.addSeparator()
  295. actCopy = menu.addAction(self.tr("Copy (%f)" % self.fValue))
  296. clipboard = QApplication.instance().clipboard()
  297. pasteText = clipboard.text()
  298. pasteValue = None
  299. if pasteText:
  300. try:
  301. pasteValue = float(pasteText)
  302. except:
  303. pass
  304. if pasteValue is None:
  305. actPaste = menu.addAction(self.tr("Paste"))
  306. else:
  307. actPaste = menu.addAction(self.tr("Paste (%s)" % pasteValue))
  308. menu.addSeparator()
  309. actSet = menu.addAction(self.tr("Set value..."))
  310. if self.fReadOnly:
  311. actReset.setEnabled(False)
  312. actPaste.setEnabled(False)
  313. actSet.setEnabled(False)
  314. actSel = menu.exec_(QCursor.pos())
  315. if actSel == actSet:
  316. dialog = CustomInputDialog(self, self.fName, self.fValue, self.fMinimum, self.fMaximum, self.fStep, self.fScalePoints)
  317. if dialog.exec_():
  318. value = dialog.returnValue()
  319. self.setValue(value)
  320. elif actSel == actCopy:
  321. clipboard.setText("%f" % self.fValue)
  322. elif actSel == actPaste:
  323. self.setValue(pasteValue)
  324. elif actSel == actReset:
  325. self.setValue(self.fDefault)
  326. @pyqtSlot()
  327. def slot_updateProgressBarGeometry(self):
  328. self.fBar.setGeometry(self.lineEdit().geometry())
  329. if self.fHaveScalePoints:
  330. self.fBox.setGeometry(self.lineEdit().geometry())
  331. def _getNearestScalePoint(self, realValue):
  332. finalValue = 0.0
  333. for i in range(len(self.fScalePoints)):
  334. scaleValue = self.fScalePoints[i]["value"]
  335. if i == 0:
  336. finalValue = scaleValue
  337. else:
  338. srange1 = abs(realValue - scaleValue)
  339. srange2 = abs(realValue - finalValue)
  340. if srange2 > srange1:
  341. finalValue = scaleValue
  342. return finalValue
  343. def _setScalePointValue(self, value):
  344. value = self._getNearestScalePoint(value)
  345. for i in range(self.fBox.count()):
  346. if float(self.fBox.itemText(i).split(" - ", 1)[0]) == value:
  347. self.fBox.setCurrentIndex(i)
  348. break
  349. # ------------------------------------------------------------------------------------------------------------
  350. # TESTING
  351. import sys
  352. from PyQt5.QtWidgets import QApplication
  353. app = QApplication(sys.argv)
  354. gui = ParamSpinBox(None)
  355. gui.show()
  356. app.exec_()