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.

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