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.

paramspinbox.py 16KB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  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(name, value, minimum, maximum):
  37. if isnan(value):
  38. print("Parameter '%s' is NaN! - %f" % (name, value))
  39. return minimum
  40. if value < minimum:
  41. print("Parameter '%s' too low! - %f/%f" % (name, value, minimum))
  42. return minimum
  43. if value > maximum:
  44. print("Parameter '%s' too high! - %f/%f" % (name, 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.fName = ""
  93. self.fPreLabel = " "
  94. self.fTextCall = None
  95. self.setFormat("(none)")
  96. # Fake internal value, 10'000 precision
  97. QProgressBar.setMinimum(self, 0)
  98. QProgressBar.setMaximum(self, 10000)
  99. QProgressBar.setValue(self, 0)
  100. def setMinimum(self, value):
  101. self.fMinimum = value
  102. def setMaximum(self, value):
  103. self.fMaximum = value
  104. def setValue(self, value):
  105. self.fRealValue = value
  106. div = float(self.fMaximum - self.fMinimum)
  107. if div == 0.0:
  108. print("Parameter '%s' division by 0 prevented (value:%f, min:%f, max:%f)" % (self.fName, value, self.fMaximum, self.fMinimum))
  109. vper = 1.0
  110. else:
  111. vper = float(value - self.fMinimum) / div
  112. QProgressBar.setValue(self, int(vper * 10000))
  113. def setLabel(self, label):
  114. self.fLabel = label.strip()
  115. if self.fLabel == "(coef)":
  116. self.fLabel = ""
  117. self.fPreLabel = "*"
  118. self.update()
  119. def setName(self, name):
  120. self.fName = name
  121. def setTextCall(self, textCall):
  122. self.fTextCall = textCall
  123. def handleMouseEventPos(self, pos):
  124. xper = float(pos.x()) / float(self.width())
  125. value = xper * (self.fMaximum - self.fMinimum) + self.fMinimum
  126. if value < self.fMinimum:
  127. value = self.fMinimum
  128. elif value > self.fMaximum:
  129. value = self.fMaximum
  130. self.valueChanged.emit(value)
  131. def mousePressEvent(self, event):
  132. if event.button() == Qt.LeftButton:
  133. self.handleMouseEventPos(event.pos())
  134. self.fLeftClickDown = True
  135. else:
  136. self.fLeftClickDown = False
  137. QProgressBar.mousePressEvent(self, event)
  138. def mouseMoveEvent(self, event):
  139. if self.fLeftClickDown:
  140. self.handleMouseEventPos(event.pos())
  141. QProgressBar.mouseMoveEvent(self, event)
  142. def mouseReleaseEvent(self, event):
  143. self.fLeftClickDown = False
  144. QProgressBar.mouseReleaseEvent(self, event)
  145. def paintEvent(self, event):
  146. if self.fTextCall is not None:
  147. self.setFormat("%s %s %s" % (self.fPreLabel, self.fTextCall(), self.fLabel))
  148. elif self.fIsInteger:
  149. self.setFormat("%s %i %s" % (self.fPreLabel, int(self.fRealValue), self.fLabel))
  150. else:
  151. self.setFormat("%s %f %s" % (self.fPreLabel, self.fRealValue, self.fLabel))
  152. QProgressBar.paintEvent(self, event)
  153. # ------------------------------------------------------------------------------------------------------------
  154. # Special SpinBox used for parameters
  155. class ParamSpinBox(QAbstractSpinBox):
  156. # signals
  157. valueChanged = pyqtSignal(float)
  158. def __init__(self, parent):
  159. QAbstractSpinBox.__init__(self, parent)
  160. self.fName = ""
  161. self.fMinimum = 0.0
  162. self.fMaximum = 1.0
  163. self.fDefault = 0.0
  164. self.fValue = None
  165. self.fStep = 0.01
  166. self.fStepSmall = 0.0001
  167. self.fStepLarge = 0.1
  168. self.fIsReadOnly = False
  169. self.fScalePoints = None
  170. self.fUseScalePoints = False
  171. self.fBar = ParamProgressBar(self)
  172. self.fBar.setContextMenuPolicy(Qt.NoContextMenu)
  173. #self.fBar.show()
  174. self.fBox = None
  175. self.lineEdit().hide()
  176. self.customContextMenuRequested.connect(self.slot_showCustomMenu)
  177. self.fBar.valueChanged.connect(self.slot_progressBarValueChanged)
  178. QTimer.singleShot(0, self.slot_updateProgressBarGeometry)
  179. def setDefault(self, value):
  180. value = geFixedValue(self.fName, value, self.fMinimum, self.fMaximum)
  181. self.fDefault = value
  182. def setMinimum(self, value):
  183. self.fMinimum = value
  184. self.fBar.setMinimum(value)
  185. def setMaximum(self, value):
  186. self.fMaximum = value
  187. self.fBar.setMaximum(value)
  188. def setValue(self, value, send=True):
  189. value = geFixedValue(self.fName, value, self.fMinimum, self.fMaximum)
  190. if self.fValue == value:
  191. return False
  192. self.fValue = value
  193. self.fBar.setValue(value)
  194. if self.fUseScalePoints:
  195. self._setScalePointValue(value)
  196. if send:
  197. self.valueChanged.emit(value)
  198. self.update()
  199. return True
  200. def setStep(self, value):
  201. if value == 0.0:
  202. self.fStep = 0.001
  203. else:
  204. self.fStep = value
  205. if self.fStepSmall > value:
  206. self.fStepSmall = value
  207. if self.fStepLarge < value:
  208. self.fStepLarge = value
  209. self.fBar.fIsInteger = bool(self.fStepSmall == 1.0)
  210. def setStepSmall(self, value):
  211. if value == 0.0:
  212. self.fStepSmall = 0.0001
  213. elif value > self.fStep:
  214. self.fStepSmall = self.fStep
  215. else:
  216. self.fStepSmall = value
  217. self.fBar.fIsInteger = bool(self.fStepSmall == 1.0)
  218. def setStepLarge(self, value):
  219. if value == 0.0:
  220. self.fStepLarge = 0.1
  221. elif value < self.fStep:
  222. self.fStepLarge = self.fStep
  223. else:
  224. self.fStepLarge = value
  225. def setLabel(self, label):
  226. self.fBar.setLabel(label)
  227. def setName(self, name):
  228. self.fName = name
  229. self.fBar.setName(name)
  230. def setTextCallback(self, textCall):
  231. self.fBar.setTextCall(textCall)
  232. def setReadOnly(self, yesNo):
  233. self.fIsReadOnly = yesNo
  234. self.setButtonSymbols(QAbstractSpinBox.UpDownArrows if yesNo else QAbstractSpinBox.NoButtons)
  235. QAbstractSpinBox.setReadOnly(self, yesNo)
  236. def setEnabled(self, yesNo):
  237. self.fBar.setEnabled(yesNo)
  238. QAbstractSpinBox.setEnabled(self, yesNo)
  239. def setScalePoints(self, scalePoints, useScalePoints):
  240. if len(scalePoints) == 0:
  241. self.fScalePoints = None
  242. self.fUseScalePoints = False
  243. return
  244. self.fScalePoints = scalePoints
  245. self.fUseScalePoints = useScalePoints
  246. if not useScalePoints:
  247. return
  248. # Hide ProgressBar and create a ComboBox
  249. self.fBar.close()
  250. self.fBox = QComboBox(self)
  251. self.fBox.setContextMenuPolicy(Qt.NoContextMenu)
  252. #self.fBox.show()
  253. self.slot_updateProgressBarGeometry()
  254. # Add items, sorted
  255. boxItemValues = []
  256. for scalePoint in scalePoints:
  257. value = scalePoint['value']
  258. if self.fStep == 1.0:
  259. label = "%i - %s" % (int(value), scalePoint['label'])
  260. else:
  261. label = "%f - %s" % (value, scalePoint['label'])
  262. if len(boxItemValues) == 0:
  263. self.fBox.addItem(label)
  264. boxItemValues.append(value)
  265. else:
  266. if value < boxItemValues[0]:
  267. self.fBox.insertItem(0, label)
  268. boxItemValues.insert(0, value)
  269. elif value > boxItemValues[-1]:
  270. self.fBox.addItem(label)
  271. boxItemValues.append(value)
  272. else:
  273. for index in range(len(boxItemValues)):
  274. if value >= boxItemValues[index]:
  275. self.fBox.insertItem(index+1, label)
  276. boxItemValues.insert(index+1, value)
  277. break
  278. if self.fValue is not None:
  279. self._setScalePointValue(self.fValue)
  280. self.fBox.currentIndexChanged['QString'].connect(self.slot_comboBoxIndexChanged)
  281. def stepBy(self, steps):
  282. if steps == 0 or self.fValue is None:
  283. return
  284. value = self.fValue + (self.fStep * steps)
  285. if value < self.fMinimum:
  286. value = self.fMinimum
  287. elif value > self.fMaximum:
  288. value = self.fMaximum
  289. self.setValue(value)
  290. def stepEnabled(self):
  291. if self.fIsReadOnly or self.fValue is None:
  292. return QAbstractSpinBox.StepNone
  293. if self.fValue <= self.fMinimum:
  294. return QAbstractSpinBox.StepUpEnabled
  295. if self.fValue >= self.fMaximum:
  296. return QAbstractSpinBox.StepDownEnabled
  297. return (QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled)
  298. def updateAll(self):
  299. self.update()
  300. self.fBar.update()
  301. if self.fBox is not None:
  302. self.fBox.update()
  303. def resizeEvent(self, event):
  304. QAbstractSpinBox.resizeEvent(self, event)
  305. self.slot_updateProgressBarGeometry()
  306. @pyqtSlot(str)
  307. def slot_comboBoxIndexChanged(self, boxText):
  308. if self.fIsReadOnly:
  309. return
  310. value = float(boxText.split(" - ", 1)[0])
  311. lastScaleValue = self.fScalePoints[-1]['value']
  312. if value == lastScaleValue:
  313. value = self.fMaximum
  314. self.setValue(value)
  315. @pyqtSlot(float)
  316. def slot_progressBarValueChanged(self, value):
  317. if self.fIsReadOnly:
  318. return
  319. if value <= self.fMinimum:
  320. realValue = self.fMinimum
  321. elif value >= self.fMaximum:
  322. realValue = self.fMaximum
  323. else:
  324. curStep = int((value - self.fMinimum) / self.fStep + 0.5)
  325. realValue = self.fMinimum + (self.fStep * curStep)
  326. if realValue < self.fMinimum:
  327. realValue = self.fMinimum
  328. elif realValue > self.fMaximum:
  329. realValue = self.fMaximum
  330. self.setValue(realValue)
  331. @pyqtSlot()
  332. def slot_showCustomMenu(self):
  333. clipboard = QApplication.instance().clipboard()
  334. pasteText = clipboard.text()
  335. pasteValue = None
  336. if pasteText:
  337. try:
  338. pasteValue = float(pasteText)
  339. except:
  340. pass
  341. menu = QMenu(self)
  342. actReset = menu.addAction(self.tr("Reset (%f)" % self.fDefault))
  343. actRandom = menu.addAction(self.tr("Random"))
  344. menu.addSeparator()
  345. actCopy = menu.addAction(self.tr("Copy (%f)" % self.fValue))
  346. if pasteValue is None:
  347. actPaste = menu.addAction(self.tr("Paste"))
  348. actPaste.setEnabled(False)
  349. else:
  350. actPaste = menu.addAction(self.tr("Paste (%f)" % pasteValue))
  351. menu.addSeparator()
  352. actSet = menu.addAction(self.tr("Set value..."))
  353. if self.fIsReadOnly:
  354. actReset.setEnabled(False)
  355. actRandom.setEnabled(False)
  356. actPaste.setEnabled(False)
  357. actSet.setEnabled(False)
  358. actSel = menu.exec_(QCursor.pos())
  359. if actSel == actReset:
  360. self.setValue(self.fDefault)
  361. elif actSel == actRandom:
  362. value = random() * (self.fMaximum - self.fMinimum) + self.fMinimum
  363. self.setValue(value)
  364. elif actSel == actCopy:
  365. clipboard.setText("%f" % self.fValue)
  366. elif actSel == actPaste:
  367. self.setValue(pasteValue)
  368. elif actSel == actSet:
  369. dialog = CustomInputDialog(self, self.fName, self.fValue, self.fMinimum, self.fMaximum, self.fStep, self.fScalePoints)
  370. if dialog.exec_():
  371. value = dialog.returnValue()
  372. self.setValue(value)
  373. @pyqtSlot()
  374. def slot_updateProgressBarGeometry(self):
  375. self.fBar.setGeometry(self.lineEdit().geometry())
  376. if self.fUseScalePoints:
  377. self.fBox.setGeometry(self.lineEdit().geometry())
  378. def _getNearestScalePoint(self, realValue):
  379. finalValue = 0.0
  380. for i in range(len(self.fScalePoints)):
  381. scaleValue = self.fScalePoints[i]["value"]
  382. if i == 0:
  383. finalValue = scaleValue
  384. else:
  385. srange1 = abs(realValue - scaleValue)
  386. srange2 = abs(realValue - finalValue)
  387. if srange2 > srange1:
  388. finalValue = scaleValue
  389. return finalValue
  390. def _setScalePointValue(self, value):
  391. value = self._getNearestScalePoint(value)
  392. for i in range(self.fBox.count()):
  393. if float(self.fBox.itemText(i).split(" - ", 1)[0]) == value:
  394. self.fBox.setCurrentIndex(i)
  395. break