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.

racklistwidget.py 13KB

9 years ago
9 years ago
9 years ago
9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Rack List Widget, a custom Qt4 widget
  4. # Copyright (C) 2011-2014 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 (Config)
  19. from carla_config import *
  20. # ------------------------------------------------------------------------------------------------------------
  21. # Imports (Global)
  22. if config_UseQt5:
  23. from PyQt5.QtCore import Qt, QSize
  24. from PyQt5.QtGui import QPainter, QPixmap
  25. from PyQt5.QtWidgets import QAbstractItemView, QFrame, QListWidget, QListWidgetItem
  26. else:
  27. from PyQt4.QtCore import Qt, QSize
  28. from PyQt4.QtGui import QAbstractItemView, QFrame, QListWidget, QListWidgetItem, QPainter, QPixmap
  29. # ------------------------------------------------------------------------------------------------------------
  30. # Imports (Custom Stuff)
  31. from carla_skin import *
  32. # ------------------------------------------------------------------------------------------------------------
  33. # Rack Widget item
  34. class RackListItem(QListWidgetItem):
  35. kRackItemType = QListWidgetItem.UserType + 1
  36. kMinimumWidth = 620
  37. def __init__(self, parent, pluginId):
  38. QListWidgetItem.__init__(self, parent, self.kRackItemType)
  39. self.host = parent.host
  40. if False:
  41. # kdevelop likes this :)
  42. parent = RackListWidget()
  43. host = CarlaHostNull()
  44. self.host = host
  45. self.fWidget = AbstractPluginSlot()
  46. # ----------------------------------------------------------------------------------------------------
  47. # Internal stuff
  48. self.fParent = parent
  49. self.fPluginId = pluginId
  50. self.fWidget = None
  51. color = self.host.get_custom_data_value(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaColor")
  52. skin = self.host.get_custom_data_value(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaSkin")
  53. compact = bool(self.host.get_custom_data_value(pluginId, CUSTOM_DATA_TYPE_PROPERTY, "CarlaSkinIsCompacted") == "true")
  54. if color:
  55. try:
  56. color = tuple(int(i) for i in color.split(";",3))
  57. except:
  58. print("Color value decode failed for", color)
  59. color = None
  60. else:
  61. color = None
  62. self.fOptions = {
  63. 'color' : color,
  64. 'skin' : skin,
  65. 'compact': compact and skin != "classic",
  66. }
  67. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  68. #self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsDragEnabled)
  69. # ----------------------------------------------------------------------------------------------------
  70. # Set-up GUI
  71. self.recreateWidget(firstInit = True)
  72. # --------------------------------------------------------------------------------------------------------
  73. def close(self):
  74. if self.fWidget is None:
  75. return
  76. widget = self.fWidget
  77. self.fWidget = None
  78. self.fParent.customClearSelection()
  79. self.fParent.setItemWidget(self, None)
  80. widget.fEditDialog.close()
  81. widget.fEditDialog.setParent(None)
  82. widget.fEditDialog.deleteLater()
  83. del widget.fEditDialog
  84. widget.close()
  85. widget.setParent(None)
  86. widget.deleteLater()
  87. del widget
  88. def getEditDialog(self):
  89. if self.fWidget is None:
  90. return None
  91. return self.fWidget.fEditDialog
  92. def getPluginId(self):
  93. return self.fPluginId
  94. def getWidget(self):
  95. return self.fWidget
  96. def isCompacted(self):
  97. return self.fOptions['compact']
  98. def isGuiShown(self):
  99. if self.fWidget is None or self.fWidget.b_gui is not None:
  100. return None
  101. return self.fWidget.b_gui.isChecked()
  102. # --------------------------------------------------------------------------------------------------------
  103. def setPluginId(self, pluginId):
  104. self.fPluginId = pluginId
  105. if self.fWidget is not None:
  106. self.fWidget.setPluginId(pluginId)
  107. def setSelected(self, select):
  108. if self.fWidget is not None:
  109. self.fWidget.setSelected(select)
  110. QListWidgetItem.setSelected(self, select)
  111. # --------------------------------------------------------------------------------------------------------
  112. def setCompacted(self, compact):
  113. self.fOptions['compact'] = compact
  114. # --------------------------------------------------------------------------------------------------------
  115. def compact(self):
  116. if self.fOptions['compact']:
  117. return
  118. self.recreateWidget(True)
  119. def expand(self):
  120. if not self.fOptions['compact']:
  121. return
  122. self.recreateWidget(True)
  123. def recreateWidget(self, invertCompactOption = False, firstInit = False, newColor = None, newSkin = None):
  124. if invertCompactOption:
  125. self.fOptions['compact'] = not self.fOptions['compact']
  126. if newColor is not None:
  127. self.fOptions['color'] = newColor
  128. if newSkin is not None:
  129. self.fOptions['skin'] = newSkin
  130. wasGuiShown = None
  131. if self.fWidget is not None and self.fWidget.b_gui is not None:
  132. wasGuiShown = self.fWidget.b_gui.isChecked()
  133. self.close()
  134. self.fWidget = createPluginSlot(self.fParent, self.host, self.fPluginId, self.fOptions)
  135. self.fWidget.setFixedHeight(self.fWidget.getFixedHeight())
  136. if wasGuiShown and self.fWidget.b_gui is not None:
  137. self.fWidget.b_gui.setChecked(True)
  138. self.setSizeHint(QSize(self.kMinimumWidth, self.fWidget.getFixedHeight()))
  139. self.fParent.setItemWidget(self, self.fWidget)
  140. if not firstInit:
  141. self.host.set_custom_data(self.fPluginId, CUSTOM_DATA_TYPE_PROPERTY,
  142. "CarlaSkinIsCompacted", "true" if self.fOptions['compact'] else "false")
  143. def recreateWidget2(self, wasCompacted, wasGuiShown):
  144. self.fOptions['compact'] = wasCompacted
  145. self.close()
  146. self.fWidget = createPluginSlot(self.fParent, self.host, self.fPluginId, self.fOptions)
  147. self.fWidget.setFixedHeight(self.fWidget.getFixedHeight())
  148. if wasGuiShown and self.fWidget.b_gui is not None:
  149. self.fWidget.b_gui.setChecked(True)
  150. self.setSizeHint(QSize(self.kMinimumWidth, self.fWidget.getFixedHeight()))
  151. self.fParent.setItemWidget(self, self.fWidget)
  152. self.host.set_custom_data(self.fPluginId, CUSTOM_DATA_TYPE_PROPERTY,
  153. "CarlaSkinIsCompacted", "true" if wasCompacted else "false")
  154. # ------------------------------------------------------------------------------------------------------------
  155. # Rack Widget
  156. class RackListWidget(QListWidget):
  157. def __init__(self, parent):
  158. QListWidget.__init__(self, parent)
  159. self.host = None
  160. self.fParent = None
  161. if False:
  162. # kdevelop likes this :)
  163. from carla_backend import CarlaHostMeta
  164. self.host = host = CarlaHostNull()
  165. exts = gCarla.utils.get_supported_file_extensions()
  166. self.fSupportedExtensions = tuple(("." + i) for i in exts)
  167. self.fLastSelectedItem = None
  168. self.fWasLastDragValid = False
  169. self.fPixmapL = QPixmap(":/bitmaps/rack_interior_left.png")
  170. self.fPixmapR = QPixmap(":/bitmaps/rack_interior_right.png")
  171. self.fPixmapWidth = self.fPixmapL.width()
  172. self.setMinimumWidth(RackListItem.kMinimumWidth)
  173. self.setSelectionMode(QAbstractItemView.SingleSelection)
  174. self.setSortingEnabled(False)
  175. self.setDragEnabled(True)
  176. self.setDragDropMode(QAbstractItemView.DropOnly)
  177. self.setDropIndicatorShown(True)
  178. self.viewport().setAcceptDrops(True)
  179. self.setFrameShape(QFrame.NoFrame)
  180. self.setFrameShadow(QFrame.Plain)
  181. # --------------------------------------------------------------------------------------------------------
  182. def createItem(self, pluginId):
  183. return RackListItem(self, pluginId)
  184. def getPluginCount(self):
  185. return self.fParent.getPluginCount()
  186. def setHostAndParent(self, host, parent):
  187. self.host = host
  188. self.fParent = parent
  189. # --------------------------------------------------------------------------------------------------------
  190. def customClearSelection(self):
  191. self.setCurrentRow(-1)
  192. self.clearSelection()
  193. self.clearFocus()
  194. def isDragUrlValid(self, filename):
  195. if os.path.isdir(filename):
  196. #if os.path.exists(os.path.join(filename, "manifest.ttl")):
  197. #return True
  198. if MACOS and filename.lower().endswith(".vst"):
  199. return True
  200. elif os.path.isfile(filename):
  201. if filename.lower().endswith(self.fSupportedExtensions):
  202. return True
  203. return False
  204. # --------------------------------------------------------------------------------------------------------
  205. def dragEnterEvent(self, event):
  206. urls = event.mimeData().urls()
  207. for url in urls:
  208. if self.isDragUrlValid(url.toLocalFile()):
  209. self.fWasLastDragValid = True
  210. event.acceptProposedAction()
  211. return
  212. self.fWasLastDragValid = False
  213. QListWidget.dragEnterEvent(self, event)
  214. def dragMoveEvent(self, event):
  215. if not self.fWasLastDragValid:
  216. QListWidget.dragMoveEvent(self, event)
  217. return
  218. event.acceptProposedAction()
  219. tryItem = self.itemAt(event.pos())
  220. if tryItem is not None:
  221. self.setCurrentRow(tryItem.getPluginId())
  222. else:
  223. self.setCurrentRow(-1)
  224. def dragLeaveEvent(self, event):
  225. self.fWasLastDragValid = False
  226. QListWidget.dragLeaveEvent(self, event)
  227. # --------------------------------------------------------------------------------------------------------
  228. # FIXME: this needs some attention
  229. # if dropping project file over 1 plugin, load it in rack or patchbay
  230. # if dropping regular files over 1 plugin, keep replacing plugins
  231. def dropEvent(self, event):
  232. event.acceptProposedAction()
  233. urls = event.mimeData().urls()
  234. if len(urls) == 0:
  235. return
  236. tryItem = self.itemAt(event.pos())
  237. if tryItem is not None:
  238. pluginId = tryItem.getPluginId()
  239. else:
  240. pluginId = -1
  241. for url in urls:
  242. if pluginId >= 0:
  243. self.host.replace_plugin(pluginId)
  244. pluginId += 1
  245. if pluginId > self.host.get_current_plugin_count():
  246. pluginId = -1
  247. filename = url.toLocalFile()
  248. if not self.host.load_file(filename):
  249. CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"),
  250. self.tr("Failed to load file"),
  251. self.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok)
  252. if tryItem is not None:
  253. self.host.replace_plugin(self.host.get_max_plugin_number())
  254. #tryItem.widget.setActive(True, True, True)
  255. # --------------------------------------------------------------------------------------------------------
  256. def mousePressEvent(self, event):
  257. if self.itemAt(event.pos()) is None and self.currentRow() != -1:
  258. event.accept()
  259. self.customClearSelection()
  260. return
  261. QListWidget.mousePressEvent(self, event)
  262. def paintEvent(self, event):
  263. painter = QPainter(self.viewport())
  264. painter.drawTiledPixmap(0, 0, self.fPixmapWidth, self.height(), self.fPixmapL)
  265. painter.drawTiledPixmap(self.width()-self.fPixmapWidth, 0, self.fPixmapWidth, self.height(), self.fPixmapR)
  266. QListWidget.paintEvent(self, event)
  267. # --------------------------------------------------------------------------------------------------------
  268. def selectionChanged(self, selected, deselected):
  269. for index in deselected.indexes():
  270. item = self.itemFromIndex(index)
  271. if item is not None:
  272. item.setSelected(False)
  273. for index in selected.indexes():
  274. item = self.itemFromIndex(index)
  275. if item is not None:
  276. item.setSelected(True)
  277. QListWidget.selectionChanged(self, selected, deselected)
  278. # ------------------------------------------------------------------------------------------------------------