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

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