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