This repository has been archived on 2020-08-22. You can view files and clone it, but cannot push or open issues or pull requests.
pyaudviz/src/mainwindow.py

804 lines
30 KiB
Python

from PyQt5 import QtCore, QtGui, uic, QtWidgets
from PyQt5.QtWidgets import QMenu, QShortcut
from queue import Queue
import sys
import os
import signal
import filecmp
import time
import core
import preview_thread
import video_thread
from presetmanager import PresetManager
from toolkit import LoadDefaultSettings, disableWhenEncoding
class PreviewWindow(QtWidgets.QLabel):
def __init__(self, parent, img):
super(PreviewWindow, self).__init__()
self.parent = parent
self.setFrameStyle(QtWidgets.QFrame.StyledPanel)
self.pixmap = QtGui.QPixmap(img)
def paintEvent(self, event):
size = self.size()
painter = QtGui.QPainter(self)
point = QtCore.QPoint(0, 0)
scaledPix = self.pixmap.scaled(
size,
QtCore.Qt.KeepAspectRatio,
transformMode=QtCore.Qt.SmoothTransformation)
# start painting the label from left upper corner
point.setX((size.width() - scaledPix.width())/2)
point.setY((size.height() - scaledPix.height())/2)
painter.drawPixmap(point, scaledPix)
def changePixmap(self, img):
self.pixmap = QtGui.QPixmap(img)
self.repaint()
class MainWindow(QtWidgets.QMainWindow):
newTask = QtCore.pyqtSignal(list)
processTask = QtCore.pyqtSignal()
videoTask = QtCore.pyqtSignal(str, str, list)
def __init__(self, window, project):
QtWidgets.QMainWindow.__init__(self)
# print('main thread id: {}'.format(QtCore.QThread.currentThreadId()))
self.window = window
self.core = core.Core()
self.pages = [] # widgets of component settings
self.lastAutosave = time.time()
self.encoding = False
# Create data directory, load/create settings
self.dataDir = self.core.dataDir
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
self.settings = self.core.settings
LoadDefaultSettings(self)
self.presetManager = PresetManager(
uic.loadUi(
os.path.join(self.core.wd, 'presetmanager.ui')), self)
if not os.path.exists(self.dataDir):
os.makedirs(self.dataDir)
for neededDirectory in (
self.core.presetDir, self.settings.value("projectDir")):
if not os.path.exists(neededDirectory):
os.mkdir(neededDirectory)
# Make queues/timers for the preview thread
self.previewQueue = Queue()
self.previewThread = QtCore.QThread(self)
self.previewWorker = preview_thread.Worker(self, self.previewQueue)
self.previewWorker.moveToThread(self.previewThread)
self.previewWorker.imageCreated.connect(self.showPreviewImage)
self.previewThread.start()
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.processTask.emit)
self.timer.start(500)
# Begin decorating the window and connecting events
componentList = self.window.listWidget_componentList
if sys.platform == 'darwin':
window.progressBar_createVideo.setTextVisible(False)
else:
window.progressLabel.setHidden(True)
window.toolButton_selectAudioFile.clicked.connect(
self.openInputFileDialog)
window.toolButton_selectOutputFile.clicked.connect(
self.openOutputFileDialog)
def changedField():
self.autosave()
self.updateWindowTitle()
window.lineEdit_audioFile.textChanged.connect(changedField)
window.lineEdit_outputFile.textChanged.connect(changedField)
window.progressBar_createVideo.setValue(0)
window.pushButton_createVideo.clicked.connect(
self.createAudioVisualisation)
window.pushButton_Cancel.clicked.connect(self.stopVideo)
for i, container in enumerate(self.core.encoder_options['containers']):
window.comboBox_videoContainer.addItem(container['name'])
if container['name'] == self.settings.value('outputContainer'):
selectedContainer = i
window.comboBox_videoContainer.setCurrentIndex(selectedContainer)
window.comboBox_videoContainer.currentIndexChanged.connect(
self.updateCodecs
)
self.updateCodecs()
for i in range(window.comboBox_videoCodec.count()):
codec = window.comboBox_videoCodec.itemText(i)
if codec == self.settings.value('outputVideoCodec'):
window.comboBox_videoCodec.setCurrentIndex(i)
for i in range(window.comboBox_audioCodec.count()):
codec = window.comboBox_audioCodec.itemText(i)
if codec == self.settings.value('outputAudioCodec'):
window.comboBox_audioCodec.setCurrentIndex(i)
window.comboBox_videoCodec.currentIndexChanged.connect(
self.updateCodecSettings
)
window.comboBox_audioCodec.currentIndexChanged.connect(
self.updateCodecSettings
)
vBitrate = int(self.settings.value('outputVideoBitrate'))
aBitrate = int(self.settings.value('outputAudioBitrate'))
window.spinBox_vBitrate.setValue(vBitrate)
window.spinBox_aBitrate.setValue(aBitrate)
window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings)
window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings)
self.previewWindow = PreviewWindow(self, os.path.join(
self.core.wd, "background.png"))
window.verticalLayout_previewWrapper.addWidget(self.previewWindow)
# Make component buttons
self.compMenu = QMenu()
self.compActions = []
for i, comp in enumerate(self.core.modules):
action = self.compMenu.addAction(comp.Component.__doc__)
action.triggered.connect(
lambda _, item=i: self.core.insertComponent(0, item, self)
)
self.window.pushButton_addComponent.setMenu(self.compMenu)
componentList.dropEvent = self.dragComponent
componentList.itemSelectionChanged.connect(
self.changeComponentWidget
)
self.window.pushButton_removeComponent.clicked.connect(
lambda: self.removeComponent()
)
componentList.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
componentList.customContextMenuRequested.connect(
self.componentContextMenu
)
currentRes = str(self.settings.value('outputWidth'))+'x' + \
str(self.settings.value('outputHeight'))
for i, res in enumerate(self.resolutions):
window.comboBox_resolution.addItem(res)
if res == currentRes:
currentRes = i
window.comboBox_resolution.setCurrentIndex(currentRes)
window.comboBox_resolution.currentIndexChanged.connect(
self.updateResolution
)
self.window.pushButton_listMoveUp.clicked.connect(
lambda: self.moveComponent(-1)
)
self.window.pushButton_listMoveDown.clicked.connect(
lambda: self.moveComponent(1)
)
# Configure the Projects Menu
self.projectMenu = QMenu()
self.window.menuButton_newProject = self.projectMenu.addAction(
"New Project"
)
self.window.menuButton_newProject.triggered.connect(
lambda: self.createNewProject()
)
self.window.menuButton_openProject = self.projectMenu.addAction(
"Open Project"
)
self.window.menuButton_openProject.triggered.connect(
lambda: self.openOpenProjectDialog()
)
action = self.projectMenu.addAction("Save Project")
action.triggered.connect(self.saveCurrentProject)
action = self.projectMenu.addAction("Save Project As")
action.triggered.connect(self.openSaveProjectDialog)
self.window.pushButton_projects.setMenu(self.projectMenu)
# Configure the Presets Button
self.window.pushButton_presets.clicked.connect(
self.openPresetManager
)
self.updateWindowTitle()
window.show()
if project and project != self.autosavePath:
if not project.endswith('.avp'):
project += '.avp'
# open a project from the commandline
if not os.path.dirname(project):
project = os.path.join(
self.settings.value("projectDir"), project
)
self.currentProject = project
self.settings.setValue("currentProject", project)
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
else:
# open the last currentProject from settings
self.currentProject = self.settings.value("currentProject")
# delete autosave if it's identical to this project
if self.autosaveExists(identical=True):
os.remove(self.autosavePath)
if self.currentProject and os.path.exists(self.autosavePath):
ch = self.showMessage(
msg="Restore unsaved changes in project '%s'?"
% os.path.basename(self.currentProject)[:-4],
showCancel=True)
if ch:
self.saveProjectChanges()
else:
os.remove(self.autosavePath)
self.openProject(self.currentProject, prompt=False)
self.drawPreview(True)
# Setup Hotkeys
QtWidgets.QShortcut("Ctrl+S", self.window, self.saveCurrentProject)
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
QtWidgets.QShortcut(
"Ctrl+T", self.window,
activated=lambda: self.window.pushButton_addComponent.click()
)
QtWidgets.QShortcut(
"Ctrl+Space", self.window,
activated=lambda: self.window.listWidget_componentList.setFocus()
)
QtWidgets.QShortcut(
"Ctrl+Shift+S", self.window,
self.presetManager.openSavePresetDialog
)
QtWidgets.QShortcut(
"Ctrl+Shift+C", self.window, self.presetManager.clearPreset
)
QtWidgets.QShortcut(
"Ctrl+Up", self.window,
activated=lambda: self.moveComponent(-1)
)
QtWidgets.QShortcut(
"Ctrl+Down", self.window,
activated=lambda: self.moveComponent(1)
)
QtWidgets.QShortcut("Ctrl+Home", self.window, self.moveComponentTop)
QtWidgets.QShortcut("Ctrl+End", self.window, self.moveComponentBottom)
QtWidgets.QShortcut("Ctrl+r", self.window, self.removeComponent)
def cleanUp(self):
self.timer.stop()
self.previewThread.quit()
self.previewThread.wait()
self.autosave()
def updateWindowTitle(self):
appName = 'Audio Visualizer'
try:
if self.currentProject:
appName += ' - %s' % \
os.path.splitext(
os.path.basename(self.currentProject))[0]
if self.autosaveExists(identical=False):
appName += '*'
except AttributeError:
pass
self.window.setWindowTitle(appName)
@QtCore.pyqtSlot(int, dict)
def updateComponentTitle(self, pos, presetStore=False):
if type(presetStore) == dict:
name = presetStore['preset']
if name is None or name not in self.core.savedPresets:
modified = False
else:
modified = (presetStore != self.core.savedPresets[name])
else:
modified = bool(presetStore)
if pos < 0:
pos = len(self.core.selectedComponents)-1
title = str(self.core.selectedComponents[pos])
if self.core.selectedComponents[pos].currentPreset:
title += ' - %s' % self.core.selectedComponents[pos].currentPreset
if modified:
title += '*'
self.window.listWidget_componentList.item(pos).setText(title)
def updateCodecs(self):
containerWidget = self.window.comboBox_videoContainer
vCodecWidget = self.window.comboBox_videoCodec
aCodecWidget = self.window.comboBox_audioCodec
index = containerWidget.currentIndex()
name = containerWidget.itemText(index)
self.settings.setValue('outputContainer', name)
vCodecWidget.clear()
aCodecWidget.clear()
for container in self.core.encoder_options['containers']:
if container['name'] == name:
for vCodec in container['video-codecs']:
vCodecWidget.addItem(vCodec)
for aCodec in container['audio-codecs']:
aCodecWidget.addItem(aCodec)
def updateCodecSettings(self):
vCodecWidget = self.window.comboBox_videoCodec
vBitrateWidget = self.window.spinBox_vBitrate
aBitrateWidget = self.window.spinBox_aBitrate
aCodecWidget = self.window.comboBox_audioCodec
currentVideoCodec = vCodecWidget.currentIndex()
currentVideoCodec = vCodecWidget.itemText(currentVideoCodec)
currentVideoBitrate = vBitrateWidget.value()
currentAudioCodec = aCodecWidget.currentIndex()
currentAudioCodec = aCodecWidget.itemText(currentAudioCodec)
currentAudioBitrate = aBitrateWidget.value()
self.settings.setValue('outputVideoCodec', currentVideoCodec)
self.settings.setValue('outputAudioCodec', currentAudioCodec)
self.settings.setValue('outputVideoBitrate', currentVideoBitrate)
self.settings.setValue('outputAudioBitrate', currentAudioBitrate)
def autosave(self, force=False):
if not self.currentProject:
if os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
elif force or time.time() - self.lastAutosave >= 0.1:
self.core.createProjectFile(self.autosavePath, self.window)
self.lastAutosave = time.time()
def autosaveExists(self, identical=True):
try:
if self.currentProject and os.path.exists(self.autosavePath) \
and filecmp.cmp(
self.autosavePath, self.currentProject) == identical:
return True
except FileNotFoundError:
print('project file couldn\'t be located:', self.currentProject)
return identical
return False
def saveProjectChanges(self):
try:
os.remove(self.currentProject)
os.rename(self.autosavePath, self.currentProject)
return True
except (FileNotFoundError, IsADirectoryError) as e:
self.showMessage(
msg='Project file couldn\'t be saved.',
detail=str(e))
return False
def openInputFileDialog(self):
inputDir = self.settings.value("inputDir", os.path.expanduser("~"))
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
self.window, "Open Audio File",
inputDir, "Audio Files (%s)" % " ".join(self.core.audioFormats))
if fileName:
self.settings.setValue("inputDir", os.path.dirname(fileName))
self.window.lineEdit_audioFile.setText(fileName)
def openOutputFileDialog(self):
outputDir = self.settings.value("outputDir", os.path.expanduser("~"))
fileName, _ = QtWidgets.QFileDialog.getSaveFileName(
self.window, "Set Output Video File",
outputDir,
"Video Files (%s);; All Files (*)" % " ".join(
self.core.videoFormats))
if fileName:
self.settings.setValue("outputDir", os.path.dirname(fileName))
self.window.lineEdit_outputFile.setText(fileName)
def stopVideo(self):
print('stop')
self.videoWorker.cancel()
self.canceled = True
def createAudioVisualisation(self):
# create output video if mandatory settings are filled in
audioFile = self.window.lineEdit_audioFile.text()
outputPath = self.window.lineEdit_outputFile.text()
if audioFile and outputPath and self.core.selectedComponents:
if not os.path.dirname(outputPath):
outputPath = os.path.join(
os.path.expanduser("~"), outputPath)
if outputPath and os.path.isdir(outputPath):
self.showMessage(
msg='Chosen filename matches a directory, which '
'cannot be overwritten. Please choose a different '
'filename or move the directory.',
icon='Warning',
)
return
else:
if not audioFile or not outputPath:
self.showMessage(
msg="You must select an audio file and output filename."
)
elif not self.core.selectedComponents:
self.showMessage(
msg="Not enough components."
)
return
self.canceled = False
self.progressBarUpdated(-1)
self.videoThread = QtCore.QThread(self)
self.videoWorker = video_thread.Worker(self)
self.videoWorker.moveToThread(self.videoThread)
self.videoWorker.videoCreated.connect(self.videoCreated)
self.videoWorker.progressBarUpdate.connect(self.progressBarUpdated)
self.videoWorker.progressBarSetText.connect(
self.progressBarSetText)
self.videoWorker.imageCreated.connect(self.showPreviewImage)
self.videoWorker.encoding.connect(self.changeEncodingStatus)
self.videoThread.start()
self.videoTask.emit(
audioFile,
outputPath,
self.core.selectedComponents)
def changeEncodingStatus(self, status):
self.encoding = status
if status:
self.window.pushButton_createVideo.setEnabled(False)
self.window.pushButton_Cancel.setEnabled(True)
self.window.comboBox_resolution.setEnabled(False)
self.window.stackedWidget.setEnabled(False)
self.window.tab_encoderSettings.setEnabled(False)
self.window.label_audioFile.setEnabled(False)
self.window.toolButton_selectAudioFile.setEnabled(False)
self.window.label_outputFile.setEnabled(False)
self.window.toolButton_selectOutputFile.setEnabled(False)
self.window.lineEdit_audioFile.setEnabled(False)
self.window.lineEdit_outputFile.setEnabled(False)
self.window.pushButton_addComponent.setEnabled(False)
self.window.pushButton_removeComponent.setEnabled(False)
self.window.pushButton_listMoveDown.setEnabled(False)
self.window.pushButton_listMoveUp.setEnabled(False)
self.window.menuButton_newProject.setEnabled(False)
self.window.menuButton_openProject.setEnabled(False)
if sys.platform == 'darwin':
self.window.progressLabel.setHidden(False)
else:
self.window.listWidget_componentList.setEnabled(False)
else:
self.window.pushButton_createVideo.setEnabled(True)
self.window.pushButton_Cancel.setEnabled(False)
self.window.comboBox_resolution.setEnabled(True)
self.window.stackedWidget.setEnabled(True)
self.window.tab_encoderSettings.setEnabled(True)
self.window.label_audioFile.setEnabled(True)
self.window.toolButton_selectAudioFile.setEnabled(True)
self.window.lineEdit_audioFile.setEnabled(True)
self.window.label_outputFile.setEnabled(True)
self.window.toolButton_selectOutputFile.setEnabled(True)
self.window.lineEdit_outputFile.setEnabled(True)
self.window.pushButton_addComponent.setEnabled(True)
self.window.pushButton_removeComponent.setEnabled(True)
self.window.pushButton_listMoveDown.setEnabled(True)
self.window.pushButton_listMoveUp.setEnabled(True)
self.window.menuButton_newProject.setEnabled(True)
self.window.menuButton_openProject.setEnabled(True)
self.window.listWidget_componentList.setEnabled(True)
self.window.progressLabel.setHidden(True)
self.drawPreview(True)
def progressBarUpdated(self, value):
self.window.progressBar_createVideo.setValue(value)
def progressBarSetText(self, value):
if sys.platform == 'darwin':
self.window.progressLabel.setText(value)
else:
self.window.progressBar_createVideo.setFormat(value)
def videoCreated(self):
self.videoThread.quit()
self.videoThread.wait()
def updateResolution(self):
resIndex = int(self.window.comboBox_resolution.currentIndex())
res = self.resolutions[resIndex].split('x')
self.settings.setValue('outputWidth', res[0])
self.settings.setValue('outputHeight', res[1])
self.drawPreview()
def drawPreview(self, force=False):
self.newTask.emit(self.core.selectedComponents)
# self.processTask.emit()
self.autosave(force)
self.updateWindowTitle()
def showPreviewImage(self, image):
self.previewWindow.changePixmap(image)
def insertComponent(self, index):
componentList = self.window.listWidget_componentList
stackedWidget = self.window.stackedWidget
componentList.insertItem(
index,
self.core.selectedComponents[index].__doc__)
componentList.setCurrentRow(index)
# connect to signal that adds an asterisk when modified
self.core.selectedComponents[index].modified.connect(
self.updateComponentTitle)
self.pages.insert(index, self.core.selectedComponents[index].page)
stackedWidget.insertWidget(index, self.pages[index])
stackedWidget.setCurrentIndex(index)
return index
def removeComponent(self):
componentList = self.window.listWidget_componentList
for selected in componentList.selectedItems():
index = componentList.row(selected)
self.window.stackedWidget.removeWidget(self.pages[index])
componentList.takeItem(index)
self.core.removeComponent(index)
self.pages.pop(index)
self.changeComponentWidget()
self.drawPreview()
@disableWhenEncoding
def moveComponent(self, change):
'''Moves a component relatively from its current position'''
componentList = self.window.listWidget_componentList
stackedWidget = self.window.stackedWidget
row = componentList.currentRow()
newRow = row + change
if newRow > -1 and newRow < componentList.count():
self.core.moveComponent(row, newRow)
# update widgets
page = self.pages.pop(row)
self.pages.insert(newRow, page)
item = componentList.takeItem(row)
newItem = componentList.insertItem(newRow, item)
widget = stackedWidget.removeWidget(page)
stackedWidget.insertWidget(newRow, page)
componentList.setCurrentRow(newRow)
stackedWidget.setCurrentIndex(newRow)
self.drawPreview()
@disableWhenEncoding
def moveComponentTop(self):
componentList = self.window.listWidget_componentList
row = -componentList.currentRow()
self.moveComponent(row)
@disableWhenEncoding
def moveComponentBottom(self):
componentList = self.window.listWidget_componentList
row = len(componentList)-componentList.currentRow()-1
self.moveComponent(row)
@disableWhenEncoding
def dragComponent(self, event):
'''Drop event for the component listwidget'''
componentList = self.window.listWidget_componentList
modelIndexes = [
componentList.model().index(i)
for i in range(componentList.count())
]
rects = [
componentList.visualRect(modelIndex)
for modelIndex in modelIndexes
]
rowPos = [rect.contains(event.pos()) for rect in rects]
if not any(rowPos):
return
i = rowPos.index(True)
change = (componentList.currentRow() - i) * -1
self.moveComponent(change)
def changeComponentWidget(self):
selected = self.window.listWidget_componentList.selectedItems()
if selected:
index = self.window.listWidget_componentList.row(selected[0])
self.window.stackedWidget.setCurrentIndex(index)
def openPresetManager(self):
'''Preset manager for importing, exporting, renaming, deleting'''
self.presetManager.show()
def clear(self):
'''Get a blank slate'''
self.core.clearComponents()
self.window.listWidget_componentList.clear()
for widget in self.pages:
self.window.stackedWidget.removeWidget(widget)
self.pages = []
for field in (
self.window.lineEdit_audioFile,
self.window.lineEdit_outputFile
):
field.blockSignals(True)
field.setText('')
field.blockSignals(False)
@disableWhenEncoding
def createNewProject(self, prompt=True):
if prompt:
self.openSaveChangesDialog('starting a new project')
self.clear()
self.currentProject = None
self.settings.setValue("currentProject", None)
self.drawPreview(True)
def saveCurrentProject(self):
if self.currentProject:
self.core.createProjectFile(self.currentProject, self.window)
self.updateWindowTitle()
else:
self.openSaveProjectDialog()
def openSaveChangesDialog(self, phrase):
success = True
if self.autosaveExists(identical=False):
ch = self.showMessage(
msg="You have unsaved changes in project '%s'. "
"Save before %s?" % (
os.path.basename(self.currentProject)[:-4],
phrase
),
showCancel=True)
if ch:
success = self.saveProjectChanges()
if success and os.path.exists(self.autosavePath):
os.remove(self.autosavePath)
def openSaveProjectDialog(self):
filename, _ = QtWidgets.QFileDialog.getSaveFileName(
self.window, "Create Project File",
self.settings.value("projectDir"),
"Project Files (*.avp)")
if not filename:
return
if not filename.endswith(".avp"):
filename += '.avp'
self.settings.setValue("projectDir", os.path.dirname(filename))
self.settings.setValue("currentProject", filename)
self.currentProject = filename
self.core.createProjectFile(filename, self.window)
self.updateWindowTitle()
@disableWhenEncoding
def openOpenProjectDialog(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName(
self.window, "Open Project File",
self.settings.value("projectDir"),
"Project Files (*.avp)")
self.openProject(filename)
def openProject(self, filepath, prompt=True):
if not filepath or not os.path.exists(filepath) \
or not filepath.endswith('.avp'):
return
self.clear()
# ask to save any changes that are about to get deleted
if prompt:
self.openSaveChangesDialog('opening another project')
self.currentProject = filepath
self.settings.setValue("currentProject", filepath)
self.settings.setValue("projectDir", os.path.dirname(filepath))
# actually load the project using core method
self.core.openProject(self, filepath)
if self.window.listWidget_componentList.count() == 0:
self.drawPreview()
self.autosave(True)
self.updateWindowTitle()
def showMessage(self, **kwargs):
parent = kwargs['parent'] if 'parent' in kwargs else self.window
msg = QtWidgets.QMessageBox(parent)
msg.setModal(True)
msg.setText(kwargs['msg'])
msg.setIcon(
eval('QtWidgets.QMessageBox.%s' % kwargs['icon'])
if 'icon' in kwargs else QtWidgets.QMessageBox.Information
)
msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None)
if 'showCancel'in kwargs and kwargs['showCancel']:
msg.setStandardButtons(
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
else:
msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
ch = msg.exec_()
if ch == 1024:
return True
return False
@disableWhenEncoding
def componentContextMenu(self, QPos):
'''Appears when right-clicking a component in the list'''
componentList = self.window.listWidget_componentList
if not componentList.selectedItems():
return
# don't show menu if clicking empty space
parentPosition = componentList.mapToGlobal(QtCore.QPoint(0, 0))
index = componentList.currentRow()
modelIndex = componentList.model().index(index)
if not componentList.visualRect(modelIndex).contains(QPos):
return
self.presetManager.findPresets()
self.menu = QMenu()
menuItem = self.menu.addAction("Save Preset")
menuItem.triggered.connect(
self.presetManager.openSavePresetDialog
)
# submenu for opening presets
try:
presets = self.presetManager.presets[
str(self.core.selectedComponents[index])
]
self.submenu = QMenu("Open Preset")
self.menu.addMenu(self.submenu)
for version, presetName in presets:
menuItem = self.submenu.addAction(presetName)
menuItem.triggered.connect(
lambda _, presetName=presetName:
self.presetManager.openPreset(presetName)
)
except KeyError:
pass
if self.core.selectedComponents[index].currentPreset:
menuItem = self.menu.addAction("Clear Preset")
menuItem.triggered.connect(
self.presetManager.clearPreset
)
self.menu.move(parentPosition + QPos)
self.menu.show()