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

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