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.

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