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.

473 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. 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