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.

498 lines
15KB

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