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.

carla_rack.py 22KB

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
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # Carla rack widget code
  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 (Global)
  19. from PyQt4.QtCore import Qt, QSize, QTimer
  20. from PyQt4.QtGui import QAbstractItemView, QApplication, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPixmap, QScrollBar
  21. # ------------------------------------------------------------------------------------------------------------
  22. # Imports (Custom Stuff)
  23. from carla_skin import *
  24. # ------------------------------------------------------------------------------------------------------------
  25. # Rack widget item
  26. class CarlaRackItem(QListWidgetItem):
  27. kRackItemType = QListWidgetItem.UserType + 1
  28. def __init__(self, parent, pluginId):
  29. QListWidgetItem.__init__(self, parent, self.kRackItemType)
  30. self.fParent = parent
  31. self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
  32. #self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemIsDragEnabled|Qt.ItemIsDropEnabled)
  33. self.createWidget(pluginId)
  34. # -----------------------------------------------------------------
  35. def createWidget(self, pluginId):
  36. self.widget = createPluginSlot(self.fParent, pluginId)
  37. self.widget.setFixedHeight(self.widget.getFixedHeight())
  38. self.setSizeHint(QSize(640, self.widget.getFixedHeight()))
  39. self.fParent.setItemWidget(self, self.widget)
  40. # -----------------------------------------------------------------
  41. def close(self):
  42. self.widget.fEditDialog.close()
  43. def reloadAll(self, pluginId):
  44. self.widget.fEditDialog.close()
  45. del self.widget
  46. self.createWidget(pluginId)
  47. # ------------------------------------------------------------------------------------------------------------
  48. # Rack widget list
  49. class CarlaRackList(QListWidget):
  50. def __init__(self, parent):
  51. QListWidget.__init__(self, parent)
  52. exts = gCarla.host.get_supported_file_extensions().split(";") if gCarla.host is not None else ["wav",]
  53. # plugin files
  54. exts.append("dll")
  55. if MACOS:
  56. exts.append("dylib")
  57. if not WINDOWS:
  58. exts.append("so")
  59. self.fSupportedExtensions = tuple(i.replace("*.","") for i in exts)
  60. self.fWasLastDragValid = False
  61. self.setMinimumWidth(640+20) # required by zita, 591 was old value
  62. self.setSelectionMode(QAbstractItemView.SingleSelection)
  63. self.setSortingEnabled(False)
  64. #self.setSortingEnabled(True)
  65. self.setDragEnabled(True)
  66. self.setDragDropMode(QAbstractItemView.DropOnly)
  67. self.setDropIndicatorShown(True)
  68. self.viewport().setAcceptDrops(True)
  69. self.fPixmapL = QPixmap(":/bitmaps/rack_interior_left.png")
  70. self.fPixmapR = QPixmap(":/bitmaps/rack_interior_right.png")
  71. self.fPixmapWidth = self.fPixmapL.width()
  72. def isDragEventValid(self, urls):
  73. for url in urls:
  74. filename = url.toLocalFile()
  75. if os.path.isdir(filename):
  76. if os.path.exists(os.path.join(filename, "manifest.ttl")):
  77. return True
  78. elif os.path.isfile(filename):
  79. if filename.lower().endswith(self.fSupportedExtensions):
  80. return True
  81. return False
  82. def dragEnterEvent(self, event):
  83. if self.isDragEventValid(event.mimeData().urls()):
  84. self.fWasLastDragValid = True
  85. event.acceptProposedAction()
  86. return
  87. self.fWasLastDragValid = False
  88. QListWidget.dragEnterEvent(self, event)
  89. def dragMoveEvent(self, event):
  90. if self.fWasLastDragValid:
  91. event.acceptProposedAction()
  92. tryItem = self.itemAt(event.pos())
  93. if tryItem is not None:
  94. self.setCurrentRow(tryItem.widget.getPluginId())
  95. else:
  96. self.setCurrentRow(-1)
  97. return
  98. QListWidget.dragMoveEvent(self, event)
  99. #def dragLeaveEvent(self, event):
  100. #self.fWasLastDragValid = False
  101. #QListWidget.dragLeaveEvent(self, event)
  102. def dropEvent(self, event):
  103. event.acceptProposedAction()
  104. urls = event.mimeData().urls()
  105. if len(urls) == 0:
  106. return
  107. tryItem = self.itemAt(event.pos())
  108. if tryItem is not None:
  109. pluginId = tryItem.widget.getPluginId()
  110. gCarla.host.replace_plugin(pluginId)
  111. for url in urls:
  112. filename = url.toLocalFile()
  113. if not gCarla.host.load_file(filename):
  114. CustomMessageBox(self, QMessageBox.Critical, self.tr("Error"),
  115. self.tr("Failed to load file"),
  116. gCarla.host.get_last_error(), QMessageBox.Ok, QMessageBox.Ok)
  117. if tryItem is not None:
  118. gCarla.host.replace_plugin(self.parent().fPluginCount)
  119. tryItem.widget.setActive(True, True, True)
  120. def mousePressEvent(self, event):
  121. if self.itemAt(event.pos()) is None:
  122. event.accept()
  123. self.setCurrentRow(-1)
  124. return
  125. QListWidget.mousePressEvent(self, event)
  126. def paintEvent(self, event):
  127. painter = QPainter(self.viewport())
  128. painter.drawTiledPixmap(0, 0, self.fPixmapWidth, self.height(), self.fPixmapL)
  129. painter.drawTiledPixmap(self.width()-self.fPixmapWidth-2, 0, self.fPixmapWidth, self.height(), self.fPixmapR)
  130. QListWidget.paintEvent(self, event)
  131. # ------------------------------------------------------------------------------------------------------------
  132. # Rack widget
  133. class CarlaRackW(QFrame):
  134. def __init__(self, parent, doSetup = True):
  135. QFrame.__init__(self, parent)
  136. self.fLayout = QHBoxLayout(self)
  137. self.fLayout.setContentsMargins(0, 0, 0, 0)
  138. self.fLayout.setSpacing(0)
  139. self.setLayout(self.fLayout)
  140. self.fPadLeft = QLabel(self)
  141. self.fPadLeft.setFixedWidth(25)
  142. self.fPadLeft.setObjectName("PadLeft")
  143. self.fPadLeft.setText("")
  144. self.fPadRight = QLabel(self)
  145. self.fPadRight.setFixedWidth(25)
  146. self.fPadRight.setObjectName("PadRight")
  147. self.fPadRight.setText("")
  148. self.fRack = CarlaRackList(self)
  149. self.fRack.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  150. self.fRack.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
  151. self.fRack.currentRowChanged.connect(self.slot_currentRowChanged)
  152. sb = self.fRack.verticalScrollBar()
  153. self.fScrollBar = QScrollBar(Qt.Vertical, self)
  154. self.fScrollBar.setMinimum(sb.minimum())
  155. self.fScrollBar.setMaximum(sb.maximum())
  156. self.fScrollBar.setValue(sb.value())
  157. #sb.actionTriggered.connect(self.fScrollBar.triggerAction)
  158. #sb.sliderMoved.connect(self.fScrollBar.)
  159. #sb.sliderPressed.connect(self.fScrollBar.)
  160. #sb.sliderReleased.connect(self.fScrollBar.)
  161. sb.rangeChanged.connect(self.fScrollBar.setRange)
  162. sb.valueChanged.connect(self.fScrollBar.setValue)
  163. self.fScrollBar.rangeChanged.connect(sb.setRange)
  164. self.fScrollBar.valueChanged.connect(sb.setValue)
  165. self.fLayout.addWidget(self.fPadLeft)
  166. self.fLayout.addWidget(self.fRack)
  167. self.fLayout.addWidget(self.fPadRight)
  168. self.fLayout.addWidget(self.fScrollBar)
  169. # -------------------------------------------------------------
  170. # Internal stuff
  171. self.fParent = parent
  172. self.fPluginCount = 0
  173. self.fPluginList = []
  174. self.fCurrentRow = -1
  175. self.fLastSelectedItem = None
  176. # -------------------------------------------------------------
  177. # Set-up GUI stuff
  178. #app = QApplication.instance()
  179. #pal1 = app.palette().base().color()
  180. #pal2 = app.palette().button().color()
  181. #col1 = "stop:0 rgb(%i, %i, %i)" % (pal1.red(), pal1.green(), pal1.blue())
  182. #col2 = "stop:1 rgb(%i, %i, %i)" % (pal2.red(), pal2.green(), pal2.blue())
  183. self.setStyleSheet("""
  184. QLabel#PadLeft {
  185. background-image: url(:/bitmaps/rack_padding_left.png);
  186. background-repeat: repeat-y;
  187. }
  188. QLabel#PadRight {
  189. background-image: url(:/bitmaps/rack_padding_right.png);
  190. background-repeat: repeat-y;
  191. }
  192. QListWidget {
  193. background-color: black;
  194. }
  195. """)
  196. # -------------------------------------------------------------
  197. # Connect actions to functions
  198. if not doSetup: return
  199. parent.ui.menu_Canvas.hide()
  200. parent.ui.act_plugins_enable.triggered.connect(self.slot_pluginsEnable)
  201. parent.ui.act_plugins_disable.triggered.connect(self.slot_pluginsDisable)
  202. parent.ui.act_plugins_volume100.triggered.connect(self.slot_pluginsVolume100)
  203. parent.ui.act_plugins_mute.triggered.connect(self.slot_pluginsMute)
  204. parent.ui.act_plugins_wet100.triggered.connect(self.slot_pluginsWet100)
  205. parent.ui.act_plugins_bypass.triggered.connect(self.slot_pluginsBypass)
  206. parent.ui.act_plugins_center.triggered.connect(self.slot_pluginsCenter)
  207. parent.ui.act_plugins_panic.triggered.connect(self.slot_pluginsDisable)
  208. parent.ui.act_settings_configure.triggered.connect(self.slot_configureCarla)
  209. parent.ParameterValueChangedCallback.connect(self.slot_handleParameterValueChangedCallback)
  210. parent.ParameterDefaultChangedCallback.connect(self.slot_handleParameterDefaultChangedCallback)
  211. parent.ParameterMidiChannelChangedCallback.connect(self.slot_handleParameterMidiChannelChangedCallback)
  212. parent.ParameterMidiCcChangedCallback.connect(self.slot_handleParameterMidiCcChangedCallback)
  213. parent.ProgramChangedCallback.connect(self.slot_handleProgramChangedCallback)
  214. parent.MidiProgramChangedCallback.connect(self.slot_handleMidiProgramChangedCallback)
  215. parent.UiStateChangedCallback.connect(self.slot_handleUiStateChangedCallback)
  216. parent.NoteOnCallback.connect(self.slot_handleNoteOnCallback)
  217. parent.NoteOffCallback.connect(self.slot_handleNoteOffCallback)
  218. parent.UpdateCallback.connect(self.slot_handleUpdateCallback)
  219. parent.ReloadInfoCallback.connect(self.slot_handleReloadInfoCallback)
  220. parent.ReloadParametersCallback.connect(self.slot_handleReloadParametersCallback)
  221. parent.ReloadProgramsCallback.connect(self.slot_handleReloadProgramsCallback)
  222. parent.ReloadAllCallback.connect(self.slot_handleReloadAllCallback)
  223. # -----------------------------------------------------------------
  224. def getPluginCount(self):
  225. return self.fPluginCount
  226. # -----------------------------------------------------------------
  227. def addPlugin(self, pluginId, isProjectLoading):
  228. pitem = CarlaRackItem(self.fRack, pluginId)
  229. self.fPluginList.append(pitem)
  230. self.fPluginCount += 1
  231. if not isProjectLoading:
  232. pitem.widget.setActive(True, True, True)
  233. def removePlugin(self, pluginId):
  234. if pluginId >= self.fPluginCount:
  235. return
  236. pitem = self.fPluginList[pluginId]
  237. if pitem is None:
  238. return
  239. self.fPluginCount -= 1
  240. self.fPluginList.pop(pluginId)
  241. self.fRack.takeItem(pluginId)
  242. pitem.close()
  243. del pitem
  244. # push all plugins 1 slot back
  245. for i in range(pluginId, self.fPluginCount):
  246. pitem = self.fPluginList[i]
  247. pitem.widget.setId(i)
  248. def renamePlugin(self, pluginId, newName):
  249. if pluginId >= self.fPluginCount:
  250. return
  251. pitem = self.fPluginList[pluginId]
  252. if pitem is None:
  253. return
  254. pitem.widget.setName(newName)
  255. def disablePlugin(self, pluginId, errorMsg):
  256. if pluginId >= self.fPluginCount:
  257. return
  258. pitem = self.fPluginList[pluginId]
  259. if pitem is None:
  260. return
  261. def removeAllPlugins(self):
  262. while self.fRack.takeItem(0):
  263. pass
  264. for i in range(self.fPluginCount):
  265. pitem = self.fPluginList[i]
  266. if pitem is None:
  267. break
  268. pitem.close()
  269. del pitem
  270. self.fPluginCount = 0
  271. self.fPluginList = []
  272. # -----------------------------------------------------------------
  273. def engineStarted(self):
  274. pass
  275. def engineStopped(self):
  276. pass
  277. def engineChanged(self):
  278. pass
  279. # -----------------------------------------------------------------
  280. def idleFast(self):
  281. for i in range(self.fPluginCount):
  282. pitem = self.fPluginList[i]
  283. if pitem is None:
  284. break
  285. pitem.widget.idleFast()
  286. def idleSlow(self):
  287. for i in range(self.fPluginCount):
  288. pitem = self.fPluginList[i]
  289. if pitem is None:
  290. break
  291. pitem.widget.idleSlow()
  292. # -----------------------------------------------------------------
  293. def projectLoadingStarted(self):
  294. self.fRack.setEnabled(False)
  295. def projectLoadingFinished(self):
  296. self.fRack.setEnabled(True)
  297. # -----------------------------------------------------------------
  298. def saveSettings(self, settings):
  299. pass
  300. def showEditDialog(self, pluginId):
  301. if pluginId >= self.fPluginCount:
  302. return
  303. pitem = self.fPluginList[pluginId]
  304. if pitem is None:
  305. return
  306. pitem.widget.slot_showEditDialog(True)
  307. # -----------------------------------------------------------------
  308. @pyqtSlot()
  309. def slot_pluginsEnable(self):
  310. if not gCarla.host.is_engine_running():
  311. return
  312. for i in range(self.fPluginCount):
  313. pitem = self.fPluginList[i]
  314. if pitem is None:
  315. break
  316. pitem.widget.setActive(True, True, True)
  317. @pyqtSlot()
  318. def slot_pluginsDisable(self):
  319. if not gCarla.host.is_engine_running():
  320. return
  321. for i in range(self.fPluginCount):
  322. pitem = self.fPluginList[i]
  323. if pitem is None:
  324. break
  325. pitem.widget.setActive(False, True, True)
  326. @pyqtSlot()
  327. def slot_pluginsVolume100(self):
  328. if not gCarla.host.is_engine_running():
  329. return
  330. for i in range(self.fPluginCount):
  331. pitem = self.fPluginList[i]
  332. if pitem is None:
  333. break
  334. pitem.widget.setInternalParameter(PLUGIN_CAN_VOLUME, 1.0)
  335. @pyqtSlot()
  336. def slot_pluginsMute(self):
  337. if not gCarla.host.is_engine_running():
  338. return
  339. for i in range(self.fPluginCount):
  340. pitem = self.fPluginList[i]
  341. if pitem is None:
  342. break
  343. pitem.widget.setInternalParameter(PLUGIN_CAN_VOLUME, 0.0)
  344. @pyqtSlot()
  345. def slot_pluginsWet100(self):
  346. if not gCarla.host.is_engine_running():
  347. return
  348. for i in range(self.fPluginCount):
  349. pitem = self.fPluginList[i]
  350. if pitem is None:
  351. break
  352. pitem.widget.setInternalParameter(PLUGIN_CAN_DRYWET, 1.0)
  353. @pyqtSlot()
  354. def slot_pluginsBypass(self):
  355. if not gCarla.host.is_engine_running():
  356. return
  357. for i in range(self.fPluginCount):
  358. pitem = self.fPluginList[i]
  359. if pitem is None:
  360. break
  361. pitem.widget.setInternalParameter(PLUGIN_CAN_DRYWET, 0.0)
  362. @pyqtSlot()
  363. def slot_pluginsCenter(self):
  364. if not gCarla.host.is_engine_running():
  365. return
  366. for i in range(self.fPluginCount):
  367. pitem = self.fPluginList[i]
  368. if pitem is None:
  369. break
  370. pitem.widget.setInternalParameter(PARAMETER_BALANCE_LEFT, -1.0)
  371. pitem.widget.setInternalParameter(PARAMETER_BALANCE_RIGHT, 1.0)
  372. pitem.widget.setInternalParameter(PARAMETER_PANNING, 0.0)
  373. # -----------------------------------------------------------------
  374. @pyqtSlot()
  375. def slot_configureCarla(self):
  376. if self.fParent is None or not self.fParent.openSettingsWindow(False, False):
  377. return
  378. self.fParent.loadSettings(False)
  379. # -----------------------------------------------------------------
  380. @pyqtSlot(int, int, float)
  381. def slot_handleParameterValueChangedCallback(self, pluginId, index, value):
  382. if pluginId >= self.fPluginCount:
  383. return
  384. pitem = self.fPluginList[pluginId]
  385. if pitem is None:
  386. return
  387. pitem.widget.setParameterValue(index, value, True)
  388. @pyqtSlot(int, int, float)
  389. def slot_handleParameterDefaultChangedCallback(self, pluginId, index, value):
  390. if pluginId >= self.fPluginCount:
  391. return
  392. pitem = self.fPluginList[pluginId]
  393. if pitem is None:
  394. return
  395. pitem.widget.setParameterDefault(index, value)
  396. @pyqtSlot(int, int, int)
  397. def slot_handleParameterMidiCcChangedCallback(self, pluginId, index, cc):
  398. if pluginId >= self.fPluginCount:
  399. return
  400. pitem = self.fPluginList[pluginId]
  401. if pitem is None:
  402. return
  403. pitem.widget.setParameterMidiControl(index, cc)
  404. @pyqtSlot(int, int, int)
  405. def slot_handleParameterMidiChannelChangedCallback(self, pluginId, index, channel):
  406. if pluginId >= self.fPluginCount:
  407. return
  408. pitem = self.fPluginList[pluginId]
  409. if pitem is None:
  410. return
  411. pitem.widget.setParameterMidiChannel(index, channel)
  412. # -----------------------------------------------------------------
  413. @pyqtSlot(int, int)
  414. def slot_handleProgramChangedCallback(self, pluginId, index):
  415. if pluginId >= self.fPluginCount:
  416. return
  417. pitem = self.fPluginList[pluginId]
  418. if pitem is None:
  419. return
  420. pitem.widget.setProgram(index, True)
  421. @pyqtSlot(int, int)
  422. def slot_handleMidiProgramChangedCallback(self, pluginId, index):
  423. if pluginId >= self.fPluginCount:
  424. return
  425. pitem = self.fPluginList[pluginId]
  426. if pitem is None:
  427. return
  428. pitem.widget.setMidiProgram(index, True)
  429. # -----------------------------------------------------------------
  430. @pyqtSlot(int, int)
  431. def slot_handleUiStateChangedCallback(self, pluginId, state):
  432. if pluginId >= self.fPluginCount:
  433. return
  434. pitem = self.fPluginList[pluginId]
  435. if pitem is None:
  436. return
  437. pitem.widget.customUiStateChanged(state)
  438. # -----------------------------------------------------------------
  439. @pyqtSlot(int, int, int, int)
  440. def slot_handleNoteOnCallback(self, pluginId, channel, note, velo):
  441. if pluginId >= self.fPluginCount:
  442. return
  443. pitem = self.fPluginList[pluginId]
  444. if pitem is None:
  445. return
  446. pitem.widget.sendNoteOn(channel, note)
  447. @pyqtSlot(int, int, int)
  448. def slot_handleNoteOffCallback(self, pluginId, channel, note):
  449. if pluginId >= self.fPluginCount:
  450. return
  451. pitem = self.fPluginList[pluginId]
  452. if pitem is None:
  453. return
  454. pitem.widget.sendNoteOff(channel, note)
  455. # -----------------------------------------------------------------
  456. @pyqtSlot(int)
  457. def slot_handleUpdateCallback(self, pluginId):
  458. if pluginId >= self.fPluginCount:
  459. return
  460. pitem = self.fPluginList[pluginId]
  461. if pitem is None:
  462. return
  463. pitem.widget.fEditDialog.updateInfo()
  464. @pyqtSlot(int)
  465. def slot_handleReloadInfoCallback(self, pluginId):
  466. if pluginId >= self.fPluginCount:
  467. return
  468. pitem = self.fPluginList[pluginId]
  469. if pitem is None:
  470. return
  471. pitem.widget.fEditDialog.reloadInfo()
  472. @pyqtSlot(int)
  473. def slot_handleReloadParametersCallback(self, pluginId):
  474. if pluginId >= self.fPluginCount:
  475. return
  476. pitem = self.fPluginList[pluginId]
  477. if pitem is None:
  478. return
  479. pitem.widget.fEditDialog.reloadParameters()
  480. @pyqtSlot(int)
  481. def slot_handleReloadProgramsCallback(self, pluginId):
  482. if pluginId >= self.fPluginCount:
  483. return
  484. pitem = self.fPluginList[pluginId]
  485. if pitem is None:
  486. return
  487. pitem.widget.fEditDialog.reloadPrograms()
  488. @pyqtSlot(int)
  489. def slot_handleReloadAllCallback(self, pluginId):
  490. if pluginId >= self.fPluginCount:
  491. return
  492. pitem = self.fPluginList[pluginId]
  493. if pitem is None:
  494. return
  495. self.fRack.setCurrentRow(-1)
  496. self.fCurrentRow = -1
  497. self.fLastSelectedItem = None
  498. pitem.reloadAll(pluginId)
  499. # -----------------------------------------------------------------
  500. def slot_currentRowChanged(self, row):
  501. self.fCurrentRow = row
  502. if self.fLastSelectedItem is not None:
  503. self.fLastSelectedItem.setSelected(False)
  504. if row < 0 or row >= self.fPluginCount or self.fPluginList[row] is None:
  505. self.fLastSelectedItem = None
  506. return
  507. pitem = self.fPluginList[row]
  508. pitem.widget.setSelected(True)
  509. self.fLastSelectedItem = pitem.widget
  510. # -----------------------------------------------------------------