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 17KB

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
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
10 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
10 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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  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, modf
  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, stepSmall, scalePoints):
  51. QDialog.__init__(self, parent)
  52. self.ui = ui_inputdialog_value.Ui_Dialog()
  53. self.ui.setupUi(self)
  54. # calculate num decimals from stepSmall
  55. if stepSmall >= 1.0:
  56. decimals = 0
  57. elif step >= 1.0:
  58. decimals = 2
  59. else:
  60. decfrac, decwhole = modf(stepSmall)
  61. if "000" in str(decfrac):
  62. decfrac = round(decfrac, str(decfrac).find("000"))
  63. else:
  64. decfrac = round(decfrac, 12)
  65. decimals = abs(len(str(decfrac))-len(str(decwhole))-1)
  66. self.ui.label.setText(label)
  67. self.ui.doubleSpinBox.setDecimals(decimals)
  68. self.ui.doubleSpinBox.setRange(minimum, maximum)
  69. self.ui.doubleSpinBox.setSingleStep(step)
  70. self.ui.doubleSpinBox.setValue(current)
  71. if not scalePoints:
  72. self.ui.groupBox.setVisible(False)
  73. self.resize(200, 0)
  74. else:
  75. text = "<table>"
  76. for scalePoint in scalePoints:
  77. text += "<tr><td align='right'>%f</td><td align='left'> - %s</td></tr>" % (scalePoint['value'], scalePoint['label'])
  78. text += "</table>"
  79. self.ui.textBrowser.setText(text)
  80. self.resize(200, 300)
  81. self.fRetValue = current
  82. self.accepted.connect(self.slot_setReturnValue)
  83. def returnValue(self):
  84. return self.fRetValue
  85. @pyqtSlot()
  86. def slot_setReturnValue(self):
  87. self.fRetValue = self.ui.doubleSpinBox.value()
  88. def done(self, r):
  89. QDialog.done(self, r)
  90. self.close()
  91. # ------------------------------------------------------------------------------------------------------------
  92. # ProgressBar used for ParamSpinBox
  93. class ParamProgressBar(QProgressBar):
  94. # signals
  95. valueChanged = pyqtSignal(float)
  96. def __init__(self, parent):
  97. QProgressBar.__init__(self, parent)
  98. self.fLeftClickDown = False
  99. self.fIsInteger = False
  100. self.fMinimum = 0.0
  101. self.fMaximum = 1.0
  102. self.fRealValue = 0.0
  103. self.fLastPaintedValue = None
  104. self.fCurrentPaintedText = ""
  105. self.fLabel = ""
  106. self.fName = ""
  107. self.fPreLabel = " "
  108. self.fTextCall = None
  109. self.fValueCall = None
  110. self.setFormat("(none)")
  111. # Fake internal value, 10'000 precision
  112. QProgressBar.setMinimum(self, 0)
  113. QProgressBar.setMaximum(self, 10000)
  114. QProgressBar.setValue(self, 0)
  115. def setMinimum(self, value):
  116. self.fMinimum = value
  117. def setMaximum(self, value):
  118. self.fMaximum = value
  119. def setValue(self, value):
  120. if self.fRealValue == value:
  121. return False
  122. self.fRealValue = value
  123. div = float(self.fMaximum - self.fMinimum)
  124. if div == 0.0:
  125. print("Parameter '%s' division by 0 prevented (value:%f, min:%f, max:%f)" % (self.fName, value, self.fMaximum, self.fMinimum))
  126. vper = 1.0
  127. else:
  128. vper = float(value - self.fMinimum) / div
  129. if self.fValueCall is not None:
  130. self.fValueCall(value)
  131. QProgressBar.setValue(self, int(vper * 10000))
  132. return True
  133. def setLabel(self, label):
  134. self.fLabel = label.strip()
  135. if self.fLabel == "(coef)":
  136. self.fLabel = ""
  137. self.fPreLabel = "*"
  138. # force refresh of text value
  139. self.fLastPaintedValue = None
  140. self.update()
  141. def setName(self, name):
  142. self.fName = name
  143. def setTextCall(self, textCall):
  144. self.fTextCall = textCall
  145. def setValueCall(self, valueCall):
  146. self.fValueCall = valueCall
  147. def handleMouseEventPos(self, pos):
  148. xper = float(pos.x()) / float(self.width())
  149. value = xper * (self.fMaximum - self.fMinimum) + self.fMinimum
  150. if self.fIsInteger:
  151. value = round(value)
  152. if value < self.fMinimum:
  153. value = self.fMinimum
  154. elif value > self.fMaximum:
  155. value = self.fMaximum
  156. if self.setValue(value):
  157. self.valueChanged.emit(value)
  158. def mousePressEvent(self, event):
  159. if event.button() == Qt.LeftButton:
  160. self.handleMouseEventPos(event.pos())
  161. self.fLeftClickDown = True
  162. else:
  163. self.fLeftClickDown = False
  164. QProgressBar.mousePressEvent(self, event)
  165. def mouseMoveEvent(self, event):
  166. if self.fLeftClickDown:
  167. self.handleMouseEventPos(event.pos())
  168. QProgressBar.mouseMoveEvent(self, event)
  169. def mouseReleaseEvent(self, event):
  170. self.fLeftClickDown = False
  171. QProgressBar.mouseReleaseEvent(self, event)
  172. def paintEvent(self, event):
  173. if self.fTextCall is not None:
  174. if self.fLastPaintedValue != self.fRealValue:
  175. self.fLastPaintedValue = self.fRealValue
  176. self.fCurrentPaintedText = self.fTextCall()
  177. self.setFormat("%s %s %s" % (self.fPreLabel, self.fCurrentPaintedText, self.fLabel))
  178. elif self.fIsInteger:
  179. self.setFormat("%s %i %s" % (self.fPreLabel, int(self.fRealValue), self.fLabel))
  180. else:
  181. self.setFormat("%s %f %s" % (self.fPreLabel, self.fRealValue, self.fLabel))
  182. QProgressBar.paintEvent(self, event)
  183. # ------------------------------------------------------------------------------------------------------------
  184. # Special SpinBox used for parameters
  185. class ParamSpinBox(QAbstractSpinBox):
  186. # signals
  187. valueChanged = pyqtSignal(float)
  188. def __init__(self, parent):
  189. QAbstractSpinBox.__init__(self, parent)
  190. self.fName = ""
  191. self.fMinimum = 0.0
  192. self.fMaximum = 1.0
  193. self.fDefault = 0.0
  194. self.fValue = None
  195. self.fStep = 0.01
  196. self.fStepSmall = 0.0001
  197. self.fStepLarge = 0.1
  198. self.fIsReadOnly = False
  199. self.fScalePoints = None
  200. self.fUseScalePoints = False
  201. self.fBar = ParamProgressBar(self)
  202. self.fBar.setContextMenuPolicy(Qt.NoContextMenu)
  203. #self.fBar.show()
  204. self.fBox = None
  205. self.lineEdit().hide()
  206. self.customContextMenuRequested.connect(self.slot_showCustomMenu)
  207. self.fBar.valueChanged.connect(self.slot_progressBarValueChanged)
  208. QTimer.singleShot(0, self.slot_updateProgressBarGeometry)
  209. def setDefault(self, value):
  210. value = geFixedValue(self.fName, value, self.fMinimum, self.fMaximum)
  211. self.fDefault = value
  212. def setMinimum(self, value):
  213. self.fMinimum = value
  214. self.fBar.setMinimum(value)
  215. def setMaximum(self, value):
  216. self.fMaximum = value
  217. self.fBar.setMaximum(value)
  218. def setValue(self, value):
  219. value = geFixedValue(self.fName, value, self.fMinimum, self.fMaximum)
  220. if self.fBar.fIsInteger:
  221. value = round(value)
  222. if self.fValue == value:
  223. return False
  224. self.fValue = value
  225. self.fBar.setValue(value)
  226. if self.fUseScalePoints:
  227. self._setScalePointValue(value)
  228. self.valueChanged.emit(value)
  229. self.update()
  230. return True
  231. def setStep(self, value):
  232. if value == 0.0:
  233. self.fStep = 0.001
  234. else:
  235. self.fStep = value
  236. if self.fStepSmall > value:
  237. self.fStepSmall = value
  238. if self.fStepLarge < value:
  239. self.fStepLarge = value
  240. self.fBar.fIsInteger = bool(self.fStepSmall == 1.0)
  241. def setStepSmall(self, value):
  242. if value == 0.0:
  243. self.fStepSmall = 0.0001
  244. elif value > self.fStep:
  245. self.fStepSmall = self.fStep
  246. else:
  247. self.fStepSmall = value
  248. self.fBar.fIsInteger = bool(self.fStepSmall == 1.0)
  249. def setStepLarge(self, value):
  250. if value == 0.0:
  251. self.fStepLarge = 0.1
  252. elif value < self.fStep:
  253. self.fStepLarge = self.fStep
  254. else:
  255. self.fStepLarge = value
  256. def setLabel(self, label):
  257. self.fBar.setLabel(label)
  258. def setName(self, name):
  259. self.fName = name
  260. self.fBar.setName(name)
  261. def setTextCallback(self, textCall):
  262. self.fBar.setTextCall(textCall)
  263. def setValueCallback(self, valueCall):
  264. self.fBar.setValueCall(valueCall)
  265. def setReadOnly(self, yesNo):
  266. self.fIsReadOnly = yesNo
  267. self.setButtonSymbols(QAbstractSpinBox.UpDownArrows if yesNo else QAbstractSpinBox.NoButtons)
  268. QAbstractSpinBox.setReadOnly(self, yesNo)
  269. # FIXME use change event
  270. def setEnabled(self, yesNo):
  271. self.fBar.setEnabled(yesNo)
  272. QAbstractSpinBox.setEnabled(self, yesNo)
  273. def setScalePoints(self, scalePoints, useScalePoints):
  274. if len(scalePoints) == 0:
  275. self.fScalePoints = None
  276. self.fUseScalePoints = False
  277. return
  278. self.fScalePoints = scalePoints
  279. self.fUseScalePoints = useScalePoints
  280. if not useScalePoints:
  281. return
  282. # Hide ProgressBar and create a ComboBox
  283. self.fBar.close()
  284. self.fBox = QComboBox(self)
  285. self.fBox.setContextMenuPolicy(Qt.NoContextMenu)
  286. #self.fBox.show()
  287. self.slot_updateProgressBarGeometry()
  288. # Add items, sorted
  289. boxItemValues = []
  290. for scalePoint in scalePoints:
  291. value = scalePoint['value']
  292. if self.fStep == 1.0:
  293. label = "%i - %s" % (int(value), scalePoint['label'])
  294. else:
  295. label = "%f - %s" % (value, scalePoint['label'])
  296. if len(boxItemValues) == 0:
  297. self.fBox.addItem(label)
  298. boxItemValues.append(value)
  299. else:
  300. if value < boxItemValues[0]:
  301. self.fBox.insertItem(0, label)
  302. boxItemValues.insert(0, value)
  303. elif value > boxItemValues[-1]:
  304. self.fBox.addItem(label)
  305. boxItemValues.append(value)
  306. else:
  307. for index in range(len(boxItemValues)):
  308. if value >= boxItemValues[index]:
  309. self.fBox.insertItem(index+1, label)
  310. boxItemValues.insert(index+1, value)
  311. break
  312. if self.fValue is not None:
  313. self._setScalePointValue(self.fValue)
  314. self.fBox.currentIndexChanged['QString'].connect(self.slot_comboBoxIndexChanged)
  315. def stepBy(self, steps):
  316. if steps == 0 or self.fValue is None:
  317. return
  318. value = self.fValue + (self.fStep * steps)
  319. if value < self.fMinimum:
  320. value = self.fMinimum
  321. elif value > self.fMaximum:
  322. value = self.fMaximum
  323. self.setValue(value)
  324. def stepEnabled(self):
  325. if self.fIsReadOnly or self.fValue is None:
  326. return QAbstractSpinBox.StepNone
  327. if self.fValue <= self.fMinimum:
  328. return QAbstractSpinBox.StepUpEnabled
  329. if self.fValue >= self.fMaximum:
  330. return QAbstractSpinBox.StepDownEnabled
  331. return (QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled)
  332. def updateAll(self):
  333. self.update()
  334. self.fBar.update()
  335. if self.fBox is not None:
  336. self.fBox.update()
  337. def resizeEvent(self, event):
  338. QAbstractSpinBox.resizeEvent(self, event)
  339. self.slot_updateProgressBarGeometry()
  340. @pyqtSlot(str)
  341. def slot_comboBoxIndexChanged(self, boxText):
  342. if self.fIsReadOnly:
  343. return
  344. value = float(boxText.split(" - ", 1)[0])
  345. lastScaleValue = self.fScalePoints[-1]['value']
  346. if value == lastScaleValue:
  347. value = self.fMaximum
  348. self.setValue(value)
  349. @pyqtSlot(float)
  350. def slot_progressBarValueChanged(self, value):
  351. if self.fIsReadOnly:
  352. return
  353. if value <= self.fMinimum:
  354. realValue = self.fMinimum
  355. elif value >= self.fMaximum:
  356. realValue = self.fMaximum
  357. else:
  358. curStep = int((value - self.fMinimum) / self.fStep + 0.5)
  359. realValue = self.fMinimum + (self.fStep * curStep)
  360. if realValue < self.fMinimum:
  361. realValue = self.fMinimum
  362. elif realValue > self.fMaximum:
  363. realValue = self.fMaximum
  364. self.setValue(realValue)
  365. @pyqtSlot()
  366. def slot_showCustomMenu(self):
  367. clipboard = QApplication.instance().clipboard()
  368. pasteText = clipboard.text()
  369. pasteValue = None
  370. if pasteText:
  371. try:
  372. pasteValue = float(pasteText)
  373. except:
  374. pass
  375. menu = QMenu(self)
  376. actReset = menu.addAction(self.tr("Reset (%f)" % self.fDefault))
  377. actRandom = menu.addAction(self.tr("Random"))
  378. menu.addSeparator()
  379. actCopy = menu.addAction(self.tr("Copy (%f)" % self.fValue))
  380. if pasteValue is None:
  381. actPaste = menu.addAction(self.tr("Paste"))
  382. actPaste.setEnabled(False)
  383. else:
  384. actPaste = menu.addAction(self.tr("Paste (%f)" % pasteValue))
  385. menu.addSeparator()
  386. actSet = menu.addAction(self.tr("Set value..."))
  387. if self.fIsReadOnly:
  388. actReset.setEnabled(False)
  389. actRandom.setEnabled(False)
  390. actPaste.setEnabled(False)
  391. actSet.setEnabled(False)
  392. actSel = menu.exec_(QCursor.pos())
  393. if actSel == actReset:
  394. self.setValue(self.fDefault)
  395. elif actSel == actRandom:
  396. value = random() * (self.fMaximum - self.fMinimum) + self.fMinimum
  397. self.setValue(value)
  398. elif actSel == actCopy:
  399. clipboard.setText("%f" % self.fValue)
  400. elif actSel == actPaste:
  401. self.setValue(pasteValue)
  402. elif actSel == actSet:
  403. dialog = CustomInputDialog(self, self.fName, self.fValue, self.fMinimum, self.fMaximum,
  404. self.fStep, self.fStepSmall, self.fScalePoints)
  405. if dialog.exec_():
  406. value = dialog.returnValue()
  407. self.setValue(value)
  408. @pyqtSlot()
  409. def slot_updateProgressBarGeometry(self):
  410. self.fBar.setGeometry(self.lineEdit().geometry())
  411. if self.fUseScalePoints:
  412. self.fBox.setGeometry(self.lineEdit().geometry())
  413. def _getNearestScalePoint(self, realValue):
  414. finalValue = 0.0
  415. for i in range(len(self.fScalePoints)):
  416. scaleValue = self.fScalePoints[i]["value"]
  417. if i == 0:
  418. finalValue = scaleValue
  419. else:
  420. srange1 = abs(realValue - scaleValue)
  421. srange2 = abs(realValue - finalValue)
  422. if srange2 > srange1:
  423. finalValue = scaleValue
  424. return finalValue
  425. def _setScalePointValue(self, value):
  426. value = self._getNearestScalePoint(value)
  427. for i in range(self.fBox.count()):
  428. if float(self.fBox.itemText(i).split(" - ", 1)[0]) == value:
  429. self.fBox.setCurrentIndex(i)
  430. break