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

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