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.

409 lines
13KB

  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 modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # 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 COPYING file
  17. # ------------------------------------------------------------------------------------------------------------
  18. # Imports (Global)
  19. from PyQt4.QtCore import pyqtSlot, Qt, QTimer, SIGNAL, SLOT
  20. from PyQt4.QtGui import QAbstractSpinBox, QComboBox, QCursor, QDialog, QInputDialog, QMenu, QPainter, QProgressBar, QValidator
  21. from PyQt4.QtGui import QStyleFactory
  22. from math import isnan
  23. # ------------------------------------------------------------------------------------------------------------
  24. # Imports (Custom)
  25. import ui_inputdialog_value
  26. def fix_value(value, minimum, maximum):
  27. if isnan(value):
  28. print("Parameter is NaN! - %f" % value)
  29. return minimum
  30. elif value < minimum:
  31. print("Parameter too low! - %f/%f" % (value, minimum))
  32. return minimum
  33. elif value > maximum:
  34. print("Parameter too high! - %f/%f" % (value, maximum))
  35. return maximum
  36. else:
  37. return value
  38. #QPlastiqueStyle = QStyleFactory.create("Plastique")
  39. # ------------------------------------------------------------------------------------------------------------
  40. # Custom InputDialog with Scale Points support
  41. class CustomInputDialog(QDialog, ui_inputdialog_value.Ui_Dialog):
  42. def __init__(self, parent, label, current, minimum, maximum, step, scalePoints):
  43. QDialog.__init__(self, parent)
  44. self.setupUi(self)
  45. self.label.setText(label)
  46. self.doubleSpinBox.setMinimum(minimum)
  47. self.doubleSpinBox.setMaximum(maximum)
  48. self.doubleSpinBox.setValue(current)
  49. self.doubleSpinBox.setSingleStep(step)
  50. self.ret_value = current
  51. if not scalePoints:
  52. self.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.textBrowser.setText(text)
  60. self.resize(200, 300)
  61. self.connect(self, SIGNAL("accepted()"), self.setReturnValue)
  62. def setReturnValue(self):
  63. self.ret_value = self.doubleSpinBox.value()
  64. def done(self, r):
  65. QDialog.done(self, r)
  66. self.close()
  67. # ------------------------------------------------------------------------------------------------------------
  68. # Progress-Bar used for ParamSpinBox
  69. class ParamProgressBar(QProgressBar):
  70. def __init__(self, parent):
  71. QProgressBar.__init__(self, parent)
  72. self.m_leftClickDown = False
  73. self.m_minimum = 0.0
  74. self.m_maximum = 1.0
  75. self.m_rvalue = 0.0
  76. self.m_label = ""
  77. self.m_preLabel = " "
  78. self.m_textCall = None
  79. self.setMinimum(0)
  80. self.setMaximum(1000)
  81. self.setValue(0)
  82. self.setFormat("(none)")
  83. def set_minimum(self, value):
  84. self.m_minimum = value
  85. def set_maximum(self, value):
  86. self.m_maximum = value
  87. def set_value(self, value):
  88. self.m_rvalue = value
  89. vper = (value - self.m_minimum) / (self.m_maximum - self.m_minimum)
  90. self.setValue(vper * 1000)
  91. def set_label(self, label):
  92. self.m_label = label.strip()
  93. if self.m_label == "(coef)":
  94. self.m_label = ""
  95. self.m_preLabel = "*"
  96. self.update()
  97. def set_text_call(self, textCall):
  98. self.m_textCall = textCall
  99. def handleMouseEventPos(self, pos):
  100. xper = float(pos.x()) / self.width()
  101. value = xper * (self.m_maximum - self.m_minimum) + self.m_minimum
  102. if value < self.m_minimum:
  103. value = self.m_minimum
  104. elif value > self.m_maximum:
  105. value = self.m_maximum
  106. self.emit(SIGNAL("valueChangedFromBar(double)"), value)
  107. def mousePressEvent(self, event):
  108. if event.button() == Qt.LeftButton:
  109. self.handleMouseEventPos(event.pos())
  110. self.m_leftClickDown = True
  111. else:
  112. self.m_leftClickDown = False
  113. QProgressBar.mousePressEvent(self, event)
  114. def mouseMoveEvent(self, event):
  115. if self.m_leftClickDown:
  116. self.handleMouseEventPos(event.pos())
  117. QProgressBar.mouseMoveEvent(self, event)
  118. def mouseReleaseEvent(self, event):
  119. self.m_leftClickDown = False
  120. QProgressBar.mouseReleaseEvent(self, event)
  121. def paintEvent(self, event):
  122. if self.m_textCall:
  123. self.setFormat("%s %s %s" % (self.m_preLabel, self.m_textCall(), self.m_label))
  124. else:
  125. self.setFormat("%s %f %s" % (self.m_preLabel, self.m_rvalue, self.m_label))
  126. QProgressBar.paintEvent(self, event)
  127. # ------------------------------------------------------------------------------------------------------------
  128. # Special SpinBox used for parameters
  129. class ParamSpinBox(QAbstractSpinBox):
  130. def __init__(self, parent):
  131. QAbstractSpinBox.__init__(self, parent)
  132. self._minimum = 0.0
  133. self._maximum = 1.0
  134. self._default = 0.0
  135. self._value = None
  136. self._step = 0.0
  137. self._step_small = 0.0
  138. self._step_large = 0.0
  139. self._read_only = False
  140. self._scalepoints = None
  141. self._have_scalepoints = False
  142. self.bar = ParamProgressBar(self)
  143. self.bar.setContextMenuPolicy(Qt.NoContextMenu)
  144. self.bar.show()
  145. self.lineEdit().setVisible(False)
  146. self.connect(self.bar, SIGNAL("valueChangedFromBar(double)"), self.handleValueChangedFromBar)
  147. self.connect(self, SIGNAL("customContextMenuRequested(QPoint)"), self.showCustomMenu)
  148. QTimer.singleShot(0, self, SLOT("slot_updateBarGeometry()"))
  149. #def force_plastique_style(self):
  150. #self.setStyle(QPlastiqueStyle)
  151. def set_minimum(self, value):
  152. self._minimum = value
  153. self.bar.set_minimum(value)
  154. def set_maximum(self, value):
  155. self._maximum = value
  156. self.bar.set_maximum(value)
  157. def set_default(self, value):
  158. value = fix_value(value, self._minimum, self._maximum)
  159. self._default = value
  160. def set_value(self, value, send=True):
  161. value = fix_value(value, self._minimum, self._maximum)
  162. if self._value != value:
  163. self._value = value
  164. self.bar.set_value(value)
  165. if self._have_scalepoints:
  166. self.set_scalepoint_value(value)
  167. if send:
  168. self.emit(SIGNAL("valueChanged(double)"), value)
  169. self.update()
  170. return True
  171. else:
  172. return False
  173. def set_step(self, value):
  174. if value == 0.0:
  175. self._step = 0.001
  176. else:
  177. self._step = value
  178. if self._step_small > value:
  179. self._step_small = value
  180. if self._step_large < value:
  181. self._step_large = value
  182. def set_step_small(self, value):
  183. if value == 0.0:
  184. self._step_small = 0.0001
  185. elif value > self._step:
  186. self._step_small = self._step
  187. else:
  188. self._step_small = value
  189. def set_step_large(self, value):
  190. if value == 0.0:
  191. self._step_large = 0.1
  192. elif value < self._step:
  193. self._step_large = self._step
  194. else:
  195. self._step_large = value
  196. def set_label(self, label):
  197. self.bar.set_label(label)
  198. def set_text_call(self, textCall):
  199. self.bar.set_text_call(textCall)
  200. def set_read_only(self, yesno):
  201. self.setButtonSymbols(QAbstractSpinBox.UpDownArrows if yesno else QAbstractSpinBox.NoButtons)
  202. self._read_only = yesno
  203. self.setReadOnly(yesno)
  204. def set_scalepoints(self, scalepoints, use_scalepoints):
  205. if len(scalepoints) > 0:
  206. self._scalepoints = scalepoints
  207. self._have_scalepoints = use_scalepoints
  208. if use_scalepoints:
  209. # Hide ProgressBar and create a ComboBox
  210. self.bar.close()
  211. self.box = QComboBox(self)
  212. self.box.setContextMenuPolicy(Qt.NoContextMenu)
  213. self.box.show()
  214. self.slot_updateBarGeometry()
  215. for scalepoint in scalepoints:
  216. self.box.addItem("%f - %s" % (scalepoint['value'], scalepoint['label']))
  217. if self._value != None:
  218. self.set_scalepoint_value(self._value)
  219. self.connect(self.box, SIGNAL("currentIndexChanged(QString)"), self.handleValueChangedFromBox)
  220. else:
  221. self._scalepoints = None
  222. def set_scalepoint_value(self, value):
  223. value = self.get_nearest_scalepoint(value)
  224. for i in range(self.box.count()):
  225. if float(self.box.itemText(i).split(" - ", 1)[0] == value):
  226. self.box.setCurrentIndex(i)
  227. break
  228. def get_nearest_scalepoint(self, real_value):
  229. final_value = 0.0
  230. for i in range(len(self._scalepoints)):
  231. scale_value = self._scalepoints[i]['value']
  232. if i == 0:
  233. final_value = scale_value
  234. else:
  235. srange1 = abs(real_value - scale_value)
  236. srange2 = abs(real_value - final_value)
  237. if srange2 > srange1:
  238. final_value = scale_value
  239. return final_value
  240. def handleValueChangedFromBar(self, value):
  241. if self._read_only:
  242. return
  243. step = int(0.5 + ((value - self._minimum) / self._step))
  244. real_value = self._minimum + (step * self._step)
  245. self.set_value(real_value)
  246. def handleValueChangedFromBox(self, box_text):
  247. if self._read_only:
  248. return
  249. value = float(box_text.split(" - ", 1)[0])
  250. last_scale_value = self._scalepoints[len(self._scalepoints) - 1]['value']
  251. if value == last_scale_value:
  252. value = self._maximum
  253. self.set_value(value)
  254. def showCustomMenu(self, pos):
  255. menu = QMenu(self)
  256. act_x_reset = menu.addAction(self.tr("Reset (%f)" % self._default))
  257. menu.addSeparator()
  258. act_x_copy = menu.addAction(self.tr("Copy (%f)" % self._value))
  259. if False and not self._read_only:
  260. act_x_paste = menu.addAction(self.tr("Paste (%s)" % "TODO"))
  261. else:
  262. act_x_paste = menu.addAction(self.tr("Paste"))
  263. act_x_paste.setEnabled(False)
  264. menu.addSeparator()
  265. act_x_set = menu.addAction(self.tr("Set value..."))
  266. if self._read_only:
  267. act_x_reset.setEnabled(False)
  268. act_x_paste.setEnabled(False)
  269. act_x_set.setEnabled(False)
  270. # TODO - NOT IMPLEMENTED YET
  271. act_x_copy.setEnabled(False)
  272. act_x_sel = menu.exec_(QCursor.pos())
  273. if act_x_sel == act_x_set:
  274. dialog = CustomInputDialog(self, self.parent().label.text(), self._value, self._minimum, self._maximum, self._step, self._scalepoints)
  275. if dialog.exec_():
  276. value = dialog.ret_value
  277. self.set_value(value)
  278. elif act_x_sel == act_x_copy:
  279. pass
  280. elif act_x_sel == act_x_paste:
  281. pass
  282. elif act_x_sel == act_x_reset:
  283. self.set_value(self._default)
  284. def stepBy(self, steps):
  285. if steps == 0 or self._value == None:
  286. return
  287. value = self._value + (steps * self._step)
  288. if value < self._minimum:
  289. value = self._minimum
  290. elif value > self._maximum:
  291. value = self._maximum
  292. self.set_value(value)
  293. def stepEnabled(self):
  294. if self._read_only or self._value == None:
  295. return QAbstractSpinBox.StepNone
  296. elif self._value <= self._minimum:
  297. return QAbstractSpinBox.StepUpEnabled
  298. elif self._value >= self._maximum:
  299. return QAbstractSpinBox.StepDownEnabled
  300. else:
  301. return QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled
  302. def updateAll(self):
  303. self.update()
  304. self.bar.update()
  305. if self._have_scalepoints:
  306. self.box.update()
  307. @pyqtSlot()
  308. def slot_updateBarGeometry(self):
  309. self.bar.setGeometry(self.lineEdit().geometry())
  310. if self._have_scalepoints:
  311. self.box.setGeometry(self.lineEdit().geometry())
  312. def resizeEvent(self, event):
  313. QTimer.singleShot(0, self, SLOT("slot_updateBarGeometry()"))
  314. QAbstractSpinBox.resizeEvent(self, event)