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.

488 lines
15KB

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