From acf22900256d75c469c78efbd98139e3bfab0e93 Mon Sep 17 00:00:00 2001 From: DH4 Date: Wed, 7 Jun 2017 14:32:05 -0500 Subject: [PATCH 01/22] Created projects and presets button. FIXME: Hookup New Project menu item. Hookup preset manager. --- mainwindow.py | 60 +++++++++++++------ mainwindow.ui | 153 ++++++++++++++++++----------------------------- presetmanager.ui | 104 ++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+), 114 deletions(-) create mode 100644 presetmanager.ui diff --git a/mainwindow.py b/mainwindow.py index 78809be..27c8f6e 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -141,7 +141,6 @@ class MainWindow(QtCore.QObject): window.spinBox_vBitrate.valueChanged.connect(self.updateCodecSettings) window.spinBox_aBitrate.valueChanged.connect(self.updateCodecSettings) - self.previewWindow = PreviewWindow(self, os.path.join( os.path.dirname(os.path.realpath(__file__)), "background.png")) window.verticalLayout_previewWrapper.addWidget(self.previewWindow) @@ -169,23 +168,44 @@ class MainWindow(QtCore.QObject): window.comboBox_resolution.setCurrentIndex(currentRes) window.comboBox_resolution.currentIndexChanged.connect( self.updateResolution) - - self.window.pushButton_listMoveUp.clicked.connect( self.moveComponentUp) self.window.pushButton_listMoveDown.clicked.connect( self.moveComponentDown) - self.window.pushButton_savePreset.clicked.connect( + + '''self.window.pushButton_savePreset.clicked.connect( self.openSavePresetDialog) self.window.comboBox_openPreset.currentIndexChanged.connect( - self.openPreset) - self.window.pushButton_saveAs.clicked.connect( + self.openPreset)''' + + # Configure the Projects Menu + self.projectMenu = QMenu() + action = self.projectMenu.addAction("New Project") + action.triggered[()].connect(self.createNewProject) + + action = self.projectMenu.addAction("Open Project") + action.triggered[()].connect(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.window.pushButton_saveAs.clicked.connect( self.openSaveProjectDialog) self.window.pushButton_saveProject.clicked.connect( self.saveCurrentProject) self.window.pushButton_openProject.clicked.connect( - self.openOpenProjectDialog) + self.openOpenProjectDialog)''' # show the window and load current project window.show() @@ -327,10 +347,8 @@ class MainWindow(QtCore.QObject): self.window.pushButton_removeComponent.setEnabled(False) self.window.pushButton_listMoveDown.setEnabled(False) self.window.pushButton_listMoveUp.setEnabled(False) - self.window.comboBox_openPreset.setEnabled(False) - self.window.pushButton_removePreset.setEnabled(False) - self.window.pushButton_savePreset.setEnabled(False) - self.window.pushButton_openProject.setEnabled(False) + self.window.comboBox_Presets.setEnabled(False) + '''self.window.pushButton_openProject.setEnabled(False)''' self.window.listWidget_componentList.setEnabled(False) else: self.window.pushButton_createVideo.setEnabled(True) @@ -348,10 +366,8 @@ class MainWindow(QtCore.QObject): self.window.pushButton_removeComponent.setEnabled(True) self.window.pushButton_listMoveDown.setEnabled(True) self.window.pushButton_listMoveUp.setEnabled(True) - self.window.comboBox_openPreset.setEnabled(True) - self.window.pushButton_removePreset.setEnabled(True) - self.window.pushButton_savePreset.setEnabled(True) - self.window.pushButton_openProject.setEnabled(True) + self.window.comboBox_Presets.setEnabled(True) + '''self.window.pushButton_openProject.setEnabled(True)''' self.window.listWidget_componentList.setEnabled(True) def progressBarSetText(self, value): @@ -401,7 +417,7 @@ class MainWindow(QtCore.QObject): self.window.stackedWidget.addWidget(self.pages[-1]) self.window.stackedWidget.setCurrentIndex(index) self.selectedComponents[-1].update() - self.updateOpenPresetComboBox(self.selectedComponents[-1]) + '''self.updateOpenPresetComboBox(self.selectedComponents[-1])''' def insertComponent(self, moduleIndex): self.selectedComponents.insert( @@ -413,7 +429,7 @@ class MainWindow(QtCore.QObject): self.window.stackedWidget.insertWidget(0, self.pages[0]) self.window.stackedWidget.setCurrentIndex(0) self.selectedComponents[0].update() - self.updateOpenPresetComboBox(self.selectedComponents[0]) + '''self.updateOpenPresetComboBox(self.selectedComponents[0])''' def removeComponent(self): for selected in self.window.listWidget_componentList.selectedItems(): @@ -430,7 +446,7 @@ class MainWindow(QtCore.QObject): if selected: index = self.window.listWidget_componentList.row(selected[0]) self.window.stackedWidget.setCurrentIndex(index) - self.updateOpenPresetComboBox(self.selectedComponents[index]) + '''self.updateOpenPresetComboBox(self.selectedComponents[index])''' def moveComponentUp(self): row = self.window.listWidget_componentList.currentRow() @@ -466,6 +482,11 @@ class MainWindow(QtCore.QObject): self.window.stackedWidget.setCurrentIndex(row + 1) self.drawPreview() + # Preset manager for importing, exporting, renaming, + # and deleting presets. + def openPresetManager(self): + return + def updateOpenPresetComboBox(self, component): self.window.comboBox_openPreset.clear() self.window.comboBox_openPreset.addItem("Component Presets") @@ -547,6 +568,9 @@ class MainWindow(QtCore.QObject): self.selectedComponents[index].loadPreset(saveValueStore) self.drawPreview() + def createNewProject(self): + return + def saveCurrentProject(self): if self.currentProject: self.createProjectFile(self.currentProject) diff --git a/mainwindow.ui b/mainwindow.ui index c010caf..62e0632 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -108,23 +108,32 @@ QLayout::SetMinimumSize - + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 140 + 20 + + + + + + - Open Project + Projects - + - Save Project - - - - - - - Save As + Presets @@ -141,53 +150,11 @@ 20 - 15 + 2 - - - - - - Add - - - - - - - Remove - - - - - - - Up - - - - - - - Down - - - - - - - - - - - 4 - - - 2 - @@ -231,54 +198,48 @@ + + + + + + Add + + + + + + + Remove + + + + + + + Up + + + + + + + Down + + + + + - + + 4 + + 2 - - - - - 0 - 0 - - - - - 180 - 0 - - - - - Component Presets - - - - - - - - - 0 - 0 - - - - Save - - - - - - - Remove - - - diff --git a/presetmanager.ui b/presetmanager.ui new file mode 100644 index 0000000..7496169 --- /dev/null +++ b/presetmanager.ui @@ -0,0 +1,104 @@ + + + presetmanager + + + + 0 + 0 + 542 + 360 + + + + Preset Manager + + + + + + + + Search + + + + + + + + 200 + 0 + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + QLayout::SetMinimumSize + + + + + Import + + + + + + + Export + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Rename + + + + + + + Delete + + + + + + + + + + From 6f2f02b709d2ea789b39b68f9a21a1a35d421075 Mon Sep 17 00:00:00 2001 From: tassaron Date: Wed, 7 Jun 2017 17:09:28 -0400 Subject: [PATCH 02/22] newProject method & various small fixes --- components/video.py | 5 ++++- mainwindow.py | 38 +++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/components/video.py b/components/video.py index b28b81e..f086cc0 100644 --- a/components/video.py +++ b/components/video.py @@ -5,7 +5,8 @@ import subprocess import threading from queue import PriorityQueue from . import __base__ - + + class Video: '''Video Component Frame-Fetcher''' def __init__(self, **kwargs): @@ -129,10 +130,12 @@ class Component(__base__.Component): def loadPreset(self, pr): self.page.lineEdit_video.setText(pr['video']) + self.page.checkBox_loop.setChecked(pr['loop']) def savePreset(self): return { 'video': self.videoPath, + 'loop': self.loopVideo, } def pickVideo(self): diff --git a/mainwindow.py b/mainwindow.py index 27c8f6e..7ea1410 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -133,7 +133,7 @@ class MainWindow(QtCore.QObject): ) vBitrate = int(self.settings.value('outputVideoBitrate')) - aBitrate = int(self.settings.value('outputAudioBitrate')) + aBitrate = int(self.settings.value('outputAudioBitrate')[:-1]) window.spinBox_vBitrate.setValue(vBitrate) window.spinBox_aBitrate.setValue(aBitrate) @@ -269,7 +269,7 @@ class MainWindow(QtCore.QObject): self.settings.setValue('outputAudioBitrate', currentAudioBitrate) def autosave(self): - if time.time() - self.lastAutosave >= 1.0: + if time.time() - self.lastAutosave >= 2.0: if os.path.exists(self.autosavePath): os.remove(self.autosavePath) self.createProjectFile(self.autosavePath) @@ -347,8 +347,7 @@ class MainWindow(QtCore.QObject): self.window.pushButton_removeComponent.setEnabled(False) self.window.pushButton_listMoveDown.setEnabled(False) self.window.pushButton_listMoveUp.setEnabled(False) - self.window.comboBox_Presets.setEnabled(False) - '''self.window.pushButton_openProject.setEnabled(False)''' + self.window.pushButton_presets.setEnabled(False) self.window.listWidget_componentList.setEnabled(False) else: self.window.pushButton_createVideo.setEnabled(True) @@ -366,8 +365,7 @@ class MainWindow(QtCore.QObject): self.window.pushButton_removeComponent.setEnabled(True) self.window.pushButton_listMoveDown.setEnabled(True) self.window.pushButton_listMoveUp.setEnabled(True) - self.window.comboBox_Presets.setEnabled(True) - '''self.window.pushButton_openProject.setEnabled(True)''' + self.window.pushButton_presets.setEnabled(True) self.window.listWidget_componentList.setEnabled(True) def progressBarSetText(self, value): @@ -569,7 +567,14 @@ class MainWindow(QtCore.QObject): self.drawPreview() def createNewProject(self): - return + self.currentProject = None + self.selectedComponents = [] + self.window.listWidget_componentList.clear() + for widget in self.pages: + self.window.stackedWidget.removeWidget(widget) + self.pages = [] + self.settings.setValue("currentProject", None) + self.drawPreview() def saveCurrentProject(self): if self.currentProject: @@ -613,7 +618,7 @@ class MainWindow(QtCore.QObject): if not filepath or not os.path.exists(filepath) \ or not filepath.endswith('.avp'): return - self.clear() + self.createNewProject() self.currentProject = filepath self.settings.setValue("currentProject", filepath) self.settings.setValue("projectDir", os.path.dirname(filepath)) @@ -652,14 +657,17 @@ class MainWindow(QtCore.QObject): self.selectedComponents[-1].loadPreset( saveValueStore) i = 0 - except (IndexError, ValueError, KeyError, NameError, - SyntaxError, AttributeError, TypeError) as e: - self.clear() + except (IndexError, ValueError, NameError, SyntaxError, + AttributeError, TypeError) as e: + self.createNewProject() typ, value, _ = sys.exc_info() msg = '%s: %s' % (typ.__name__, value) self.showMessage( "Project file '%s' is corrupted." % filepath, False, QtGui.QMessageBox.Warning, msg) + except KeyError as e: + # probably just an old version, still loadable + print('project file missing value: %s' % e) def showMessage( self, string, showCancel=False, @@ -677,11 +685,3 @@ class MainWindow(QtCore.QObject): if ch == 1024: return True return False - - def clear(self): - ''' empty out all components and fields, get a blank slate ''' - self.selectedComponents = [] - self.window.listWidget_componentList.clear() - for widget in self.pages: - self.window.stackedWidget.removeWidget(widget) - self.pages = [] From 6093e701e151af96464b564e275db4664d828a82 Mon Sep 17 00:00:00 2001 From: tassaron Date: Wed, 7 Jun 2017 20:30:37 -0400 Subject: [PATCH 03/22] laying some foundations for new preset implementation --- mainwindow.py | 88 ++++++++++++++++++++++++++++-------------------- presetmanager.py | 70 ++++++++++++++++++++++++++++++++++++++ presetmanager.ui | 17 ++++++---- 3 files changed, 132 insertions(+), 43 deletions(-) create mode 100644 presetmanager.py diff --git a/mainwindow.py b/mainwindow.py index 7ea1410..2c1a95a 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -2,7 +2,7 @@ from os.path import expanduser from queue import Queue from importlib import import_module from collections import OrderedDict -from PyQt4 import QtCore, QtGui +from PyQt4 import QtCore, QtGui, uic from PyQt4.QtCore import QSettings, Qt from PyQt4.QtGui import QDesktopServices, QMenu import sys @@ -16,6 +16,7 @@ import time import core import preview_thread import video_thread +from presetmanager import PresetManager from main import LoadDefaultSettings @@ -59,7 +60,7 @@ class MainWindow(QtCore.QObject): self.selectedComponents = [] self.lastAutosave = time.time() - # create data directory, load/create settings + # Create data directory, load/create settings self.dataDir = QDesktopServices.storageLocation( QDesktopServices.DataLocation) self.autosavePath = os.path.join(self.dataDir, 'autosave.avp') @@ -74,7 +75,13 @@ class MainWindow(QtCore.QObject): if not os.path.exists(neededDirectory): os.mkdir(neededDirectory) - # + self.presetManager = PresetManager( + uic.loadUi( + os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'presetmanager.ui')), + self) + + # Make queues/timers for the preview thread self.previewQueue = Queue() self.previewThread = QtCore.QThread(self) self.previewWorker = preview_thread.Worker(self, self.previewQueue) @@ -86,7 +93,7 @@ class MainWindow(QtCore.QObject): self.timer.timeout.connect(self.processTask.emit) self.timer.start(500) - # begin decorating the window and connecting events + # Begin decorating the window and connecting events window.toolButton_selectAudioFile.clicked.connect( self.openInputFileDialog) @@ -145,6 +152,7 @@ class MainWindow(QtCore.QObject): os.path.dirname(os.path.realpath(__file__)), "background.png")) window.verticalLayout_previewWrapper.addWidget(self.previewWindow) + # Make component buttons self.modules = self.findComponents() self.compMenu = QMenu() for i, comp in enumerate(self.modules): @@ -153,12 +161,20 @@ class MainWindow(QtCore.QObject): lambda item=i: self.insertComponent(item)) self.window.pushButton_addComponent.setMenu(self.compMenu) + window.listWidget_componentList.clicked.connect( lambda _: self.changeComponentWidget()) self.window.pushButton_removeComponent.clicked.connect( lambda _: self.removeComponent()) + self.window.listWidget_componentList.setContextMenuPolicy( + QtCore.Qt.CustomContextMenu) + self.window.listWidget_componentList.connect( + self.window.listWidget_componentList, + QtCore.SIGNAL("customContextMenuRequested(QPoint)"), + self.componentContextMenu) + currentRes = str(self.settings.value('outputWidth'))+'x' + \ str(self.settings.value('outputHeight')) for i, res in enumerate(self.resolutions): @@ -174,11 +190,6 @@ class MainWindow(QtCore.QObject): self.window.pushButton_listMoveDown.clicked.connect( self.moveComponentDown) - '''self.window.pushButton_savePreset.clicked.connect( - self.openSavePresetDialog) - self.window.comboBox_openPreset.currentIndexChanged.connect( - self.openPreset)''' - # Configure the Projects Menu self.projectMenu = QMenu() action = self.projectMenu.addAction("New Project") @@ -200,14 +211,7 @@ class MainWindow(QtCore.QObject): self.openPresetManager ) - '''self.window.pushButton_saveAs.clicked.connect( - self.openSaveProjectDialog) - self.window.pushButton_saveProject.clicked.connect( - self.saveCurrentProject) - self.window.pushButton_openProject.clicked.connect( - self.openOpenProjectDialog)''' - - # show the window and load current project + # Show the window and load current project window.show() self.currentProject = self.settings.value("currentProject") if self.currentProject and os.path.exists(self.autosavePath) \ @@ -217,8 +221,9 @@ class MainWindow(QtCore.QObject): if self.currentProject and os.path.exists(self.autosavePath): ch = self.showMessage( - "Restore unsaved changes in project '%s'?" - % os.path.basename(self.currentProject)[:-4], True) + msg="Restore unsaved changes in project '%s'?" + % os.path.basename(self.currentProject)[:-4], + showCancel=True) if ch: os.remove(self.currentProject) os.rename(self.autosavePath, self.currentProject) @@ -325,7 +330,7 @@ class MainWindow(QtCore.QObject): self.selectedComponents) else: self.showMessage( - "You must select an audio file and output filename.") + msg="You must select an audio file and output filename.") def progressBarUpdated(self, value): self.window.progressBar_createVideo.setValue(value) @@ -480,10 +485,9 @@ class MainWindow(QtCore.QObject): self.window.stackedWidget.setCurrentIndex(row + 1) self.drawPreview() - # Preset manager for importing, exporting, renaming, - # and deleting presets. def openPresetManager(self): - return + '''Preset manager for importing, exporting, renaming, deleting''' + self.presetManager.show() def updateOpenPresetComboBox(self, component): self.window.comboBox_openPreset.clear() @@ -507,8 +511,8 @@ class MainWindow(QtCore.QObject): badName = True if badName: # some filesystems don't like bizarre characters - self.showMessage("Preset names must contain only letters, \ - numbers, and spaces.") + self.showMessage(msg="Preset names must contain only \ + letters, numbers, and spaces.") continue if OK and newName: index = self.window.listWidget_componentList.currentRow() @@ -529,8 +533,8 @@ class MainWindow(QtCore.QObject): filepath = os.path.join(dirname, filename) if os.path.exists(filepath): ch = self.showMessage( - "%s already exists! Overwrite it?" % filename, - True, QtGui.QMessageBox.Warning) + msg="%s already exists! Overwrite it?" % filename, + showCancel=True, icon=QtGui.QMessageBox.Warning) if not ch: return # remove old copies of the preset @@ -663,20 +667,20 @@ class MainWindow(QtCore.QObject): typ, value, _ = sys.exc_info() msg = '%s: %s' % (typ.__name__, value) self.showMessage( - "Project file '%s' is corrupted." % filepath, False, - QtGui.QMessageBox.Warning, msg) + msg="Project file '%s' is corrupted." % filepath, + showCancel=False, + icon=QtGui.QMessageBox.Warning, + detail=msg) except KeyError as e: # probably just an old version, still loadable print('project file missing value: %s' % e) - def showMessage( - self, string, showCancel=False, - icon=QtGui.QMessageBox.Information, detail=None): + def showMessage(self, **kwargs): msg = QtGui.QMessageBox() - msg.setIcon(icon) - msg.setText(string) - msg.setDetailedText(detail) - if showCancel: + msg.setText(kwargs['msg']) + msg.setIcon(kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information) + msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None) + if 'showCancel'in kwargs and kwargs['showCancel']: msg.setStandardButtons( QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) else: @@ -685,3 +689,15 @@ class MainWindow(QtCore.QObject): if ch == 1024: return True return False + + def componentContextMenu(self, QPos): + self.menu = QtGui.QMenu() + menuItem = self.menu.addAction("Save Preset") + self.connect(menuItem, QtCore.SIGNAL("triggered()"), self.openSavePresetDialog) + parentPosition = self.window.listWidget_componentList.mapToGlobal(QtCore.QPoint(0, 0)) + self.menu.move(parentPosition + QPos) + self.menu.show() + + def menuItemClicked(self): + currentItemName=str(self.window.listWidget_componentList.currentItem().text() ) + print(currentItemName) diff --git a/presetmanager.py b/presetmanager.py new file mode 100644 index 0000000..cc6c482 --- /dev/null +++ b/presetmanager.py @@ -0,0 +1,70 @@ +from PyQt4 import QtGui +#import sys +import os + +class PresetManager(QtGui.QDialog): + def __init__(self, window, parent): + super().__init__() + self.parent = parent + self.presetDir = parent.presetDir + self.window = window + self.presets = self.findPresets() + + # create filter box and preset list + self.drawFilterList() + self.window.comboBox_filter.currentIndexChanged.connect( + lambda: self.drawPresetList(self.window.comboBox_filter.currentText()) + ) + self.drawPresetList('*') + + # make auto-completion for search bar + self.autocomplete = QtGui.QStringListModel() + completer = QtGui.QCompleter() + completer.setModel(self.autocomplete) + self.window.lineEdit_search.setCompleter(completer) + + def show(self): + presetNames = [] + for presetList in self.presets.values(): + for preset in presetList: + presetNames.append(preset[1]) + self.autocomplete.setStringList(presetNames) + self.presets = self.findPresets() + self.drawFilterList() + self.drawPresetList('*') + self.window.show() + + def findPresets(self): + parseList = [] + for dirpath, dirnames, filenames in os.walk(self.presetDir): + # anything without a subdirectory must be a preset folder + if dirnames: + continue + for preset in filenames: + compName = os.path.basename(os.path.dirname(dirpath)) + compVers = os.path.basename(dirpath) + try: + parseList.append((compName, int(compVers), preset)) + except ValueError: + continue + return { compName : \ + [ (vers, preset) \ + for name, vers, preset in parseList \ + if name == compName \ + ] \ + for compName, _, __ in parseList \ + } + + def drawPresetList(self, filter): + self.window.listWidget_presets.clear() + for component, presets in self.presets.items(): + if filter != '*' and component != filter: + continue + for vers, preset in presets: + self.window.listWidget_presets.addItem('%s: %s' % (component, preset)) + + def drawFilterList(self): + self.window.comboBox_filter.clear() + self.window.comboBox_filter.addItem('*') + for component in self.presets: + self.window.comboBox_filter.addItem(component) diff --git a/presetmanager.ui b/presetmanager.ui index 7496169..610de91 100644 --- a/presetmanager.ui +++ b/presetmanager.ui @@ -17,14 +17,17 @@ - + + + + Search - + 200 @@ -38,7 +41,7 @@ - + 0 @@ -55,14 +58,14 @@ QLayout::SetMinimumSize - + Import - + Export @@ -82,14 +85,14 @@ - + Rename - + Delete From 292d21c20372634f4d0cabf43611d0e50386bc4c Mon Sep 17 00:00:00 2001 From: tassaron Date: Wed, 7 Jun 2017 23:22:55 -0400 Subject: [PATCH 04/22] added submenu for opening presets, moved code --- components/__base__.py | 6 +- components/color.py | 3 +- components/image.py | 5 +- components/original.py | 3 +- components/text.py | 3 +- components/video.py | 5 +- mainwindow.py | 138 +++++++++++------------------------------ presetmanager.py | 98 +++++++++++++++++++++++++---- 8 files changed, 140 insertions(+), 121 deletions(-) diff --git a/components/__base__.py b/components/__base__.py index 4fdf31f..b95edf4 100644 --- a/components/__base__.py +++ b/components/__base__.py @@ -2,6 +2,9 @@ from PyQt4 import QtGui class Component: + def __init__(self): + self.currentPreset = None + def __str__(self): return self.__doc__ @@ -72,7 +75,8 @@ class Component: image = Image.new("RGBA", (width, height), (0,0,0,0)) return image - def loadPreset(self, presetDict): + def loadPreset(self, presetDict, presetName=None): + self.currentPreset = presetName # update widgets using a preset dict def savePreset(self): diff --git a/components/color.py b/components/color.py index b050fbd..b13f54e 100644 --- a/components/color.py +++ b/components/color.py @@ -69,7 +69,8 @@ class Component(__base__.Component): r, g, b = self.color1 return Image.new("RGBA", (width, height), (r, g, b, 255)) - def loadPreset(self, pr): + def loadPreset(self, pr, presetName=None): + self.currentPreset = presetName self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) diff --git a/components/image.py b/components/image.py index f9a92ca..6ccddb6 100644 --- a/components/image.py +++ b/components/image.py @@ -48,7 +48,8 @@ class Component(__base__.Component): frame.paste(image) return frame - def loadPreset(self, pr): + def loadPreset(self, pr, presetName=None): + self.currentPreset = presetName self.page.lineEdit_image.setText(pr['image']) def savePreset(self): @@ -60,7 +61,7 @@ class Component(__base__.Component): imgDir = self.settings.value("backgroundDir", os.path.expanduser("~")) filename = QtGui.QFileDialog.getOpenFileName( self.page, "Choose Image", imgDir, "Image Files (*.jpg *.png)") - if filename: + if filename: self.settings.setValue("backgroundDir", os.path.dirname(filename)) self.page.lineEdit_image.setText(filename) self.update() diff --git a/components/original.py b/components/original.py index 4d0e83b..a2059d1 100644 --- a/components/original.py +++ b/components/original.py @@ -37,7 +37,8 @@ class Component(__base__.Component): self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text()) self.parent.drawPreview() - def loadPreset(self, pr): + def loadPreset(self, pr, presetName=None): + self.currentPreset = presetName self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor']) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['visColor']).name() diff --git a/components/text.py b/components/text.py index 6cdc0dd..1725a41 100644 --- a/components/text.py +++ b/components/text.py @@ -78,7 +78,8 @@ class Component(__base__.Component): x = self.xPosition - offset return x, self.yPosition - def loadPreset(self, pr): + def loadPreset(self, pr, presetName=None): + self.currentPreset = presetName self.page.lineEdit_title.setText(pr['title']) font = QFont() font.fromString(pr['titleFont']) diff --git a/components/video.py b/components/video.py index f086cc0..e636224 100644 --- a/components/video.py +++ b/components/video.py @@ -128,7 +128,8 @@ class Component(__base__.Component): def frameRender(self, moduleNo, arrayNo, frameNo): return self.video.frame(frameNo) - def loadPreset(self, pr): + def loadPreset(self, pr, presetName=None): + self.currentPreset = presetName self.page.lineEdit_video.setText(pr['video']) self.page.checkBox_loop.setChecked(pr['loop']) @@ -144,7 +145,7 @@ class Component(__base__.Component): self.page, "Choose Video", imgDir, "Video Files (*.mp4 *.mov)" ) - if filename: + if filename: self.settings.setValue("backgroundDir", os.path.dirname(filename)) self.page.lineEdit_video.setText(filename) self.update() diff --git a/mainwindow.py b/mainwindow.py index 2c1a95a..883d475 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -6,9 +6,7 @@ from PyQt4 import QtCore, QtGui, uic from PyQt4.QtCore import QSettings, Qt from PyQt4.QtGui import QDesktopServices, QMenu import sys -import io import os -import string import signal import filecmp import time @@ -63,24 +61,22 @@ class MainWindow(QtCore.QObject): # Create data directory, load/create settings self.dataDir = QDesktopServices.storageLocation( QDesktopServices.DataLocation) + self.presetManager = PresetManager( + uic.loadUi( + os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'presetmanager.ui')), + self) self.autosavePath = os.path.join(self.dataDir, 'autosave.avp') - self.presetDir = os.path.join(self.dataDir, 'presets') self.settings = QSettings( os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat) LoadDefaultSettings(self) if not os.path.exists(self.dataDir): os.makedirs(self.dataDir) for neededDirectory in ( - self.presetDir, self.settings.value("projectDir")): + self.presetManager.presetDir, self.settings.value("projectDir")): if not os.path.exists(neededDirectory): os.mkdir(neededDirectory) - self.presetManager = PresetManager( - uic.loadUi( - os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'presetmanager.ui')), - self) - # Make queues/timers for the preview thread self.previewQueue = Queue() self.previewThread = QtCore.QThread(self) @@ -140,7 +136,7 @@ class MainWindow(QtCore.QObject): ) vBitrate = int(self.settings.value('outputVideoBitrate')) - aBitrate = int(self.settings.value('outputAudioBitrate')[:-1]) + aBitrate = int(self.settings.value('outputAudioBitrate')) window.spinBox_vBitrate.setValue(vBitrate) window.spinBox_aBitrate.setValue(aBitrate) @@ -420,7 +416,6 @@ class MainWindow(QtCore.QObject): self.window.stackedWidget.addWidget(self.pages[-1]) self.window.stackedWidget.setCurrentIndex(index) self.selectedComponents[-1].update() - '''self.updateOpenPresetComboBox(self.selectedComponents[-1])''' def insertComponent(self, moduleIndex): self.selectedComponents.insert( @@ -432,7 +427,6 @@ class MainWindow(QtCore.QObject): self.window.stackedWidget.insertWidget(0, self.pages[0]) self.window.stackedWidget.setCurrentIndex(0) self.selectedComponents[0].update() - '''self.updateOpenPresetComboBox(self.selectedComponents[0])''' def removeComponent(self): for selected in self.window.listWidget_componentList.selectedItems(): @@ -449,7 +443,6 @@ class MainWindow(QtCore.QObject): if selected: index = self.window.listWidget_componentList.row(selected[0]) self.window.stackedWidget.setCurrentIndex(index) - '''self.updateOpenPresetComboBox(self.selectedComponents[index])''' def moveComponentUp(self): row = self.window.listWidget_componentList.currentRow() @@ -489,87 +482,6 @@ class MainWindow(QtCore.QObject): '''Preset manager for importing, exporting, renaming, deleting''' self.presetManager.show() - def updateOpenPresetComboBox(self, component): - self.window.comboBox_openPreset.clear() - self.window.comboBox_openPreset.addItem("Component Presets") - destination = os.path.join( - self.presetDir, str(component).strip(), str(component.version())) - if not os.path.exists(destination): - os.makedirs(destination) - for f in os.listdir(destination): - self.window.comboBox_openPreset.addItem(f) - - def openSavePresetDialog(self): - if self.window.listWidget_componentList.currentRow() == -1: - return - while True: - newName, OK = QtGui.QInputDialog.getText( - QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:') - badName = False - for letter in newName: - if letter in string.punctuation: - badName = True - if badName: - # some filesystems don't like bizarre characters - self.showMessage(msg="Preset names must contain only \ - letters, numbers, and spaces.") - continue - if OK and newName: - index = self.window.listWidget_componentList.currentRow() - if index != -1: - saveValueStore = \ - self.selectedComponents[index].savePreset() - componentName = str(self.selectedComponents[index]).strip() - vers = self.selectedComponents[index].version() - self.createPresetFile( - componentName, vers, saveValueStore, newName) - break - - def createPresetFile( - self, componentName, version, saveValueStore, filename): - dirname = os.path.join(self.presetDir, componentName, str(version)) - if not os.path.exists(dirname): - os.makedirs(dirname) - filepath = os.path.join(dirname, filename) - if os.path.exists(filepath): - ch = self.showMessage( - msg="%s already exists! Overwrite it?" % filename, - showCancel=True, icon=QtGui.QMessageBox.Warning) - if not ch: - return - # remove old copies of the preset - for i in range(0, self.window.comboBox_openPreset.count()): - if self.window.comboBox_openPreset.itemText(i) == filename: - self.window.comboBox_openPreset.removeItem(i) - with open(filepath, 'w') as f: - f.write(core.Core.stringOrderedDict(saveValueStore)) - self.window.comboBox_openPreset.addItem(filename) - self.window.comboBox_openPreset.setCurrentIndex( - self.window.comboBox_openPreset.count()-1) - - def openPreset(self): - if self.window.comboBox_openPreset.currentIndex() < 1: - return - index = self.window.listWidget_componentList.currentRow() - if index == -1: - return - filename = self.window.comboBox_openPreset.itemText( - self.window.comboBox_openPreset.currentIndex()) - componentName = str(self.selectedComponents[index]).strip() - version = self.selectedComponents[index].version() - dirname = os.path.join(self.presetDir, componentName, str(version)) - filepath = os.path.join(dirname, filename) - if not os.path.exists(filepath): - self.window.comboBox_openPreset.removeItem( - self.window.comboBox_openPreset.currentIndex()) - return - with open(filepath, 'r') as f: - for line in f: - saveValueStore = dict(eval(line.strip())) - break - self.selectedComponents[index].loadPreset(saveValueStore) - self.drawPreview() - def createNewProject(self): self.currentProject = None self.selectedComponents = [] @@ -678,7 +590,8 @@ class MainWindow(QtCore.QObject): def showMessage(self, **kwargs): msg = QtGui.QMessageBox() msg.setText(kwargs['msg']) - msg.setIcon(kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information) + msg.setIcon( + kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information) msg.setDetailedText(kwargs['detail'] if 'detail' in kwargs else None) if 'showCancel'in kwargs and kwargs['showCancel']: msg.setStandardButtons( @@ -691,13 +604,36 @@ class MainWindow(QtCore.QObject): return False def componentContextMenu(self, QPos): + '''Appears when right-clicking a component in the list''' + if not self.window.listWidget_componentList.selectedItems(): + return + + self.presetManager.findPresets() self.menu = QtGui.QMenu() menuItem = self.menu.addAction("Save Preset") - self.connect(menuItem, QtCore.SIGNAL("triggered()"), self.openSavePresetDialog) + self.connect( + menuItem, + QtCore.SIGNAL("triggered()"), + self.presetManager.openSavePresetDialog + ) + + # submenu for opening presets + index = self.window.listWidget_componentList.currentRow() + try: + presets = self.presetManager.presets[str(self.selectedComponents[index])] + self.submenu = QtGui.QMenu("Open Preset") + self.menu.addMenu(self.submenu) + + for version, presetName in presets: + menuItem = self.submenu.addAction(presetName) + self.connect( + menuItem, + QtCore.SIGNAL("triggered()"), + lambda presetName=presetName: + self.presetManager.openPreset(presetName) + ) + except KeyError as e: + print(e) parentPosition = self.window.listWidget_componentList.mapToGlobal(QtCore.QPoint(0, 0)) self.menu.move(parentPosition + QPos) self.menu.show() - - def menuItemClicked(self): - currentItemName=str(self.window.listWidget_componentList.currentItem().text() ) - print(currentItemName) diff --git a/presetmanager.py b/presetmanager.py index cc6c482..f67dbb9 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -1,14 +1,19 @@ from PyQt4 import QtGui -#import sys +from collections import OrderedDict +import string import os +import core + + class PresetManager(QtGui.QDialog): def __init__(self, window, parent): super().__init__() self.parent = parent - self.presetDir = parent.presetDir + self.presetDir = os.path.join(self.parent.dataDir, 'presets') self.window = window - self.presets = self.findPresets() + self.findPresets() + self.lastFilter = '*' # create filter box and preset list self.drawFilterList() @@ -29,7 +34,7 @@ class PresetManager(QtGui.QDialog): for preset in presetList: presetNames.append(preset[1]) self.autocomplete.setStringList(presetNames) - self.presets = self.findPresets() + self.findPresets() self.drawFilterList() self.drawPresetList('*') self.window.show() @@ -47,16 +52,23 @@ class PresetManager(QtGui.QDialog): parseList.append((compName, int(compVers), preset)) except ValueError: continue - return { compName : \ - [ (vers, preset) \ - for name, vers, preset in parseList \ - if name == compName \ - ] \ - for compName, _, __ in parseList \ - } + self.presets =\ + { + compName : \ + [ + (vers, preset) \ + for name, vers, preset in parseList \ + if name == compName \ + ] \ + for compName, _, __ in parseList \ + } - def drawPresetList(self, filter): + def drawPresetList(self, filter=None): self.window.listWidget_presets.clear() + if filter: + self.lastFilter = str(filter) + else: + filter = str(self.lastFilter) for component, presets in self.presets.items(): if filter != '*' and component != filter: continue @@ -68,3 +80,65 @@ class PresetManager(QtGui.QDialog): self.window.comboBox_filter.addItem('*') for component in self.presets: self.window.comboBox_filter.addItem(component) + + def openSavePresetDialog(self): + window = self.parent.window + if window.listWidget_componentList.currentRow() == -1: + return + while True: + dialog = QtGui.QInputDialog( + QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:') + dialog.setTextValue() + newName, OK = dialog.getText() + badName = False + for letter in newName: + if letter in string.punctuation: + badName = True + if badName: + # some filesystems don't like bizarre characters + self.parent.showMessage(msg=\ +'''Preset names must contain only letters, numbers, and spaces.''') + continue + if OK and newName: + index = window.listWidget_componentList.currentRow() + if index != -1: + saveValueStore = \ + self.parent.selectedComponents[index].savePreset() + componentName = str(self.parent.selectedComponents[index]).strip() + vers = self.parent.selectedComponents[index].version() + self.createPresetFile( + componentName, vers, saveValueStore, newName) + break + + def createPresetFile(self, compName, vers, saveValueStore, filename): + dirname = os.path.join(self.presetDir, compName, str(vers)) + if not os.path.exists(dirname): + os.makedirs(dirname) + filepath = os.path.join(dirname, filename) + if os.path.exists(filepath): + ch = self.parent.showMessage( + msg="%s already exists! Overwrite it?" % filename, + showCancel=True, icon=QtGui.QMessageBox.Warning) + if not ch: + return + with open(filepath, 'w') as f: + f.write(core.Core.stringOrderedDict(saveValueStore)) + self.drawPresetList() + + def openPreset(self, presetName): + index = self.parent.window.listWidget_componentList.currentRow() + if index == -1: + return + componentName = str(self.parent.selectedComponents[index]).strip() + version = self.parent.selectedComponents[index].version() + dirname = os.path.join(self.presetDir, componentName, str(version)) + filepath = os.path.join(dirname, presetName) + if not os.path.exists(filepath): + return + with open(filepath, 'r') as f: + for line in f: + saveValueStore = dict(eval(line.strip())) + break + self.parent.selectedComponents[index].loadPreset(saveValueStore) + self.parent.drawPreview() + From 6079c4fd24aecf2ecfed0528c1427a74d596993f Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 8 Jun 2017 09:56:57 -0400 Subject: [PATCH 05/22] drag'n'drop componentList, move component code to core.py FIXME: finish implementing drag'n'drop, Down button --- core.py | 32 ++++++++++++++ mainwindow.py | 112 +++++++++++++++++++++-------------------------- mainwindow.ui | 11 +++-- presetmanager.py | 56 ++++++++++++++---------- 4 files changed, 121 insertions(+), 90 deletions(-) diff --git a/core.py b/core.py index 7b3c69a..0fa5ec5 100644 --- a/core.py +++ b/core.py @@ -12,6 +12,7 @@ import atexit import time from collections import OrderedDict import json +from importlib import import_module class Core(): @@ -26,6 +27,37 @@ class Core(): self.wd = os.path.dirname(os.path.realpath(__file__)) self.loadEncoderOptions() + self.modules = self.findComponents() + self.selectedComponents = [] + + def findComponents(self): + def findComponents(): + srcPath = os.path.join(self.wd, 'components') + if os.path.exists(srcPath): + for f in sorted(os.listdir(srcPath)): + name, ext = os.path.splitext(f) + if name.startswith("__"): + continue + elif ext == '.py': + yield name + return [ + import_module('components.%s' % name) + for name in findComponents()] + + def insertComponent(self, compPos, moduleIndex): + self.selectedComponents.insert( + compPos, + self.modules[moduleIndex].Component()) + return compPos #if compPos > -1 else len(self.selectedComponents)-1 + + def moveComponent(self, startI, endI): + comp = self.selectedComponents.pop(startI) + i = self.selectedComponents.insert(endI, comp) + return i + + def updateComponent(self, i): + self.selectedComponents[i].update() + def loadEncoderOptions(self): file_path = os.path.join(self.wd, 'encoder-options.json') with open(file_path) as json_file: diff --git a/mainwindow.py b/mainwindow.py index 883d475..f24fc66 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -1,6 +1,5 @@ from os.path import expanduser from queue import Queue -from importlib import import_module from collections import OrderedDict from PyQt4 import QtCore, QtGui, uic from PyQt4.QtCore import QSettings, Qt @@ -54,8 +53,9 @@ class MainWindow(QtCore.QObject): # print('main thread id: {}'.format(QtCore.QThread.currentThreadId())) self.window = window self.core = core.Core() - self.pages = [] - self.selectedComponents = [] + + self.pages = [] # widgets of component settings + self.componentRows = {} # QListWidgetItems self.lastAutosave = time.time() # Create data directory, load/create settings @@ -149,16 +149,16 @@ class MainWindow(QtCore.QObject): window.verticalLayout_previewWrapper.addWidget(self.previewWindow) # Make component buttons - self.modules = self.findComponents() self.compMenu = QMenu() - for i, comp in enumerate(self.modules): + for i, comp in enumerate(self.core.modules): action = self.compMenu.addAction(comp.Component.__doc__) action.triggered[()].connect( lambda item=i: self.insertComponent(item)) self.window.pushButton_addComponent.setMenu(self.compMenu) - window.listWidget_componentList.clicked.connect( + self.window.listWidget_componentList.dropEvent = self.componentMoved + self.window.listWidget_componentList.clicked.connect( lambda _: self.changeComponentWidget()) self.window.pushButton_removeComponent.clicked.connect( @@ -183,8 +183,8 @@ class MainWindow(QtCore.QObject): self.window.pushButton_listMoveUp.clicked.connect( self.moveComponentUp) - self.window.pushButton_listMoveDown.clicked.connect( - self.moveComponentDown) + #self.window.pushButton_listMoveDown.clicked.connect( + # self.moveComponentDown) # Configure the Projects Menu self.projectMenu = QMenu() @@ -323,7 +323,7 @@ class MainWindow(QtCore.QObject): self.videoTask.emit( self.window.lineEdit_audioFile.text(), self.window.lineEdit_outputFile.text(), - self.selectedComponents) + self.core.selectedComponents) else: self.showMessage( msg="You must select an audio file and output filename.") @@ -384,56 +384,35 @@ class MainWindow(QtCore.QObject): self.drawPreview() def drawPreview(self): - self.newTask.emit(self.selectedComponents) + self.newTask.emit(self.core.selectedComponents) # self.processTask.emit() self.autosave() def showPreviewImage(self, image): self.previewWindow.changePixmap(image) - def findComponents(self): - def findComponents(): - srcPath = os.path.join( - os.path.dirname(os.path.realpath(__file__)), 'components') - if os.path.exists(srcPath): - for f in sorted(os.listdir(srcPath)): - name, ext = os.path.splitext(f) - if name.startswith("__"): - continue - elif ext == '.py': - yield name - return [ - import_module('components.%s' % name) - for name in findComponents()] + def insertComponent(self, moduleIndex, compPos=0): + componentList = self.window.listWidget_componentList - def addComponent(self, moduleIndex): - index = len(self.pages) - self.selectedComponents.append(self.modules[moduleIndex].Component()) - self.window.listWidget_componentList.addItem( - self.selectedComponents[-1].__doc__) - self.pages.append(self.selectedComponents[-1].widget(self)) - self.window.listWidget_componentList.setCurrentRow(index) - self.window.stackedWidget.addWidget(self.pages[-1]) + index = self.core.insertComponent( + compPos, moduleIndex) + row = componentList.insertItem( + index, + self.core.selectedComponents[index].__doc__) + self.componentRows[index] = componentList.row(row) + componentList.setCurrentRow(index) + + self.pages.insert(index, self.core.selectedComponents[index].widget(self)) + self.window.stackedWidget.insertWidget(index, self.pages[index]) self.window.stackedWidget.setCurrentIndex(index) - self.selectedComponents[-1].update() - - def insertComponent(self, moduleIndex): - self.selectedComponents.insert( - 0, self.modules[moduleIndex].Component()) - self.window.listWidget_componentList.insertItem( - 0, self.selectedComponents[0].__doc__) - self.pages.insert(0, self.selectedComponents[0].widget(self)) - self.window.listWidget_componentList.setCurrentRow(0) - self.window.stackedWidget.insertWidget(0, self.pages[0]) - self.window.stackedWidget.setCurrentIndex(0) - self.selectedComponents[0].update() + self.core.updateComponent(index) def removeComponent(self): for selected in self.window.listWidget_componentList.selectedItems(): index = self.window.listWidget_componentList.row(selected) self.window.stackedWidget.removeWidget(self.pages[index]) self.window.listWidget_componentList.takeItem(index) - self.selectedComponents.pop(index) + self.core.selectedComponents.pop(index) self.pages.pop(index) self.changeComponentWidget() self.drawPreview() @@ -447,20 +426,21 @@ class MainWindow(QtCore.QObject): def moveComponentUp(self): row = self.window.listWidget_componentList.currentRow() if row > 0: - module = self.selectedComponents[row] - self.selectedComponents.pop(row) - self.selectedComponents.insert(row - 1, module) - page = self.pages[row] - self.pages.pop(row) + self.core.moveComponent(row, row - 1) + page = self.pages.pop(row) self.pages.insert(row - 1, page) - item = self.window.listWidget_componentList.takeItem(row) - self.window.listWidget_componentList.insertItem(row - 1, item) - widget = self.window.stackedWidget.removeWidget(page) - self.window.stackedWidget.insertWidget(row - 1, page) - self.window.listWidget_componentList.setCurrentRow(row - 1) - self.window.stackedWidget.setCurrentIndex(row - 1) - self.drawPreview() + # update widgets + componentList = self.window.listWidget_componentList + stackedWidget = self.window.stackedWidget + item = componentList.takeItem(row) + componentList.insertItem(row - 1, item) + widget = stackedWidget.removeWidget(page) + stackedWidget.insertWidget(row - 1, page) + componentList.setCurrentRow(row - 1) + stackedWidget.setCurrentIndex(row - 1) + self.drawPreview() + ''' def moveComponentDown(self): row = self.window.listWidget_componentList.currentRow() if row != -1 and row < len(self.pages)+1: @@ -477,6 +457,12 @@ class MainWindow(QtCore.QObject): self.window.listWidget_componentList.setCurrentRow(row + 1) self.window.stackedWidget.setCurrentIndex(row + 1) self.drawPreview() + ''' + def componentMoved(self, event): + widget = self.window.listWidget_componentList + for i in range(widget.count()): + pass + #print(widget.item(i) == self.componentRows[i]) def openPresetManager(self): '''Preset manager for importing, exporting, renaming, deleting''' @@ -484,7 +470,7 @@ class MainWindow(QtCore.QObject): def createNewProject(self): self.currentProject = None - self.selectedComponents = [] + self.core.selectedComponents = [] self.window.listWidget_componentList.clear() for widget in self.pages: self.window.stackedWidget.removeWidget(widget) @@ -513,7 +499,7 @@ class MainWindow(QtCore.QObject): with open(filepath, 'w') as f: print('creating %s' % filepath) f.write('[Components]\n') - for comp in self.selectedComponents: + for comp in self.core.selectedComponents: saveValueStore = comp.savePreset() f.write('%s\n' % str(comp)) f.write('%s\n' % str(comp.version())) @@ -538,7 +524,7 @@ class MainWindow(QtCore.QObject): self.currentProject = filepath self.settings.setValue("currentProject", filepath) self.settings.setValue("projectDir", os.path.dirname(filepath)) - compNames = [mod.Component.__doc__ for mod in self.modules] + compNames = [mod.Component.__doc__ for mod in self.core.modules] try: with open(filepath, 'r') as f: validSections = ('Components') @@ -563,14 +549,14 @@ class MainWindow(QtCore.QObject): if line and section == 'Components': if i == 0: compIndex = compNames.index(line) - self.addComponent(compIndex) + self.insertComponent(compIndex, -1) i += 1 elif i == 1: # version, not used yet i += 1 elif i == 2: saveValueStore = dict(eval(line)) - self.selectedComponents[-1].loadPreset( + self.core.selectedComponents[-1].loadPreset( saveValueStore) i = 0 except (IndexError, ValueError, NameError, SyntaxError, @@ -620,7 +606,7 @@ class MainWindow(QtCore.QObject): # submenu for opening presets index = self.window.listWidget_componentList.currentRow() try: - presets = self.presetManager.presets[str(self.selectedComponents[index])] + presets = self.presetManager.presets[str(self.core.selectedComponents[index])] self.submenu = QtGui.QMenu("Open Preset") self.menu.addMenu(self.submenu) diff --git a/mainwindow.ui b/mainwindow.ui index 62e0632..e809ee8 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -184,17 +184,20 @@ 1 - - false + + true - false + true false - QAbstractItemView::NoDragDrop + QAbstractItemView::InternalMove + + + Qt::MoveAction diff --git a/presetmanager.py b/presetmanager.py index f67dbb9..50efd8d 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -86,28 +86,35 @@ class PresetManager(QtGui.QDialog): if window.listWidget_componentList.currentRow() == -1: return while True: - dialog = QtGui.QInputDialog( - QtGui.QWidget(), 'Audio Visualizer', 'New Preset Name:') - dialog.setTextValue() - newName, OK = dialog.getText() - badName = False - for letter in newName: - if letter in string.punctuation: - badName = True - if badName: - # some filesystems don't like bizarre characters - self.parent.showMessage(msg=\ -'''Preset names must contain only letters, numbers, and spaces.''') - continue - if OK and newName: - index = window.listWidget_componentList.currentRow() - if index != -1: - saveValueStore = \ - self.parent.selectedComponents[index].savePreset() - componentName = str(self.parent.selectedComponents[index]).strip() - vers = self.parent.selectedComponents[index].version() - self.createPresetFile( - componentName, vers, saveValueStore, newName) + index = window.listWidget_componentList.currentRow() + currentPreset = self.parent.selectedComponents[index].currentPreset + newName, OK = QtGui.QInputDialog.getText( + self.parent.window, + 'Audio Visualizer', + 'New Preset Name:', + QtGui.QLineEdit.Normal, + currentPreset + ) + if OK: + badName = False + for letter in newName: + if letter in string.punctuation: + badName = True + if badName: + # some filesystems don't like bizarre characters + self.parent.showMessage( + msg='Preset names must contain only letters,' + 'numbers, and spaces.') + continue + if newName: + if index != -1: + saveValueStore = \ + self.parent.selectedComponents[index].savePreset() + componentName = str(self.parent.selectedComponents[index]).strip() + vers = self.parent.selectedComponents[index].version() + self.createPresetFile( + componentName, vers, saveValueStore, newName) + self.parent.selectedComponents[index].currentPreset = newName break def createPresetFile(self, compName, vers, saveValueStore, filename): @@ -139,6 +146,9 @@ class PresetManager(QtGui.QDialog): for line in f: saveValueStore = dict(eval(line.strip())) break - self.parent.selectedComponents[index].loadPreset(saveValueStore) + self.parent.selectedComponents[index].loadPreset( + saveValueStore, + presetName + ) self.parent.drawPreview() From bb1e54b31eb6157ef041764cfccd60484a0e02d8 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 8 Jun 2017 16:50:48 -0400 Subject: [PATCH 06/22] saved preset titles, code clean-ups componentList drag'n'drop disabled for now; will work on it in another branch --- components/color.py | 3 +- components/image.py | 3 +- components/original.py | 3 +- components/text.py | 3 +- components/video.py | 3 +- core.py | 58 +++++++++++---- mainwindow.py | 161 ++++++++++++++++++++++++----------------- mainwindow.ui | 2 +- presetmanager.py | 42 ++++++----- 9 files changed, 170 insertions(+), 108 deletions(-) diff --git a/components/color.py b/components/color.py index b13f54e..a86927b 100644 --- a/components/color.py +++ b/components/color.py @@ -70,7 +70,7 @@ class Component(__base__.Component): return Image.new("RGBA", (width, height), (r, g, b, 255)) def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName + self.currentPreset = presetName if presetName else pr['preset'] self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) @@ -85,6 +85,7 @@ class Component(__base__.Component): def savePreset(self): return { + 'preset': self.currentPreset, 'color1': self.color1, 'color2': self.color2, } diff --git a/components/image.py b/components/image.py index 6ccddb6..441e0e1 100644 --- a/components/image.py +++ b/components/image.py @@ -49,11 +49,12 @@ class Component(__base__.Component): return frame def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName + self.currentPreset = presetName if presetName else pr['preset'] self.page.lineEdit_image.setText(pr['image']) def savePreset(self): return { + 'preset': self.currentPreset, 'image': self.imagePath, } diff --git a/components/original.py b/components/original.py index a2059d1..7873f43 100644 --- a/components/original.py +++ b/components/original.py @@ -38,7 +38,7 @@ class Component(__base__.Component): self.parent.drawPreview() def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName + self.currentPreset = presetName if presetName else pr['preset'] self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor']) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['visColor']).name() @@ -47,6 +47,7 @@ class Component(__base__.Component): def savePreset(self): return { + 'preset': self.currentPreset, 'layout': self.layout, 'visColor': self.visColor, } diff --git a/components/text.py b/components/text.py index 1725a41..68cffca 100644 --- a/components/text.py +++ b/components/text.py @@ -79,7 +79,7 @@ class Component(__base__.Component): return x, self.yPosition def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName + self.currentPreset = presetName if presetName else pr['preset'] self.page.lineEdit_title.setText(pr['title']) font = QFont() font.fromString(pr['titleFont']) @@ -95,6 +95,7 @@ class Component(__base__.Component): def savePreset(self): return { + 'preset': self.currentPreset, 'title': self.title, 'titleFont': self.titleFont.toString(), 'alignment': self.alignment, diff --git a/components/video.py b/components/video.py index e636224..c529658 100644 --- a/components/video.py +++ b/components/video.py @@ -129,12 +129,13 @@ class Component(__base__.Component): return self.video.frame(frameNo) def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName + self.currentPreset = presetName if presetName else pr['preset'] self.page.lineEdit_video.setText(pr['video']) self.page.checkBox_loop.setChecked(pr['loop']) def savePreset(self): return { + 'preset': self.currentPreset, 'video': self.videoPath, 'loop': self.loopVideo, } diff --git a/core.py b/core.py index 0fa5ec5..797749d 100644 --- a/core.py +++ b/core.py @@ -6,29 +6,34 @@ from os.path import expanduser import subprocess as sp import numpy from PIL import Image -import tempfile +#import tempfile from shutil import rmtree -import atexit +#import atexit import time from collections import OrderedDict import json from importlib import import_module +from PyQt4.QtGui import QDesktopServices class Core(): def __init__(self): self.FFMPEG_BIN = self.findFfmpeg() - self.tempDir = os.path.join( - tempfile.gettempdir(), 'audio-visualizer-python-data') - if not os.path.exists(self.tempDir): - os.makedirs(self.tempDir) - atexit.register(self.deleteTempDir) + #self.tempDir = os.path.join( + # tempfile.gettempdir(), 'audio-visualizer-python-data') + #if not os.path.exists(self.tempDir): + # os.makedirs(self.tempDir) + #atexit.register(self.deleteTempDir) + self.dataDir = QDesktopServices.storageLocation( + QDesktopServices.DataLocation) + self.presetDir = os.path.join(self.dataDir, 'presets') self.wd = os.path.dirname(os.path.realpath(__file__)) self.loadEncoderOptions() self.modules = self.findComponents() self.selectedComponents = [] + self.selectedModules = [] def findComponents(self): def findComponents(): @@ -45,19 +50,40 @@ class Core(): for name in findComponents()] def insertComponent(self, compPos, moduleIndex): + if compPos < 0: + compPos = len(self.selectedComponents) -1 self.selectedComponents.insert( compPos, - self.modules[moduleIndex].Component()) - return compPos #if compPos > -1 else len(self.selectedComponents)-1 + self.modules[moduleIndex].Component() + ) + self.selectedModules.insert( + compPos, + moduleIndex + ) + return compPos def moveComponent(self, startI, endI): comp = self.selectedComponents.pop(startI) - i = self.selectedComponents.insert(endI, comp) - return i + self.selectedComponents.insert(endI, comp) + i = self.selectedModules.pop(startI) + self.selectedModules.insert(endI, i) + return endI def updateComponent(self, i): + print('updating %s' % self.selectedComponents[i]) self.selectedComponents[i].update() + def moduleIndexFor(self, compIndex): + return self.selectedModules[compIndex] + + def createPresetFile(self, compName, vers, saveValueStore, filename): + dirname = os.path.join(self.presetDir, compName, str(vers)) + if not os.path.exists(dirname): + os.makedirs(dirname) + filepath = os.path.join(dirname, filename) + with open(filepath, 'w') as f: + f.write(Core.stringOrderedDict(saveValueStore)) + def loadEncoderOptions(self): file_path = os.path.join(self.wd, 'encoder-options.json') with open(file_path) as json_file: @@ -139,11 +165,11 @@ class Core(): return completeAudioArray - def deleteTempDir(self): - try: - rmtree(self.tempDir) - except FileNotFoundError: - pass + #def deleteTempDir(self): + # try: + # rmtree(self.tempDir) + # except FileNotFoundError: + # pass def cancel(self): self.canceled = True diff --git a/mainwindow.py b/mainwindow.py index f24fc66..8812e7f 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -3,7 +3,7 @@ from queue import Queue from collections import OrderedDict from PyQt4 import QtCore, QtGui, uic from PyQt4.QtCore import QSettings, Qt -from PyQt4.QtGui import QDesktopServices, QMenu +from PyQt4.QtGui import QMenu import sys import os import signal @@ -55,12 +55,11 @@ class MainWindow(QtCore.QObject): self.core = core.Core() self.pages = [] # widgets of component settings - self.componentRows = {} # QListWidgetItems + self.componentRows = [] # (moduleIndex, QListWidgetItem) tuples self.lastAutosave = time.time() # Create data directory, load/create settings - self.dataDir = QDesktopServices.storageLocation( - QDesktopServices.DataLocation) + self.dataDir = self.core.dataDir self.presetManager = PresetManager( uic.loadUi( os.path.join(os.path.dirname(os.path.realpath(__file__)), @@ -73,7 +72,7 @@ class MainWindow(QtCore.QObject): if not os.path.exists(self.dataDir): os.makedirs(self.dataDir) for neededDirectory in ( - self.presetManager.presetDir, self.settings.value("projectDir")): + self.core.presetDir, self.settings.value("projectDir")): if not os.path.exists(neededDirectory): os.mkdir(neededDirectory) @@ -90,6 +89,8 @@ class MainWindow(QtCore.QObject): self.timer.start(500) # Begin decorating the window and connecting events + componentList = self.window.listWidget_componentList + window.toolButton_selectAudioFile.clicked.connect( self.openInputFileDialog) @@ -120,7 +121,7 @@ class MainWindow(QtCore.QObject): codec = window.comboBox_videoCodec.itemText(i) if codec == self.settings.value('outputVideoCodec'): window.comboBox_videoCodec.setCurrentIndex(i) - print(codec) + #print(codec) for i in range(window.comboBox_audioCodec.count()): codec = window.comboBox_audioCodec.itemText(i) @@ -157,17 +158,17 @@ class MainWindow(QtCore.QObject): self.window.pushButton_addComponent.setMenu(self.compMenu) - self.window.listWidget_componentList.dropEvent = self.componentMoved - self.window.listWidget_componentList.clicked.connect( + componentList.dropEvent = self.componentListChanged + componentList.clicked.connect( lambda _: self.changeComponentWidget()) self.window.pushButton_removeComponent.clicked.connect( lambda _: self.removeComponent()) - self.window.listWidget_componentList.setContextMenuPolicy( + componentList.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) - self.window.listWidget_componentList.connect( - self.window.listWidget_componentList, + componentList.connect( + componentList, QtCore.SIGNAL("customContextMenuRequested(QPoint)"), self.componentContextMenu) @@ -182,9 +183,11 @@ class MainWindow(QtCore.QObject): self.updateResolution) self.window.pushButton_listMoveUp.clicked.connect( - self.moveComponentUp) - #self.window.pushButton_listMoveDown.clicked.connect( - # self.moveComponentDown) + lambda: self.moveComponent(-1) + ) + self.window.pushButton_listMoveDown.clicked.connect( + lambda: self.moveComponent(1) + ) # Configure the Projects Menu self.projectMenu = QMenu() @@ -210,8 +213,7 @@ class MainWindow(QtCore.QObject): # Show the window and load current project window.show() self.currentProject = self.settings.value("currentProject") - if self.currentProject and os.path.exists(self.autosavePath) \ - and filecmp.cmp(self.autosavePath, self.currentProject): + if self.autosaveExists(): # delete autosave if it's identical to the project os.remove(self.autosavePath) @@ -235,6 +237,14 @@ class MainWindow(QtCore.QObject): self.previewThread.wait() self.autosave() + def updateComponentTitle(self, pos): + 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 + self.window.listWidget_componentList.item(pos).setText(title) + def updateCodecs(self): containerWidget = self.window.comboBox_videoContainer vCodecWidget = self.window.comboBox_videoCodec @@ -270,12 +280,20 @@ class MainWindow(QtCore.QObject): self.settings.setValue('outputAudioBitrate', currentAudioBitrate) def autosave(self): - if time.time() - self.lastAutosave >= 2.0: + if not self.currentProject: if os.path.exists(self.autosavePath): os.remove(self.autosavePath) + elif time.time() - self.lastAutosave >= 2.0: self.createProjectFile(self.autosavePath) self.lastAutosave = time.time() + def autosaveExists(self): + if self.currentProject and os.path.exists(self.autosavePath) \ + and filecmp.cmp(self.autosavePath, self.currentProject): + return True + else: + return False + def openInputFileDialog(self): inputDir = self.settings.value("inputDir", expanduser("~")) @@ -393,88 +411,94 @@ class MainWindow(QtCore.QObject): def insertComponent(self, moduleIndex, compPos=0): componentList = self.window.listWidget_componentList + stackedWidget = self.window.stackedWidget + if compPos < 0: + compPos = componentList.count() index = self.core.insertComponent( compPos, moduleIndex) row = componentList.insertItem( index, self.core.selectedComponents[index].__doc__) - self.componentRows[index] = componentList.row(row) + self.componentRows.insert(compPos, (moduleIndex, row)) componentList.setCurrentRow(index) self.pages.insert(index, self.core.selectedComponents[index].widget(self)) - self.window.stackedWidget.insertWidget(index, self.pages[index]) - self.window.stackedWidget.setCurrentIndex(index) + stackedWidget.insertWidget(index, self.pages[index]) + stackedWidget.setCurrentIndex(index) + self.core.updateComponent(index) def removeComponent(self): - for selected in self.window.listWidget_componentList.selectedItems(): - index = self.window.listWidget_componentList.row(selected) + componentList = self.window.listWidget_componentList + + for selected in componentList.selectedItems(): + index = componentList.row(selected) self.window.stackedWidget.removeWidget(self.pages[index]) - self.window.listWidget_componentList.takeItem(index) + componentList.takeItem(index) + self.componentRows.pop(index) self.core.selectedComponents.pop(index) self.pages.pop(index) self.changeComponentWidget() self.drawPreview() + 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.componentRows.pop(row) + self.componentRows.insert(newRow, (self.core.moduleIndexFor(row), newItem)) + self.drawPreview() + + def componentListChanged(self, *args): + '''Update all our tracking variables to match the widget''' + pass + 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 moveComponentUp(self): - row = self.window.listWidget_componentList.currentRow() - if row > 0: - self.core.moveComponent(row, row - 1) - page = self.pages.pop(row) - self.pages.insert(row - 1, page) - - # update widgets - componentList = self.window.listWidget_componentList - stackedWidget = self.window.stackedWidget - item = componentList.takeItem(row) - componentList.insertItem(row - 1, item) - widget = stackedWidget.removeWidget(page) - stackedWidget.insertWidget(row - 1, page) - componentList.setCurrentRow(row - 1) - stackedWidget.setCurrentIndex(row - 1) - self.drawPreview() - ''' - def moveComponentDown(self): - row = self.window.listWidget_componentList.currentRow() - if row != -1 and row < len(self.pages)+1: - module = self.selectedComponents[row] - self.selectedComponents.pop(row) - self.selectedComponents.insert(row + 1, module) - page = self.pages[row] - self.pages.pop(row) - self.pages.insert(row + 1, page) - item = self.window.listWidget_componentList.takeItem(row) - self.window.listWidget_componentList.insertItem(row + 1, item) - widget = self.window.stackedWidget.removeWidget(page) - self.window.stackedWidget.insertWidget(row + 1, page) - self.window.listWidget_componentList.setCurrentRow(row + 1) - self.window.stackedWidget.setCurrentIndex(row + 1) - self.drawPreview() - ''' - def componentMoved(self, event): - widget = self.window.listWidget_componentList - for i in range(widget.count()): - pass - #print(widget.item(i) == self.componentRows[i]) - def openPresetManager(self): '''Preset manager for importing, exporting, renaming, deleting''' self.presetManager.show() - def createNewProject(self): - self.currentProject = None + def clear(self): + '''Get a blank slate''' self.core.selectedComponents = [] self.window.listWidget_componentList.clear() for widget in self.pages: self.window.stackedWidget.removeWidget(widget) self.pages = [] + + def createNewProject(self): + if self.autosaveExists(): + ch = self.showMessage( + msg="You have unsaved changes in project '%s'. " + "Save before starting a new project?" + % os.path.basename(self.currentProject)[:-4], + showCancel=True) + if ch: + self.saveCurrentProject() + + self.clear() + self.currentProject = None self.settings.setValue("currentProject", None) self.drawPreview() @@ -496,6 +520,8 @@ class MainWindow(QtCore.QObject): def createProjectFile(self, filepath): if not filepath.endswith(".avp"): filepath += '.avp' + if os.path.exists(filepath): + os.remove(filepath) with open(filepath, 'w') as f: print('creating %s' % filepath) f.write('[Components]\n') @@ -520,7 +546,7 @@ class MainWindow(QtCore.QObject): if not filepath or not os.path.exists(filepath) \ or not filepath.endswith('.avp'): return - self.createNewProject() + self.clear() self.currentProject = filepath self.settings.setValue("currentProject", filepath) self.settings.setValue("projectDir", os.path.dirname(filepath)) @@ -558,6 +584,7 @@ class MainWindow(QtCore.QObject): saveValueStore = dict(eval(line)) self.core.selectedComponents[-1].loadPreset( saveValueStore) + self.updateComponentTitle(-1) i = 0 except (IndexError, ValueError, NameError, SyntaxError, AttributeError, TypeError) as e: diff --git a/mainwindow.ui b/mainwindow.ui index e809ee8..af47cee 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -188,7 +188,7 @@ true - true + false false diff --git a/presetmanager.py b/presetmanager.py index 50efd8d..268754e 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -10,7 +10,8 @@ class PresetManager(QtGui.QDialog): def __init__(self, window, parent): super().__init__() self.parent = parent - self.presetDir = os.path.join(self.parent.dataDir, 'presets') + self.core = self.parent.core + self.presetDir = self.core.presetDir self.window = window self.findPresets() self.lastFilter = '*' @@ -83,11 +84,14 @@ class PresetManager(QtGui.QDialog): def openSavePresetDialog(self): window = self.parent.window - if window.listWidget_componentList.currentRow() == -1: + self.selectedComponents = self.parent.core.selectedComponents + componentList = window.listWidget_componentList + + if componentList.currentRow() == -1: return while True: - index = window.listWidget_componentList.currentRow() - currentPreset = self.parent.selectedComponents[index].currentPreset + index = componentList.currentRow() + currentPreset = self.selectedComponents[index].currentPreset newName, OK = QtGui.QInputDialog.getText( self.parent.window, 'Audio Visualizer', @@ -109,35 +113,34 @@ class PresetManager(QtGui.QDialog): if newName: if index != -1: saveValueStore = \ - self.parent.selectedComponents[index].savePreset() - componentName = str(self.parent.selectedComponents[index]).strip() - vers = self.parent.selectedComponents[index].version() + self.selectedComponents[index].savePreset() + componentName = str(self.selectedComponents[index]).strip() + vers = self.selectedComponents[index].version() self.createPresetFile( componentName, vers, saveValueStore, newName) - self.parent.selectedComponents[index].currentPreset = newName + self.selectedComponents[index].currentPreset = newName break def createPresetFile(self, compName, vers, saveValueStore, filename): - dirname = os.path.join(self.presetDir, compName, str(vers)) - if not os.path.exists(dirname): - os.makedirs(dirname) - filepath = os.path.join(dirname, filename) - if os.path.exists(filepath): + path = os.path.join(self.presetDir, compName, str(vers), filename) + if os.path.exists(path): ch = self.parent.showMessage( msg="%s already exists! Overwrite it?" % filename, showCancel=True, icon=QtGui.QMessageBox.Warning) if not ch: return - with open(filepath, 'w') as f: - f.write(core.Core.stringOrderedDict(saveValueStore)) + self.core.createPresetFile(compName, vers, saveValueStore, filename) self.drawPresetList() def openPreset(self, presetName): - index = self.parent.window.listWidget_componentList.currentRow() + componentList = self.parent.window.listWidget_componentList + selectedComponents = self.parent.core.selectedComponents + + index = componentList.currentRow() if index == -1: return - componentName = str(self.parent.selectedComponents[index]).strip() - version = self.parent.selectedComponents[index].version() + componentName = str(selectedComponents[index]).strip() + version = selectedComponents[index].version() dirname = os.path.join(self.presetDir, componentName, str(version)) filepath = os.path.join(dirname, presetName) if not os.path.exists(filepath): @@ -146,9 +149,10 @@ class PresetManager(QtGui.QDialog): for line in f: saveValueStore = dict(eval(line.strip())) break - self.parent.selectedComponents[index].loadPreset( + selectedComponents[index].loadPreset( saveValueStore, presetName ) + self.parent.updateComponentTitle(index) self.parent.drawPreview() From 4fc73f1e094289b50f828f0a3128d710e1d9ec4c Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 8 Jun 2017 20:32:25 -0400 Subject: [PATCH 07/22] rename and delete buttons in preset manager --- components/color.py | 16 +++--- core.py | 10 ++++ presetmanager.py | 116 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 118 insertions(+), 24 deletions(-) diff --git a/components/color.py b/components/color.py index a86927b..11c1b19 100644 --- a/components/color.py +++ b/components/color.py @@ -20,14 +20,14 @@ class Component(__base__.Component): page.lineEdit_color1.setText('%s,%s,%s' % self.color1) page.lineEdit_color2.setText('%s,%s,%s' % self.color2) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.color1).name() - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.color2).name() - page.pushButton_color1.setStyleSheet(btnStyle) - page.pushButton_color2.setStyleSheet(btnStyle) + page.pushButton_color1.setStyleSheet(btnStyle1) + page.pushButton_color2.setStyleSheet(btnStyle2) page.pushButton_color1.clicked.connect(lambda: self.pickColor(1)) page.pushButton_color2.clicked.connect(lambda: self.pickColor(2)) @@ -74,14 +74,14 @@ class Component(__base__.Component): self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + btnStyle1 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['color1']).name() - btnStyle = "QPushButton { background-color : %s; outline: none; }" \ + btnStyle2 = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['color2']).name() - self.page.pushButton_color1.setStyleSheet(btnStyle) - self.page.pushButton_color2.setStyleSheet(btnStyle) + self.page.pushButton_color1.setStyleSheet(btnStyle1) + self.page.pushButton_color2.setStyleSheet(btnStyle2) def savePreset(self): return { diff --git a/core.py b/core.py index 797749d..06367cf 100644 --- a/core.py +++ b/core.py @@ -14,6 +14,7 @@ from collections import OrderedDict import json from importlib import import_module from PyQt4.QtGui import QDesktopServices +import string class Core(): @@ -177,6 +178,15 @@ class Core(): def reset(self): self.canceled = False + @staticmethod + def badName(name): + '''Returns whether a name contains non-alphanumeric chars''' + badName = False + for letter in name: + if letter in string.punctuation: + badName = True + return badName + @staticmethod def stringOrderedDict(dictionary): sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) diff --git a/presetmanager.py b/presetmanager.py index 268754e..d7189b1 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -15,6 +15,11 @@ class PresetManager(QtGui.QDialog): self.window = window self.findPresets() self.lastFilter = '*' + self.presetRows = [] # list of (comp, vers, name) tuples + + # connect button signals + self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog) + self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog) # create filter box and preset list self.drawFilterList() @@ -30,6 +35,7 @@ class PresetManager(QtGui.QDialog): self.window.lineEdit_search.setCompleter(completer) def show(self): + '''Open a new preset manager window from the mainwindow''' presetNames = [] for presetList in self.presets.values(): for preset in presetList: @@ -70,11 +76,13 @@ class PresetManager(QtGui.QDialog): self.lastFilter = str(filter) else: filter = str(self.lastFilter) + self.presetRows = [] for component, presets in self.presets.items(): if filter != '*' and component != filter: continue for vers, preset in presets: self.window.listWidget_presets.addItem('%s: %s' % (component, preset)) + self.presetRows.append((component, vers, preset)) def drawFilterList(self): self.window.comboBox_filter.clear() @@ -83,9 +91,10 @@ class PresetManager(QtGui.QDialog): self.window.comboBox_filter.addItem(component) def openSavePresetDialog(self): + '''Functions on mainwindow level from the context menu''' window = self.parent.window self.selectedComponents = self.parent.core.selectedComponents - componentList = window.listWidget_componentList + componentList = self.parent.window.listWidget_componentList if componentList.currentRow() == -1: return @@ -100,15 +109,8 @@ class PresetManager(QtGui.QDialog): currentPreset ) if OK: - badName = False - for letter in newName: - if letter in string.punctuation: - badName = True - if badName: - # some filesystems don't like bizarre characters - self.parent.showMessage( - msg='Preset names must contain only letters,' - 'numbers, and spaces.') + if core.Core.badName(newName): + self.warnMessage() continue if newName: if index != -1: @@ -116,21 +118,30 @@ class PresetManager(QtGui.QDialog): self.selectedComponents[index].savePreset() componentName = str(self.selectedComponents[index]).strip() vers = self.selectedComponents[index].version() - self.createPresetFile( + self.createNewPreset( componentName, vers, saveValueStore, newName) self.selectedComponents[index].currentPreset = newName + self.findPresets() + self.drawPresetList() break - def createPresetFile(self, compName, vers, saveValueStore, filename): + def createNewPreset(self, compName, vers, saveValueStore, filename): path = os.path.join(self.presetDir, compName, str(vers), filename) + if self.presetExists(path): + return + self.core.createPresetFile(compName, vers, saveValueStore, filename) + + def presetExists(self, path): if os.path.exists(path): ch = self.parent.showMessage( - msg="%s already exists! Overwrite it?" % filename, + msg="%s already exists! Overwrite it?" % + os.path.basename(path), showCancel=True, icon=QtGui.QMessageBox.Warning) if not ch: - return - self.core.createPresetFile(compName, vers, saveValueStore, filename) - self.drawPresetList() + # user clicked cancel + return True + + return False def openPreset(self, presetName): componentList = self.parent.window.listWidget_componentList @@ -156,3 +167,76 @@ class PresetManager(QtGui.QDialog): self.parent.updateComponentTitle(index) self.parent.drawPreview() + def openDeletePresetDialog(self): + selected = self.window.listWidget_presets.selectedItems() + if not selected: + return + row = self.window.listWidget_presets.row(selected[0]) + comp, vers, name = self.presetRows[row] + ch = self.parent.showMessage( + msg='Really delete %s?' % name, + showCancel=True, icon=QtGui.QMessageBox.Warning + ) + if not ch: + return + self.deletePreset(comp, vers, name) + self.findPresets() + self.drawPresetList() + + def deletePreset(self, comp, vers, name): + filepath = os.path.join(self.presetDir, comp, str(vers), name) + os.remove(filepath) + + def warnMessage(self): + self.parent.showMessage( + msg='Preset names must contain only letters, ' + 'numbers, and spaces.') + + def openRenamePresetDialog(self): + presetList = self.window.listWidget_presets + if presetList.currentRow() == -1: + return + + while True: + index = presetList.currentRow() + newName, OK = QtGui.QInputDialog.getText( + self.window, + 'Preset Manager', + 'Rename Preset:', + QtGui.QLineEdit.Normal, + self.presetRows[index][2] + ) + if OK: + if core.Core.badName(newName): + self.warnMessage() + continue + if newName: + comp, vers, oldName = self.presetRows[index] + path = os.path.join( + self.presetDir, comp, str(vers)) + newPath = os.path.join(path, newName) + oldPath = os.path.join(path, oldName) + if self.presetExists(newPath): + return + if os.path.exists(newPath): + os.remove(newPath) + os.rename(oldPath, newPath) + self.findPresets() + self.drawPresetList() + break + + + + + + + + + + + + + + + + From c51d86dd74c0548a0e81725534b78e23f6b6acaa Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 8 Jun 2017 22:31:02 -0400 Subject: [PATCH 08/22] preset searchbar works, ui experimentally changed closebutton where I keep expecting it to be --- presetmanager.py | 53 ++++++++++++++++++++---------------------------- presetmanager.ui | 26 +++++++++++++++++++++++- video_thread.py | 2 +- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/presetmanager.py b/presetmanager.py index d7189b1..ff50444 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -8,7 +8,7 @@ import core class PresetManager(QtGui.QDialog): def __init__(self, window, parent): - super().__init__() + super().__init__(parent.window) self.parent = parent self.core = self.parent.core self.presetDir = self.core.presetDir @@ -20,27 +20,30 @@ class PresetManager(QtGui.QDialog): # connect button signals self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog) self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog) + self.window.pushButton_close.clicked.connect(self.close) # create filter box and preset list self.drawFilterList() self.window.comboBox_filter.currentIndexChanged.connect( - lambda: self.drawPresetList(self.window.comboBox_filter.currentText()) + lambda: self.drawPresetList( + self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text() + ) ) - self.drawPresetList('*') # make auto-completion for search bar self.autocomplete = QtGui.QStringListModel() completer = QtGui.QCompleter() completer.setModel(self.autocomplete) self.window.lineEdit_search.setCompleter(completer) + self.window.lineEdit_search.textChanged.connect( + lambda: self.drawPresetList( + self.window.comboBox_filter.currentText(), self.window.lineEdit_search.text() + ) + ) + self.drawPresetList('*') def show(self): '''Open a new preset manager window from the mainwindow''' - presetNames = [] - for presetList in self.presets.values(): - for preset in presetList: - presetNames.append(preset[1]) - self.autocomplete.setStringList(presetNames) self.findPresets() self.drawFilterList() self.drawPresetList('*') @@ -70,19 +73,23 @@ class PresetManager(QtGui.QDialog): for compName, _, __ in parseList \ } - def drawPresetList(self, filter=None): + def drawPresetList(self, compFilter=None, presetFilter=''): self.window.listWidget_presets.clear() - if filter: - self.lastFilter = str(filter) + if compFilter: + self.lastFilter = str(compFilter) else: - filter = str(self.lastFilter) + compFilter = str(self.lastFilter) self.presetRows = [] + presetNames = [] for component, presets in self.presets.items(): - if filter != '*' and component != filter: + if compFilter != '*' and component != compFilter: continue for vers, preset in presets: - self.window.listWidget_presets.addItem('%s: %s' % (component, preset)) - self.presetRows.append((component, vers, preset)) + if not presetFilter or presetFilter in preset: + self.window.listWidget_presets.addItem('%s: %s' % (component, preset)) + self.presetRows.append((component, vers, preset)) + presetNames.append(preset) + self.autocomplete.setStringList(presetNames) def drawFilterList(self): self.window.comboBox_filter.clear() @@ -224,19 +231,3 @@ class PresetManager(QtGui.QDialog): self.findPresets() self.drawPresetList() break - - - - - - - - - - - - - - - - diff --git a/presetmanager.ui b/presetmanager.ui index 610de91..a7ef15f 100644 --- a/presetmanager.ui +++ b/presetmanager.ui @@ -2,6 +2,9 @@ presetmanager + + Qt::ApplicationModal + 0 @@ -22,7 +25,7 @@ - Search + Filter by name @@ -48,6 +51,9 @@ 0 + + true + @@ -98,6 +104,24 @@ + + + + Close + + + + + + + + + + + <html><head/><body><p><span style=" font-style:italic;">Right-click components in the list to create presets.</span></p></body></html> + + + diff --git a/video_thread.py b/video_thread.py index f5354be..fc877bd 100644 --- a/video_thread.py +++ b/video_thread.py @@ -26,7 +26,7 @@ class Worker(QtCore.QObject): QtCore.QObject.__init__(self) self.core = core.Core() self.core.settings = parent.settings - self.modules = parent.modules + self.modules = parent.core.modules self.stackedWidget = parent.window.stackedWidget self.parent = parent parent.videoTask.connect(self.createVideo) From d3f979ef2461a3de701df0d5add545b80dfe23ad Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 8 Jun 2017 22:56:33 -0400 Subject: [PATCH 09/22] start connecting import/export buttons --- core.py | 6 ++++++ mainwindow.py | 8 ++++---- presetmanager.py | 23 ++++++++++++++++++++++- presetmanager.ui | 29 +++++++++++++++++++++-------- 4 files changed, 53 insertions(+), 13 deletions(-) diff --git a/core.py b/core.py index 06367cf..776af55 100644 --- a/core.py +++ b/core.py @@ -85,6 +85,12 @@ class Core(): with open(filepath, 'w') as f: f.write(Core.stringOrderedDict(saveValueStore)) + def importPreset(self, filepath): + print(filepath) + + def exportPreset(self, exportName, compName, vers, origName): + pass + def loadEncoderOptions(self): file_path = os.path.join(self.wd, 'encoder-options.json') with open(file_path) as json_file: diff --git a/mainwindow.py b/mainwindow.py index 8812e7f..45262f7 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -60,15 +60,15 @@ class MainWindow(QtCore.QObject): # Create data directory, load/create settings self.dataDir = self.core.dataDir + self.autosavePath = os.path.join(self.dataDir, 'autosave.avp') + self.settings = QSettings( + os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat) + LoadDefaultSettings(self) self.presetManager = PresetManager( uic.loadUi( os.path.join(os.path.dirname(os.path.realpath(__file__)), 'presetmanager.ui')), self) - self.autosavePath = os.path.join(self.dataDir, 'autosave.avp') - self.settings = QSettings( - os.path.join(self.dataDir, 'settings.ini'), QSettings.IniFormat) - LoadDefaultSettings(self) if not os.path.exists(self.dataDir): os.makedirs(self.dataDir) for neededDirectory in ( diff --git a/presetmanager.py b/presetmanager.py index ff50444..73a2431 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -10,7 +10,8 @@ class PresetManager(QtGui.QDialog): def __init__(self, window, parent): super().__init__(parent.window) self.parent = parent - self.core = self.parent.core + self.core = parent.core + self.settings = parent.settings self.presetDir = self.core.presetDir self.window = window self.findPresets() @@ -21,6 +22,8 @@ class PresetManager(QtGui.QDialog): self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog) self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog) self.window.pushButton_close.clicked.connect(self.close) + self.window.pushButton_import.clicked.connect(self.openImportDialog) + self.window.pushButton_export.clicked.connect(self.openExportDialog) # create filter box and preset list self.drawFilterList() @@ -231,3 +234,21 @@ class PresetManager(QtGui.QDialog): self.findPresets() self.drawPresetList() break + + def openImportDialog(self): + filename = QtGui.QFileDialog.getOpenFileName( + self.window, "Import Preset File", + self.settings.value("projectDir"), + "Preset Files (*.avl)") + if filename: + self.core.importPreset(filename) + + def openExportDialog(self): + filename = QtGui.QFileDialog.getSaveFileName( + self.window, "Export Preset", + self.settings.value("projectDir"), + "Preset Files (*.avl)") + if filename: + index = self.window.listWidget_presets.currentRow() + comp, vers, name = self.presetRows[index] + self.core.exportPreset(filename, comp, vers, name) diff --git a/presetmanager.ui b/presetmanager.ui index a7ef15f..47568fb 100644 --- a/presetmanager.ui +++ b/presetmanager.ui @@ -9,7 +9,7 @@ 0 0 - 542 + 475 360 @@ -104,13 +104,6 @@ - - - - Close - - - @@ -122,6 +115,26 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + From b048312882f80d43e3d8b1573814badf70fe3b92 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sat, 10 Jun 2017 12:10:05 -0400 Subject: [PATCH 10/22] close button works, dialogs properly parented hint text wording changed by IamDH4's suggestion --- mainwindow.py | 4 +++- presetmanager.py | 18 ++++++++++++------ presetmanager.ui | 6 +++--- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/mainwindow.py b/mainwindow.py index 45262f7..3e49ab2 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -601,7 +601,9 @@ class MainWindow(QtCore.QObject): print('project file missing value: %s' % e) def showMessage(self, **kwargs): - msg = QtGui.QMessageBox() + parent = kwargs['parent'] if 'parent' in kwargs else self.window + msg = QtGui.QMessageBox(parent) + msg.setModal(True) msg.setText(kwargs['msg']) msg.setIcon( kwargs['icon'] if 'icon' in kwargs else QtGui.QMessageBox.Information) diff --git a/presetmanager.py b/presetmanager.py index 73a2431..04a9042 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -21,9 +21,9 @@ class PresetManager(QtGui.QDialog): # connect button signals self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog) self.window.pushButton_rename.clicked.connect(self.openRenamePresetDialog) - self.window.pushButton_close.clicked.connect(self.close) self.window.pushButton_import.clicked.connect(self.openImportDialog) self.window.pushButton_export.clicked.connect(self.openExportDialog) + self.window.pushButton_close.clicked.connect(self.window.close) # create filter box and preset list self.drawFilterList() @@ -91,7 +91,8 @@ class PresetManager(QtGui.QDialog): if not presetFilter or presetFilter in preset: self.window.listWidget_presets.addItem('%s: %s' % (component, preset)) self.presetRows.append((component, vers, preset)) - presetNames.append(preset) + if preset not in presetNames: + presetNames.append(preset) self.autocomplete.setStringList(presetNames) def drawFilterList(self): @@ -120,7 +121,7 @@ class PresetManager(QtGui.QDialog): ) if OK: if core.Core.badName(newName): - self.warnMessage() + self.warnMessage(self.parent.window) continue if newName: if index != -1: @@ -185,7 +186,9 @@ class PresetManager(QtGui.QDialog): comp, vers, name = self.presetRows[row] ch = self.parent.showMessage( msg='Really delete %s?' % name, - showCancel=True, icon=QtGui.QMessageBox.Warning + showCancel=True, + icon=QtGui.QMessageBox.Warning, + parent=self.window ) if not ch: return @@ -197,10 +200,11 @@ class PresetManager(QtGui.QDialog): filepath = os.path.join(self.presetDir, comp, str(vers), name) os.remove(filepath) - def warnMessage(self): + def warnMessage(self, window=None): self.parent.showMessage( msg='Preset names must contain only letters, ' - 'numbers, and spaces.') + 'numbers, and spaces.', + parent=window if window else self.window) def openRenamePresetDialog(self): presetList = self.window.listWidget_presets @@ -242,6 +246,8 @@ class PresetManager(QtGui.QDialog): "Preset Files (*.avl)") if filename: self.core.importPreset(filename) + self.findPresets() + self.drawPresetList() def openExportDialog(self): filename = QtGui.QFileDialog.getSaveFileName( diff --git a/presetmanager.ui b/presetmanager.ui index 47568fb..b3c25fe 100644 --- a/presetmanager.ui +++ b/presetmanager.ui @@ -9,8 +9,8 @@ 0 0 - 475 - 360 + 497 + 377 @@ -111,7 +111,7 @@ - <html><head/><body><p><span style=" font-style:italic;">Right-click components in the list to create presets.</span></p></body></html> + <html><head/><body><p><span style=" font-size:10pt; font-style:italic;">Right-click components in the main window to create presets</span></p></body></html> From 59c2c090ab9275bc1146329536d43855a46d34f4 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sat, 10 Jun 2017 14:52:01 -0400 Subject: [PATCH 11/22] made basic export function, moved more code into core --- core.py | 70 +++++++++++++++++++++++++++++++++++++++++------- mainwindow.py | 26 +++++------------- presetmanager.py | 12 +++++---- presetmanager.ui | 8 +++++- 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/core.py b/core.py index 776af55..c50918f 100644 --- a/core.py +++ b/core.py @@ -77,19 +77,69 @@ class Core(): def moduleIndexFor(self, compIndex): return self.selectedModules[compIndex] - def createPresetFile(self, compName, vers, saveValueStore, filename): - dirname = os.path.join(self.presetDir, compName, str(vers)) - if not os.path.exists(dirname): - os.makedirs(dirname) - filepath = os.path.join(dirname, filename) - with open(filepath, 'w') as f: - f.write(Core.stringOrderedDict(saveValueStore)) - def importPreset(self, filepath): print(filepath) - def exportPreset(self, exportName, compName, vers, origName): - pass + def exportPreset(self, exportPath, compName, vers, origName): + internalPath = os.path.join(self.presetDir, compName, str(vers), origName) + if not os.path.exists(internalPath): + return + if os.path.exists(exportPath): + os.remove(exportPath) + with open(internalPath, 'r') as f: + internalData = [line for line in f] + try: + saveValueStore = dict(eval(internalData[0].strip())) + self.createPresetFile( + compName, vers, + origName, saveValueStore, + exportPath + ) + except: + # TODO: add proper warning message + print('couldn\'t export %s' % exportPath) + + def createPresetFile( + self, compName, vers, presetName, saveValueStore, filepath=''): + '''Create a preset file (.avl) at filepath using args. + Or if filepath is empty, create an internal preset using + the args for the filepath.''' + if not filepath: + dirname = os.path.join(self.presetDir, compName, str(vers)) + if not os.path.exists(dirname): + os.makedirs(dirname) + filepath = os.path.join(dirname, presetName) + internal = True + else: + if not filepath.endswith('.avl'): + filepath += '.avl' + internal = False + + with open(filepath, 'w') as f: + if not internal: + f.write('[Components]\n') + f.write('%s\n' % compName) + f.write('%s\n' % str(vers)) + f.write(Core.stringOrderedDict(saveValueStore)) + + def createProjectFile(self, filepath): + '''Create a project file (.avp) using the current program state''' + try: + if not filepath.endswith(".avp"): + filepath += '.avp' + if os.path.exists(filepath): + os.remove(filepath) + with open(filepath, 'w') as f: + print('creating %s' % filepath) + f.write('[Components]\n') + for comp in self.selectedComponents: + saveValueStore = comp.savePreset() + f.write('%s\n' % str(comp)) + f.write('%s\n' % str(comp.version())) + f.write('%s\n' % Core.stringOrderedDict(saveValueStore)) + return True + except: + return False def loadEncoderOptions(self): file_path = os.path.join(self.wd, 'encoder-options.json') diff --git a/mainwindow.py b/mainwindow.py index 3e49ab2..5c929c3 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -284,7 +284,7 @@ class MainWindow(QtCore.QObject): if os.path.exists(self.autosavePath): os.remove(self.autosavePath) elif time.time() - self.lastAutosave >= 2.0: - self.createProjectFile(self.autosavePath) + self.core.createProjectFile(self.autosavePath) self.lastAutosave = time.time() def autosaveExists(self): @@ -504,7 +504,7 @@ class MainWindow(QtCore.QObject): def saveCurrentProject(self): if self.currentProject: - self.createProjectFile(self.currentProject) + self.core.createProjectFile(self.currentProject) else: self.openSaveProjectDialog() @@ -515,25 +515,11 @@ class MainWindow(QtCore.QObject): "Project Files (*.avp)") if not filename: return - self.createProjectFile(filename) + self.settings.setValue("projectDir", os.path.dirname(filename)) + self.settings.setValue("currentProject", filename) + self.currentProject = filename - def createProjectFile(self, filepath): - if not filepath.endswith(".avp"): - filepath += '.avp' - if os.path.exists(filepath): - os.remove(filepath) - with open(filepath, 'w') as f: - print('creating %s' % filepath) - f.write('[Components]\n') - for comp in self.core.selectedComponents: - saveValueStore = comp.savePreset() - f.write('%s\n' % str(comp)) - f.write('%s\n' % str(comp.version())) - f.write('%s\n' % core.Core.stringOrderedDict(saveValueStore)) - if filepath != self.autosavePath: - self.settings.setValue("projectDir", os.path.dirname(filepath)) - self.settings.setValue("currentProject", filepath) - self.currentProject = filepath + self.core.createProjectFile(filename) def openOpenProjectDialog(self): filename = QtGui.QFileDialog.getOpenFileName( diff --git a/presetmanager.py b/presetmanager.py index 04a9042..7e4efbb 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -1,4 +1,4 @@ -from PyQt4 import QtGui +from PyQt4 import QtGui, QtCore from collections import OrderedDict import string import os @@ -13,10 +13,12 @@ class PresetManager(QtGui.QDialog): self.core = parent.core self.settings = parent.settings self.presetDir = self.core.presetDir - self.window = window self.findPresets() + self.lastFilter = '*' self.presetRows = [] # list of (comp, vers, name) tuples + self.window = window + self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) # connect button signals self.window.pushButton_delete.clicked.connect(self.openDeletePresetDialog) @@ -130,17 +132,17 @@ class PresetManager(QtGui.QDialog): componentName = str(self.selectedComponents[index]).strip() vers = self.selectedComponents[index].version() self.createNewPreset( - componentName, vers, saveValueStore, newName) + componentName, vers, newName, saveValueStore) self.selectedComponents[index].currentPreset = newName self.findPresets() self.drawPresetList() break - def createNewPreset(self, compName, vers, saveValueStore, filename): + def createNewPreset(self, compName, vers, filename, saveValueStore): path = os.path.join(self.presetDir, compName, str(vers), filename) if self.presetExists(path): return - self.core.createPresetFile(compName, vers, saveValueStore, filename) + self.core.createPresetFile(compName, vers, filename, saveValueStore) def presetExists(self, path): if os.path.exists(path): diff --git a/presetmanager.ui b/presetmanager.ui index b3c25fe..5257b1c 100644 --- a/presetmanager.ui +++ b/presetmanager.ui @@ -3,7 +3,10 @@ presetmanager - Qt::ApplicationModal + Qt::NonModal + + + true @@ -92,6 +95,9 @@ + + true + Rename From be5d47f8634d29d58b9811657ede815814ffde18 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 11 Jun 2017 12:52:29 -0400 Subject: [PATCH 12/22] can't right-click empty space + color eyedropper --- components/__base__.py | 4 +++- components/text.py | 2 +- core.py | 4 ++-- mainwindow.py | 27 +++++++++++++++------------ presetmanager.py | 6 +++++- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/components/__base__.py b/components/__base__.py index b95edf4..3ccb5dc 100644 --- a/components/__base__.py +++ b/components/__base__.py @@ -24,7 +24,9 @@ class Component: exec('self.%s = value' % var) def pickColor(self): - color = QtGui.QColorDialog.getColor() + dialog = QtGui.QColorDialog() + dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) + color = dialog.getColor() if color.isValid(): RGBstring = '%s,%s,%s' % ( str(color.red()), str(color.green()), str(color.blue())) diff --git a/components/text.py b/components/text.py index 68cffca..e8fb3d5 100644 --- a/components/text.py +++ b/components/text.py @@ -31,7 +31,7 @@ class Component(__base__.Component): page.comboBox_textAlign.addItem("Right") page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor) - page.pushButton_textColor.clicked.connect(lambda: self.pickColor()) + page.pushButton_textColor.clicked.connect(self.pickColor) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*self.textColor).name() page.pushButton_textColor.setStyleSheet(btnStyle) diff --git a/core.py b/core.py index c50918f..e69de50 100644 --- a/core.py +++ b/core.py @@ -95,9 +95,9 @@ class Core(): origName, saveValueStore, exportPath ) + return True except: - # TODO: add proper warning message - print('couldn\'t export %s' % exportPath) + return False def createPresetFile( self, compName, vers, presetName, saveValueStore, filepath=''): diff --git a/mainwindow.py b/mainwindow.py index 5c929c3..2f04559 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -606,20 +606,25 @@ class MainWindow(QtCore.QObject): def componentContextMenu(self, QPos): '''Appears when right-clicking a component in the list''' - if not self.window.listWidget_componentList.selectedItems(): + 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 = QtGui.QMenu() menuItem = self.menu.addAction("Save Preset") - self.connect( - menuItem, - QtCore.SIGNAL("triggered()"), + menuItem.triggered.connect( self.presetManager.openSavePresetDialog ) # submenu for opening presets - index = self.window.listWidget_componentList.currentRow() try: presets = self.presetManager.presets[str(self.core.selectedComponents[index])] self.submenu = QtGui.QMenu("Open Preset") @@ -627,14 +632,12 @@ class MainWindow(QtCore.QObject): for version, presetName in presets: menuItem = self.submenu.addAction(presetName) - self.connect( - menuItem, - QtCore.SIGNAL("triggered()"), - lambda presetName=presetName: + menuItem.triggered.connect( + lambda _, presetName=presetName: self.presetManager.openPreset(presetName) ) - except KeyError as e: - print(e) - parentPosition = self.window.listWidget_componentList.mapToGlobal(QtCore.QPoint(0, 0)) + except KeyError: + pass + self.menu.move(parentPosition + QPos) self.menu.show() diff --git a/presetmanager.py b/presetmanager.py index 7e4efbb..3036f7a 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -259,4 +259,8 @@ class PresetManager(QtGui.QDialog): if filename: index = self.window.listWidget_presets.currentRow() comp, vers, name = self.presetRows[index] - self.core.exportPreset(filename, comp, vers, name) + if not self.core.exportPreset(filename, comp, vers, name): + self.parent.showMessage( + msg='Couldn\'t export %s.' % filename, + parent=self.window + ) From 28f07272cc174efe8e219abed683d22f72f18d94 Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 11 Jun 2017 17:03:40 -0400 Subject: [PATCH 13/22] using tab in component list updates widget and more understandable function names for writing/reading presets --- components/video.py | 11 +++++++++-- core.py | 14 +++++++++----- main.py | 1 - mainwindow.py | 7 +++---- presetmanager.py | 3 +-- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/components/video.py b/components/video.py index c529658..bd1bf96 100644 --- a/components/video.py +++ b/components/video.py @@ -10,8 +10,15 @@ from . import __base__ class Video: '''Video Component Frame-Fetcher''' def __init__(self, **kwargs): - mandatoryArgs = ['ffmpeg', 'videoPath', 'width', 'height', - 'frameRate', 'chunkSize', 'parent'] + mandatoryArgs = [ + 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN + 'videoPath', + 'width', + 'height', + 'frameRate', # frames per second + 'chunkSize', # number of bytes in one frame + 'parent' + ] for arg in mandatoryArgs: try: exec('self.%s = kwargs[arg]' % arg) diff --git a/core.py b/core.py index e69de50..4f30973 100644 --- a/core.py +++ b/core.py @@ -71,7 +71,7 @@ class Core(): return endI def updateComponent(self, i): - print('updating %s' % self.selectedComponents[i]) + # print('updating %s' % self.selectedComponents[i]) self.selectedComponents[i].update() def moduleIndexFor(self, compIndex): @@ -89,7 +89,7 @@ class Core(): with open(internalPath, 'r') as f: internalData = [line for line in f] try: - saveValueStore = dict(eval(internalData[0].strip())) + saveValueStore = Core.presetFromString(internalData[0].strip()) self.createPresetFile( compName, vers, origName, saveValueStore, @@ -120,7 +120,7 @@ class Core(): f.write('[Components]\n') f.write('%s\n' % compName) f.write('%s\n' % str(vers)) - f.write(Core.stringOrderedDict(saveValueStore)) + f.write(Core.presetToString(saveValueStore)) def createProjectFile(self, filepath): '''Create a project file (.avp) using the current program state''' @@ -136,7 +136,7 @@ class Core(): saveValueStore = comp.savePreset() f.write('%s\n' % str(comp)) f.write('%s\n' % str(comp.version())) - f.write('%s\n' % Core.stringOrderedDict(saveValueStore)) + f.write('%s\n' % Core.presetToString(saveValueStore)) return True except: return False @@ -244,6 +244,10 @@ class Core(): return badName @staticmethod - def stringOrderedDict(dictionary): + def presetToString(dictionary): sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) return repr(sorted_) + + @staticmethod + def presetFromString(string): + return dict(eval(string)) diff --git a/main.py b/main.py index c771aca..7c0727b 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,4 @@ from importlib import import_module -from collections import OrderedDict from PyQt4 import QtGui, uic from PyQt4.QtCore import Qt import sys diff --git a/mainwindow.py b/mainwindow.py index 2f04559..dbbc631 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -1,6 +1,5 @@ from os.path import expanduser from queue import Queue -from collections import OrderedDict from PyQt4 import QtCore, QtGui, uic from PyQt4.QtCore import QSettings, Qt from PyQt4.QtGui import QMenu @@ -159,8 +158,8 @@ class MainWindow(QtCore.QObject): self.window.pushButton_addComponent.setMenu(self.compMenu) componentList.dropEvent = self.componentListChanged - componentList.clicked.connect( - lambda _: self.changeComponentWidget()) + componentList.itemSelectionChanged.connect( + self.changeComponentWidget) self.window.pushButton_removeComponent.clicked.connect( lambda _: self.removeComponent()) @@ -567,7 +566,7 @@ class MainWindow(QtCore.QObject): # version, not used yet i += 1 elif i == 2: - saveValueStore = dict(eval(line)) + saveValueStore = core.Core.presetFromString(line) self.core.selectedComponents[-1].loadPreset( saveValueStore) self.updateComponentTitle(-1) diff --git a/presetmanager.py b/presetmanager.py index 3036f7a..6708d11 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -1,5 +1,4 @@ from PyQt4 import QtGui, QtCore -from collections import OrderedDict import string import os @@ -171,7 +170,7 @@ class PresetManager(QtGui.QDialog): return with open(filepath, 'r') as f: for line in f: - saveValueStore = dict(eval(line.strip())) + saveValueStore = core.Core.presetFromString(line.strip()) break selectedComponents[index].loadPreset( saveValueStore, From dbbefbf70ec9fad1735ee7199992ca6123bd54be Mon Sep 17 00:00:00 2001 From: tassaron Date: Sun, 11 Jun 2017 23:29:13 -0400 Subject: [PATCH 14/22] split up openProject code for use in importPreset --- core.py | 121 +++++++++++++++++++++++++++++++++++++++-------- mainwindow.py | 58 ++--------------------- presetmanager.py | 18 ++++++- 3 files changed, 123 insertions(+), 74 deletions(-) diff --git a/core.py b/core.py index 4f30973..c47068c 100644 --- a/core.py +++ b/core.py @@ -32,9 +32,8 @@ class Core(): self.wd = os.path.dirname(os.path.realpath(__file__)) self.loadEncoderOptions() - self.modules = self.findComponents() + self.findComponents() self.selectedComponents = [] - self.selectedModules = [] def findComponents(self): def findComponents(): @@ -46,9 +45,10 @@ class Core(): continue elif ext == '.py': yield name - return [ + self.modules = [ import_module('components.%s' % name) for name in findComponents()] + self.moduleIndexes = [i for i in range(len(self.modules))] def insertComponent(self, compPos, moduleIndex): if compPos < 0: @@ -57,28 +57,114 @@ class Core(): compPos, self.modules[moduleIndex].Component() ) - self.selectedModules.insert( - compPos, - moduleIndex - ) return compPos def moveComponent(self, startI, endI): comp = self.selectedComponents.pop(startI) self.selectedComponents.insert(endI, comp) - i = self.selectedModules.pop(startI) - self.selectedModules.insert(endI, i) return endI def updateComponent(self, i): # print('updating %s' % self.selectedComponents[i]) self.selectedComponents[i].update() - def moduleIndexFor(self, compIndex): - return self.selectedModules[compIndex] + def moduleIndexFor(self, compName): + compNames = [mod.Component.__doc__ for mod in self.modules] + index = compNames.index(compName) + return self.moduleIndexes[index] + + def openProject(self, loader, filepath): + '''loader is the object calling this method (mainwindow/command) + which implements an insertComponent method''' + errcode, data = self.parseAvFile(filepath) + if errcode == 0: + for name, vers, preset in data['Components']: + loader.insertComponent( + self.moduleIndexFor(name), -1) + self.selectedComponents[-1].loadPreset( + preset) + elif errcode == 1: + typ, value, _ = data + if typ.__name__ == KeyError: + # probably just an old version, still loadable + print('file missing value: %s' % value) + return + loader.createNewProject() + msg = '%s: %s' % (typ.__name__, value) + loader.showMessage( + msg="Project file '%s' is corrupted." % filepath, + showCancel=False, + icon=QtGui.QMessageBox.Warning, + detail=msg) + + def parseAvFile(self, filepath): + '''Parses an avp (project) or avl (preset package) file. + Returns data usable by another method.''' + data = {} + try: + with open(filepath, 'r') as f: + def parseLine(line): + '''Decides if a given avp or avl line is a section header''' + validSections = ('Components') + line = line.strip() + newSection = '' + + if line.startswith('[') and line.endswith(']') \ + and line[1:-1] in validSections: + newSection = line[1:-1] + + return line, newSection + + section = '' + i = 0 + for line in f: + line, newSection = parseLine(line) + if newSection: + section = str(newSection) + data[section] = [] + continue + if line and section == 'Components': + if i == 0: + lastCompName = str(line) + i += 1 + elif i == 1: + lastCompVers = str(line) + i += 1 + elif i == 2: + lastCompPreset = Core.presetFromString(line) + data[section].append( + (lastCompName, + lastCompVers, + lastCompPreset) + ) + i = 0 + return 0, data + except: + return 1, sys.exc_info() def importPreset(self, filepath): - print(filepath) + errcode, data = self.parseAvFile(filepath) + returnList = [] + if errcode == 0: + name, vers, preset = data['Components'][0] + presetName = preset['preset'] \ + if preset['preset'] else os.path.basename(filepath)[:-4] + newPath = os.path.join( + self.presetDir, + name, + vers, + presetName + ) + if os.path.exists(newPath): + return False, newPath + preset['preset'] = presetName + self.createPresetFile( + name, vers, presetName, preset + ) + return True, presetName + elif errcode == 1: + # TODO: an error message + return False, '' def exportPreset(self, exportPath, compName, vers, origName): internalPath = os.path.join(self.presetDir, compName, str(vers), origName) @@ -237,17 +323,14 @@ class Core(): @staticmethod def badName(name): '''Returns whether a name contains non-alphanumeric chars''' - badName = False - for letter in name: - if letter in string.punctuation: - badName = True - return badName + return any([letter in string.punctuation for letter in name]) @staticmethod def presetToString(dictionary): - sorted_ = OrderedDict(sorted(dictionary.items(), key=lambda t: t[0])) - return repr(sorted_) + '''Alphabetizes a dict into OrderedDict & returns string repr''' + return repr(OrderedDict(sorted(dictionary.items(), key=lambda t: t[0]))) @staticmethod def presetFromString(string): + '''Turns a string repr of OrderedDict into a regular dict''' return dict(eval(string)) diff --git a/mainwindow.py b/mainwindow.py index dbbc631..42105d1 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -54,7 +54,6 @@ class MainWindow(QtCore.QObject): self.core = core.Core() self.pages = [] # widgets of component settings - self.componentRows = [] # (moduleIndex, QListWidgetItem) tuples self.lastAutosave = time.time() # Create data directory, load/create settings @@ -419,7 +418,6 @@ class MainWindow(QtCore.QObject): row = componentList.insertItem( index, self.core.selectedComponents[index].__doc__) - self.componentRows.insert(compPos, (moduleIndex, row)) componentList.setCurrentRow(index) self.pages.insert(index, self.core.selectedComponents[index].widget(self)) @@ -435,7 +433,6 @@ class MainWindow(QtCore.QObject): index = componentList.row(selected) self.window.stackedWidget.removeWidget(self.pages[index]) componentList.takeItem(index) - self.componentRows.pop(index) self.core.selectedComponents.pop(index) self.pages.pop(index) self.changeComponentWidget() @@ -460,8 +457,6 @@ class MainWindow(QtCore.QObject): stackedWidget.insertWidget(newRow, page) componentList.setCurrentRow(newRow) stackedWidget.setCurrentIndex(newRow) - self.componentRows.pop(row) - self.componentRows.insert(newRow, (self.core.moduleIndexFor(row), newItem)) self.drawPreview() def componentListChanged(self, *args): @@ -535,55 +530,12 @@ class MainWindow(QtCore.QObject): self.currentProject = filepath self.settings.setValue("currentProject", filepath) self.settings.setValue("projectDir", os.path.dirname(filepath)) - compNames = [mod.Component.__doc__ for mod in self.core.modules] - try: - with open(filepath, 'r') as f: - validSections = ('Components') - section = '' + # actually load the project using core method + self.core.openProject(self, filepath) - def parseLine(line): - line = line.strip() - newSection = '' - - if line.startswith('[') and line.endswith(']') \ - and line[1:-1] in validSections: - newSection = line[1:-1] - - return line, newSection - - i = 0 - for line in f: - line, newSection = parseLine(line) - if newSection: - section = str(newSection) - continue - if line and section == 'Components': - if i == 0: - compIndex = compNames.index(line) - self.insertComponent(compIndex, -1) - i += 1 - elif i == 1: - # version, not used yet - i += 1 - elif i == 2: - saveValueStore = core.Core.presetFromString(line) - self.core.selectedComponents[-1].loadPreset( - saveValueStore) - self.updateComponentTitle(-1) - i = 0 - except (IndexError, ValueError, NameError, SyntaxError, - AttributeError, TypeError) as e: - self.createNewProject() - typ, value, _ = sys.exc_info() - msg = '%s: %s' % (typ.__name__, value) - self.showMessage( - msg="Project file '%s' is corrupted." % filepath, - showCancel=False, - icon=QtGui.QMessageBox.Warning, - detail=msg) - except KeyError as e: - # probably just an old version, still loadable - print('project file missing value: %s' % e) + for i in range(self.window.listWidget_componentList.count()): + # update listwidget titles to indicate loaded presets + self.updateComponentTitle(i) def showMessage(self, **kwargs): parent = kwargs['parent'] if 'parent' in kwargs else self.window diff --git a/presetmanager.py b/presetmanager.py index 6708d11..91dc373 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -148,7 +148,9 @@ class PresetManager(QtGui.QDialog): ch = self.parent.showMessage( msg="%s already exists! Overwrite it?" % os.path.basename(path), - showCancel=True, icon=QtGui.QMessageBox.Warning) + showCancel=True, + icon=QtGui.QMessageBox.Warning, + parent=self.window) if not ch: # user clicked cancel return True @@ -246,11 +248,23 @@ class PresetManager(QtGui.QDialog): self.settings.value("projectDir"), "Preset Files (*.avl)") if filename: - self.core.importPreset(filename) + path = '' + while True: + if path: + if self.presetExists(path): + break + else: + if os.path.exists(path): + os.remove(path) + success, path = self.core.importPreset(filename) + if success: + break self.findPresets() self.drawPresetList() def openExportDialog(self): + if not self.window.listWidget_presets.selectedItems(): + return filename = QtGui.QFileDialog.getSaveFileName( self.window, "Export Preset", self.settings.value("projectDir"), From 307d499f9ae2729c790fe9258d88aca72331cdf6 Mon Sep 17 00:00:00 2001 From: tassaron Date: Mon, 12 Jun 2017 22:34:37 -0400 Subject: [PATCH 15/22] adding an asterisk to modified, unsaved presets flags for unsaved changes saved in project files --- components/__base__.py | 38 ++++++++++++------ components/color.py | 7 +++- components/image.py | 6 ++- components/original.py | 10 +++-- components/text.py | 11 ++++-- components/video.py | 6 ++- core.py | 87 +++++++++++++++++++++++++++++++++++++++--- mainwindow.py | 26 ++++++++----- presetmanager.py | 32 +++++++++------- 9 files changed, 175 insertions(+), 48 deletions(-) diff --git a/components/__base__.py b/components/__base__.py index 3ccb5dc..b32c120 100644 --- a/components/__base__.py +++ b/components/__base__.py @@ -1,9 +1,15 @@ -from PyQt4 import QtGui +from PyQt4 import QtGui, QtCore +class Component(QtCore.QObject): + '''A base class for components to inherit from''' -class Component: - def __init__(self): + # modified = QtCore.pyqtSignal(int, bool) + + def __init__(self, moduleIndex, compPos): + super().__init__() self.currentPreset = None + self.moduleIndex = moduleIndex + self.compPos = compPos def __str__(self): return self.__doc__ @@ -13,12 +19,27 @@ class Component: return 1 def cancel(self): - # make sure your component responds to these variables in frameRender() + # please stop any lengthy process in response to this variable self.canceled = True def reset(self): self.canceled = False + def update(self): + self.modified.emit(self.compPos, True) + # use super().update() then read your widget values here + + def loadPreset(self, presetDict, presetName): + '''Children should take (presetDict, presetName=None) as args''' + + # Use super().loadPreset(presetDict, presetName) + # Then update your widgets using the preset dict + self.currentPreset = presetName \ + if presetName != None else presetDict['preset'] + + def savePreset(self): + return {} + def preFrameRender(self, **kwargs): for var, value in kwargs.items(): exec('self.%s = value' % var) @@ -62,7 +83,7 @@ class Component: return page def update(self): - # read widget values + super().update() self.parent.drawPreview() def previewRender(self, previewWorker): @@ -77,13 +98,6 @@ class Component: image = Image.new("RGBA", (width, height), (0,0,0,0)) return image - def loadPreset(self, presetDict, presetName=None): - self.currentPreset = presetName - # update widgets using a preset dict - - def savePreset(self): - return {} - def cancel(self): self.canceled = True diff --git a/components/color.py b/components/color.py index 11c1b19..b6105c8 100644 --- a/components/color.py +++ b/components/color.py @@ -7,6 +7,9 @@ from . import __base__ class Component(__base__.Component): '''Color''' + + modified = QtCore.pyqtSignal(int, bool) + def widget(self, parent): self.parent = parent page = uic.loadUi(os.path.join( @@ -45,6 +48,7 @@ class Component(__base__.Component): return page def update(self): + super().update() self.color1 = self.RGBFromString(self.page.lineEdit_color1.text()) self.color2 = self.RGBFromString(self.page.lineEdit_color2.text()) self.x = self.page.spinBox_x.value() @@ -70,7 +74,8 @@ class Component(__base__.Component): return Image.new("RGBA", (width, height), (r, g, b, 255)) def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName if presetName else pr['preset'] + super().loadPreset(pr, presetName) + self.page.lineEdit_color1.setText('%s,%s,%s' % pr['color1']) self.page.lineEdit_color2.setText('%s,%s,%s' % pr['color2']) diff --git a/components/image.py b/components/image.py index 441e0e1..ed5f243 100644 --- a/components/image.py +++ b/components/image.py @@ -6,6 +6,9 @@ from . import __base__ class Component(__base__.Component): '''Image''' + + modified = QtCore.pyqtSignal(int, bool) + def widget(self, parent): self.parent = parent self.settings = parent.settings @@ -22,6 +25,7 @@ class Component(__base__.Component): return page def update(self): + super().update() self.imagePath = self.page.lineEdit_image.text() self.parent.drawPreview() @@ -49,7 +53,7 @@ class Component(__base__.Component): return frame def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName if presetName else pr['preset'] + super().loadPreset(pr, presetName) self.page.lineEdit_image.setText(pr['image']) def savePreset(self): diff --git a/components/original.py b/components/original.py index 7873f43..f3f578d 100644 --- a/components/original.py +++ b/components/original.py @@ -1,9 +1,8 @@ import numpy from PIL import Image, ImageDraw -from PyQt4 import uic, QtGui +from PyQt4 import uic, QtGui, QtCore from PyQt4.QtGui import QColor import os -import random from . import __base__ import time from copy import copy @@ -11,6 +10,9 @@ from copy import copy class Component(__base__.Component): '''Original Audio Visualization''' + + modified = QtCore.pyqtSignal(int, bool) + def widget(self, parent): self.parent = parent self.visColor = (255, 255, 255) @@ -33,12 +35,14 @@ class Component(__base__.Component): return page def update(self): + super().update() self.layout = self.page.comboBox_visLayout.currentIndex() self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text()) self.parent.drawPreview() def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName if presetName else pr['preset'] + super().loadPreset(pr, presetName) + self.page.lineEdit_visColor.setText('%s,%s,%s' % pr['visColor']) btnStyle = "QPushButton { background-color : %s; outline: none; }" \ % QColor(*pr['visColor']).name() diff --git a/components/text.py b/components/text.py index e8fb3d5..a40e2a9 100644 --- a/components/text.py +++ b/components/text.py @@ -9,8 +9,11 @@ from . import __base__ class Component(__base__.Component): '''Title Text''' - def __init__(self): - super().__init__() + + modified = QtCore.pyqtSignal(int, bool) + + def __init__(self, *args): + super().__init__(*args) self.titleFont = QFont() def widget(self, parent): @@ -53,6 +56,7 @@ class Component(__base__.Component): return page def update(self): + super().update() self.title = self.page.lineEdit_title.text() self.alignment = self.page.comboBox_textAlign.currentIndex() self.titleFont = self.page.fontComboBox_titleFont.currentFont() @@ -79,7 +83,8 @@ class Component(__base__.Component): return x, self.yPosition def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName if presetName else pr['preset'] + super().loadPreset(pr, presetName) + self.page.lineEdit_title.setText(pr['title']) font = QFont() font.fromString(pr['titleFont']) diff --git a/components/video.py b/components/video.py index bd1bf96..ff06329 100644 --- a/components/video.py +++ b/components/video.py @@ -86,6 +86,9 @@ class Video: class Component(__base__.Component): '''Video''' + + modified = QtCore.pyqtSignal(int, bool) + def widget(self, parent): self.parent = parent self.settings = parent.settings @@ -106,6 +109,7 @@ class Component(__base__.Component): return page def update(self): + super().update() self.videoPath = self.page.lineEdit_video.text() self.loopVideo = self.page.checkBox_loop.isChecked() self.parent.drawPreview() @@ -136,7 +140,7 @@ class Component(__base__.Component): return self.video.frame(frameNo) def loadPreset(self, pr, presetName=None): - self.currentPreset = presetName if presetName else pr['preset'] + super().loadPreset(pr, presetName) self.page.lineEdit_video.setText(pr['video']) self.page.checkBox_loop.setChecked(pr['loop']) diff --git a/core.py b/core.py index c47068c..5617c81 100644 --- a/core.py +++ b/core.py @@ -34,6 +34,7 @@ class Core(): self.findComponents() self.selectedComponents = [] + self.modifiedComponents = {} def findComponents(self): def findComponents(): @@ -50,24 +51,86 @@ class Core(): for name in findComponents()] self.moduleIndexes = [i for i in range(len(self.modules))] + def componentListChanged(self): + for i, component in enumerate(self.selectedComponents): + component.compPos = i + # print('Modified Components: ', self.modifiedComponents) + def insertComponent(self, compPos, moduleIndex): if compPos < 0: compPos = len(self.selectedComponents) -1 + + component = self.modules[moduleIndex].Component( + moduleIndex, compPos) self.selectedComponents.insert( compPos, - self.modules[moduleIndex].Component() - ) + component) + + newDict = {} + for i, val in self.modifiedComponents.items(): + if i >= compPos: + newDict[i+1] = bool(val) + else: + newDict[i] = bool(val) + self.modifiedComponents.clear() + self.modifiedComponents = newDict + + self.componentListChanged() return compPos def moveComponent(self, startI, endI): + def insert(target, comp): + self.selectedComponents.insert(target, comp) + comp = self.selectedComponents.pop(startI) - self.selectedComponents.insert(endI, comp) + insert(endI, comp) + + try: + oldModified = self.modifiedComponents.pop(startI) + if endI in self.modifiedComponents: + self.modifiedComponents[startI] = \ + self.modifiedComponents.pop(endI) + self.modifiedComponents[endI] = oldModified + except KeyError: + pass + + self.componentListChanged() return endI + def removeComponent(self, i): + self.selectedComponents.pop(i) + try: + self.modifiedComponents.pop(i) + except KeyError: + pass + + newDict = {} + for index, val in self.modifiedComponents.items(): + if index >= i: + newDict[index-1] = bool(val) + else: + newDict[index] = bool(val) + self.modifiedComponents.clear() + self.modifiedComponents = newDict + + self.componentListChanged() + def updateComponent(self, i): # print('updating %s' % self.selectedComponents[i]) self.selectedComponents[i].update() + def componentModified(self, i): + '''Triggered by mainwindow.updateComponentTitle() + Tracks temporary state of whether components are modified or not + for retrieval upon loading a project file''' + self.modifiedComponents[i] = True + + def componentUnmodified(self, i): + try: + self.modifiedComponents.pop(i) + except KeyError: + pass + def moduleIndexFor(self, compName): compNames = [mod.Component.__doc__ for mod in self.modules] index = compNames.index(compName) @@ -78,11 +141,17 @@ class Core(): which implements an insertComponent method''' errcode, data = self.parseAvFile(filepath) if errcode == 0: - for name, vers, preset in data['Components']: + for i, tup in enumerate(data['Components']): + name, vers, preset = tup loader.insertComponent( self.moduleIndexFor(name), -1) self.selectedComponents[-1].loadPreset( preset) + if data['Modified'][i] == True: + self.componentModified(i) + loader.updateComponentTitle(i, True) + else: + loader.updateComponentTitle(i) elif errcode == 1: typ, value, _ = data if typ.__name__ == KeyError: @@ -105,7 +174,7 @@ class Core(): with open(filepath, 'r') as f: def parseLine(line): '''Decides if a given avp or avl line is a section header''' - validSections = ('Components') + validSections = ('Components', 'Modified') line = line.strip() newSection = '' @@ -138,6 +207,8 @@ class Core(): lastCompPreset) ) i = 0 + if line and section == 'Modified': + data[section].append(eval(line)) return 0, data except: return 1, sys.exc_info() @@ -223,6 +294,12 @@ class Core(): f.write('%s\n' % str(comp)) f.write('%s\n' % str(comp.version())) f.write('%s\n' % Core.presetToString(saveValueStore)) + f.write('[Modified]\n') + for i in range(len(self.selectedComponents)): + if i in self.modifiedComponents: + f.write('%s\n' % repr(True)) + else: + f.write('%s\n' % repr(False)) return True except: return False diff --git a/mainwindow.py b/mainwindow.py index 42105d1..2a04a4a 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -235,13 +235,21 @@ class MainWindow(QtCore.QObject): self.previewThread.wait() self.autosave() - def updateComponentTitle(self, pos): + @QtCore.pyqtSlot(int, bool) + def updateComponentTitle(self, pos, modified=False): + #print(pos, modified) 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) + if modified: + self.core.componentModified(pos) + else: + self.core.componentUnmodified(pos) def updateCodecs(self): containerWidget = self.window.comboBox_videoContainer @@ -344,9 +352,6 @@ class MainWindow(QtCore.QObject): self.showMessage( msg="You must select an audio file and output filename.") - def progressBarUpdated(self, value): - self.window.progressBar_createVideo.setValue(value) - def changeEncodingStatus(self, status): if status: self.window.pushButton_createVideo.setEnabled(False) @@ -385,6 +390,9 @@ class MainWindow(QtCore.QObject): self.window.pushButton_presets.setEnabled(True) self.window.listWidget_componentList.setEnabled(True) + def progressBarUpdated(self, value): + self.window.progressBar_createVideo.setValue(value) + def progressBarSetText(self, value): self.window.progressBar_createVideo.setFormat(value) @@ -420,6 +428,10 @@ class MainWindow(QtCore.QObject): 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].widget(self)) stackedWidget.insertWidget(index, self.pages[index]) stackedWidget.setCurrentIndex(index) @@ -433,7 +445,7 @@ class MainWindow(QtCore.QObject): index = componentList.row(selected) self.window.stackedWidget.removeWidget(self.pages[index]) componentList.takeItem(index) - self.core.selectedComponents.pop(index) + self.core.removeComponent(index) self.pages.pop(index) self.changeComponentWidget() self.drawPreview() @@ -533,10 +545,6 @@ class MainWindow(QtCore.QObject): # actually load the project using core method self.core.openProject(self, filepath) - for i in range(self.window.listWidget_componentList.count()): - # update listwidget titles to indicate loaded presets - self.updateComponentTitle(i) - def showMessage(self, **kwargs): parent = kwargs['parent'] if 'parent' in kwargs else self.window msg = QtGui.QMessageBox(parent) diff --git a/presetmanager.py b/presetmanager.py index 91dc373..4300ce1 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -105,14 +105,14 @@ class PresetManager(QtGui.QDialog): def openSavePresetDialog(self): '''Functions on mainwindow level from the context menu''' window = self.parent.window - self.selectedComponents = self.parent.core.selectedComponents + selectedComponents = self.parent.core.selectedComponents componentList = self.parent.window.listWidget_componentList if componentList.currentRow() == -1: return while True: index = componentList.currentRow() - currentPreset = self.selectedComponents[index].currentPreset + currentPreset = selectedComponents[index].currentPreset newName, OK = QtGui.QInputDialog.getText( self.parent.window, 'Audio Visualizer', @@ -127,30 +127,35 @@ class PresetManager(QtGui.QDialog): if newName: if index != -1: saveValueStore = \ - self.selectedComponents[index].savePreset() - componentName = str(self.selectedComponents[index]).strip() - vers = self.selectedComponents[index].version() + selectedComponents[index].savePreset() + componentName = str(selectedComponents[index]).strip() + vers = selectedComponents[index].version() self.createNewPreset( - componentName, vers, newName, saveValueStore) - self.selectedComponents[index].currentPreset = newName - self.findPresets() - self.drawPresetList() + componentName, vers, newName, + saveValueStore, window=self.parent.window) + selectedComponents[index].currentPreset = newName + #self.findPresets() + #self.drawPresetList() + self.parent.updateComponentTitle(index) break - def createNewPreset(self, compName, vers, filename, saveValueStore): + def createNewPreset( + self, compName, vers, filename, saveValueStore, **kwargs): path = os.path.join(self.presetDir, compName, str(vers), filename) - if self.presetExists(path): + if self.presetExists(path, **kwargs): return self.core.createPresetFile(compName, vers, filename, saveValueStore) - def presetExists(self, path): + def presetExists(self, path, **kwargs): if os.path.exists(path): + window = self.window \ + if 'window' not in kwargs else kwargs['window'] ch = self.parent.showMessage( msg="%s already exists! Overwrite it?" % os.path.basename(path), showCancel=True, icon=QtGui.QMessageBox.Warning, - parent=self.window) + parent=window) if not ch: # user clicked cancel return True @@ -204,6 +209,7 @@ class PresetManager(QtGui.QDialog): os.remove(filepath) def warnMessage(self, window=None): + print(window) self.parent.showMessage( msg='Preset names must contain only letters, ' 'numbers, and spaces.', From 2ad14b7d6ca9216bcdc72c5e13937fcbccc887a3 Mon Sep 17 00:00:00 2001 From: tassaron Date: Tue, 13 Jun 2017 22:47:18 -0400 Subject: [PATCH 16/22] asterisk next to modified preset is more accurate hopefully --- components/__base__.py | 9 +-- components/color.py | 4 +- components/image.py | 4 +- components/original.py | 4 +- components/text.py | 4 +- components/video.py | 4 +- core.py | 125 ++++++++++++++++------------------------- mainwindow.py | 20 ++++--- presetmanager.py | 19 ++----- 9 files changed, 80 insertions(+), 113 deletions(-) diff --git a/components/__base__.py b/components/__base__.py index b32c120..bc6644b 100644 --- a/components/__base__.py +++ b/components/__base__.py @@ -1,5 +1,6 @@ from PyQt4 import QtGui, QtCore + class Component(QtCore.QObject): '''A base class for components to inherit from''' @@ -26,8 +27,8 @@ class Component(QtCore.QObject): self.canceled = False def update(self): - self.modified.emit(self.compPos, True) - # use super().update() then read your widget values here + self.modified.emit(self.compPos, self.savePreset()) + # read your widget values, then call super().update() def loadPreset(self, presetDict, presetName): '''Children should take (presetDict, presetName=None) as args''' @@ -36,10 +37,10 @@ class Component(QtCore.QObject): # Then update your widgets using the preset dict self.currentPreset = presetName \ if presetName != None else presetDict['preset'] - + ''' def savePreset(self): return {} - + ''' def preFrameRender(self, **kwargs): for var, value in kwargs.items(): exec('self.%s = value' % var) diff --git a/components/color.py b/components/color.py index b6105c8..5912bfa 100644 --- a/components/color.py +++ b/components/color.py @@ -8,7 +8,7 @@ from . import __base__ class Component(__base__.Component): '''Color''' - modified = QtCore.pyqtSignal(int, bool) + modified = QtCore.pyqtSignal(int, dict) def widget(self, parent): self.parent = parent @@ -48,12 +48,12 @@ class Component(__base__.Component): return page def update(self): - super().update() self.color1 = self.RGBFromString(self.page.lineEdit_color1.text()) self.color2 = self.RGBFromString(self.page.lineEdit_color2.text()) self.x = self.page.spinBox_x.value() self.y = self.page.spinBox_y.value() self.parent.drawPreview() + super().update() def previewRender(self, previewWorker): width = int(previewWorker.core.settings.value('outputWidth')) diff --git a/components/image.py b/components/image.py index ed5f243..cdf10c7 100644 --- a/components/image.py +++ b/components/image.py @@ -7,7 +7,7 @@ from . import __base__ class Component(__base__.Component): '''Image''' - modified = QtCore.pyqtSignal(int, bool) + modified = QtCore.pyqtSignal(int, dict) def widget(self, parent): self.parent = parent @@ -25,9 +25,9 @@ class Component(__base__.Component): return page def update(self): - super().update() self.imagePath = self.page.lineEdit_image.text() self.parent.drawPreview() + super().update() def previewRender(self, previewWorker): width = int(previewWorker.core.settings.value('outputWidth')) diff --git a/components/original.py b/components/original.py index f3f578d..9df2815 100644 --- a/components/original.py +++ b/components/original.py @@ -11,7 +11,7 @@ from copy import copy class Component(__base__.Component): '''Original Audio Visualization''' - modified = QtCore.pyqtSignal(int, bool) + modified = QtCore.pyqtSignal(int, dict) def widget(self, parent): self.parent = parent @@ -35,10 +35,10 @@ class Component(__base__.Component): return page def update(self): - super().update() self.layout = self.page.comboBox_visLayout.currentIndex() self.visColor = self.RGBFromString(self.page.lineEdit_visColor.text()) self.parent.drawPreview() + super().update() def loadPreset(self, pr, presetName=None): super().loadPreset(pr, presetName) diff --git a/components/text.py b/components/text.py index a40e2a9..165a093 100644 --- a/components/text.py +++ b/components/text.py @@ -10,7 +10,7 @@ from . import __base__ class Component(__base__.Component): '''Title Text''' - modified = QtCore.pyqtSignal(int, bool) + modified = QtCore.pyqtSignal(int, dict) def __init__(self, *args): super().__init__(*args) @@ -56,7 +56,6 @@ class Component(__base__.Component): return page def update(self): - super().update() self.title = self.page.lineEdit_title.text() self.alignment = self.page.comboBox_textAlign.currentIndex() self.titleFont = self.page.fontComboBox_titleFont.currentFont() @@ -66,6 +65,7 @@ class Component(__base__.Component): self.textColor = self.RGBFromString( self.page.lineEdit_textColor.text()) self.parent.drawPreview() + super().update() def getXY(self): '''Returns true x, y after considering alignment settings''' diff --git a/components/video.py b/components/video.py index ff06329..841f77b 100644 --- a/components/video.py +++ b/components/video.py @@ -87,7 +87,7 @@ class Video: class Component(__base__.Component): '''Video''' - modified = QtCore.pyqtSignal(int, bool) + modified = QtCore.pyqtSignal(int, dict) def widget(self, parent): self.parent = parent @@ -109,10 +109,10 @@ class Component(__base__.Component): return page def update(self): - super().update() self.videoPath = self.page.lineEdit_video.text() self.loopVideo = self.page.checkBox_loop.isChecked() self.parent.drawPreview() + super().update() def previewRender(self, previewWorker): width = int(previewWorker.core.settings.value('outputWidth')) diff --git a/core.py b/core.py index 5617c81..ef369c9 100644 --- a/core.py +++ b/core.py @@ -6,9 +6,7 @@ from os.path import expanduser import subprocess as sp import numpy from PIL import Image -#import tempfile from shutil import rmtree -#import atexit import time from collections import OrderedDict import json @@ -21,11 +19,6 @@ class Core(): def __init__(self): self.FFMPEG_BIN = self.findFfmpeg() - #self.tempDir = os.path.join( - # tempfile.gettempdir(), 'audio-visualizer-python-data') - #if not os.path.exists(self.tempDir): - # os.makedirs(self.tempDir) - #atexit.register(self.deleteTempDir) self.dataDir = QDesktopServices.storageLocation( QDesktopServices.DataLocation) self.presetDir = os.path.join(self.dataDir, 'presets') @@ -34,7 +27,8 @@ class Core(): self.findComponents() self.selectedComponents = [] - self.modifiedComponents = {} + # copies of named presets to detect modification + self.savedPresets = {} def findComponents(self): def findComponents(): @@ -54,7 +48,6 @@ class Core(): def componentListChanged(self): for i, component in enumerate(self.selectedComponents): component.compPos = i - # print('Modified Components: ', self.modifiedComponents) def insertComponent(self, compPos, moduleIndex): if compPos < 0: @@ -66,76 +59,52 @@ class Core(): compPos, component) - newDict = {} - for i, val in self.modifiedComponents.items(): - if i >= compPos: - newDict[i+1] = bool(val) - else: - newDict[i] = bool(val) - self.modifiedComponents.clear() - self.modifiedComponents = newDict - self.componentListChanged() return compPos def moveComponent(self, startI, endI): - def insert(target, comp): - self.selectedComponents.insert(target, comp) - comp = self.selectedComponents.pop(startI) - insert(endI, comp) - - try: - oldModified = self.modifiedComponents.pop(startI) - if endI in self.modifiedComponents: - self.modifiedComponents[startI] = \ - self.modifiedComponents.pop(endI) - self.modifiedComponents[endI] = oldModified - except KeyError: - pass + self.selectedComponents.insert(endI, comp) self.componentListChanged() return endI def removeComponent(self, i): self.selectedComponents.pop(i) - try: - self.modifiedComponents.pop(i) - except KeyError: - pass - - newDict = {} - for index, val in self.modifiedComponents.items(): - if index >= i: - newDict[index-1] = bool(val) - else: - newDict[index] = bool(val) - self.modifiedComponents.clear() - self.modifiedComponents = newDict - self.componentListChanged() def updateComponent(self, i): # print('updating %s' % self.selectedComponents[i]) self.selectedComponents[i].update() - def componentModified(self, i): - '''Triggered by mainwindow.updateComponentTitle() - Tracks temporary state of whether components are modified or not - for retrieval upon loading a project file''' - self.modifiedComponents[i] = True - - def componentUnmodified(self, i): - try: - self.modifiedComponents.pop(i) - except KeyError: - pass - def moduleIndexFor(self, compName): compNames = [mod.Component.__doc__ for mod in self.modules] index = compNames.index(compName) return self.moduleIndexes[index] + def openPreset(self, filepath, compIndex, presetName): + '''Applies a preset to a specific component''' + saveValueStore = self.getPreset(filepath) + if not saveValueStore: + return False + + self.selectedComponents[compIndex].loadPreset( + saveValueStore, + presetName + ) + self.savedPresets[presetName] = dict(saveValueStore) + return True + + def getPreset(self, filepath): + '''Returns the preset dict stored at this filepath''' + if not os.path.exists(filepath): + return False + with open(filepath, 'r') as f: + for line in f: + saveValueStore = Core.presetFromString(line.strip()) + break + return saveValueStore + def openProject(self, loader, filepath): '''loader is the object calling this method (mainwindow/command) which implements an insertComponent method''' @@ -143,15 +112,29 @@ class Core(): if errcode == 0: for i, tup in enumerate(data['Components']): name, vers, preset = tup + + # add loaded named presets to savedPresets dict + if 'preset' in preset and preset['preset'] != None: + nam = preset['preset'] + filepath2 = os.path.join( + self.presetDir, name, str(vers), nam) + origSaveValueStore = self.getPreset(filepath2) + self.savedPresets[nam] = dict(origSaveValueStore) + + # insert component into the loader loader.insertComponent( self.moduleIndexFor(name), -1) - self.selectedComponents[-1].loadPreset( - preset) - if data['Modified'][i] == True: - self.componentModified(i) - loader.updateComponentTitle(i, True) + + if 'preset' in preset and preset['preset'] != None: + self.selectedComponents[-1].loadPreset( + preset + ) else: - loader.updateComponentTitle(i) + self.selectedComponents[-1].loadPreset( + preset, + preset['preset'] + ) + elif errcode == 1: typ, value, _ = data if typ.__name__ == KeyError: @@ -174,7 +157,7 @@ class Core(): with open(filepath, 'r') as f: def parseLine(line): '''Decides if a given avp or avl line is a section header''' - validSections = ('Components', 'Modified') + validSections = ('Components') line = line.strip() newSection = '' @@ -207,8 +190,6 @@ class Core(): lastCompPreset) ) i = 0 - if line and section == 'Modified': - data[section].append(eval(line)) return 0, data except: return 1, sys.exc_info() @@ -294,12 +275,6 @@ class Core(): f.write('%s\n' % str(comp)) f.write('%s\n' % str(comp.version())) f.write('%s\n' % Core.presetToString(saveValueStore)) - f.write('[Modified]\n') - for i in range(len(self.selectedComponents)): - if i in self.modifiedComponents: - f.write('%s\n' % repr(True)) - else: - f.write('%s\n' % repr(False)) return True except: return False @@ -385,12 +360,6 @@ class Core(): return completeAudioArray - #def deleteTempDir(self): - # try: - # rmtree(self.tempDir) - # except FileNotFoundError: - # pass - def cancel(self): self.canceled = True diff --git a/mainwindow.py b/mainwindow.py index 2a04a4a..3ea4f58 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -235,9 +235,17 @@ class MainWindow(QtCore.QObject): self.previewThread.wait() self.autosave() - @QtCore.pyqtSlot(int, bool) - def updateComponentTitle(self, pos, modified=False): - #print(pos, modified) + @QtCore.pyqtSlot(int, dict) + def updateComponentTitle(self, pos, presetStore=False): + if type(presetStore) == dict: + name = presetStore['preset'] + if name == None: + modified = False + else: + modified = (presetStore != self.core.savedPresets[name]) + else: + print(pos, presetStore) + modified = bool(presetStore) if pos < 0: pos = len(self.core.selectedComponents)-1 title = str(self.core.selectedComponents[pos]) @@ -246,10 +254,6 @@ class MainWindow(QtCore.QObject): if modified: title += '*' self.window.listWidget_componentList.item(pos).setText(title) - if modified: - self.core.componentModified(pos) - else: - self.core.componentUnmodified(pos) def updateCodecs(self): containerWidget = self.window.comboBox_videoContainer @@ -521,6 +525,8 @@ class MainWindow(QtCore.QObject): "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 diff --git a/presetmanager.py b/presetmanager.py index 4300ce1..2083483 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -14,6 +14,7 @@ class PresetManager(QtGui.QDialog): self.presetDir = self.core.presetDir self.findPresets() + # window self.lastFilter = '*' self.presetRows = [] # list of (comp, vers, name) tuples self.window = window @@ -126,6 +127,7 @@ class PresetManager(QtGui.QDialog): continue if newName: if index != -1: + selectedComponents[index].currentPreset = newName saveValueStore = \ selectedComponents[index].savePreset() componentName = str(selectedComponents[index]).strip() @@ -133,10 +135,7 @@ class PresetManager(QtGui.QDialog): self.createNewPreset( componentName, vers, newName, saveValueStore, window=self.parent.window) - selectedComponents[index].currentPreset = newName - #self.findPresets() - #self.drawPresetList() - self.parent.updateComponentTitle(index) + self.openPreset(newName) break def createNewPreset( @@ -173,16 +172,8 @@ class PresetManager(QtGui.QDialog): version = selectedComponents[index].version() dirname = os.path.join(self.presetDir, componentName, str(version)) filepath = os.path.join(dirname, presetName) - if not os.path.exists(filepath): - return - with open(filepath, 'r') as f: - for line in f: - saveValueStore = core.Core.presetFromString(line.strip()) - break - selectedComponents[index].loadPreset( - saveValueStore, - presetName - ) + self.core.openPreset(filepath, index, presetName) + self.parent.updateComponentTitle(index) self.parent.drawPreview() From 807e37bddd16cb8fa195a220d415cb4bedb1364b Mon Sep 17 00:00:00 2001 From: tassaron Date: Wed, 14 Jun 2017 17:36:46 -0400 Subject: [PATCH 17/22] no keyerror when opening new preset --- mainwindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mainwindow.py b/mainwindow.py index 3ea4f58..4b2d567 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -239,7 +239,7 @@ class MainWindow(QtCore.QObject): def updateComponentTitle(self, pos, presetStore=False): if type(presetStore) == dict: name = presetStore['preset'] - if name == None: + if name == None or name not in self.core.savedPresets: modified = False else: modified = (presetStore != self.core.savedPresets[name]) From 8846af57ba9635fe4a1c44778dc468f22277e538 Mon Sep 17 00:00:00 2001 From: tassaron Date: Wed, 14 Jun 2017 19:37:47 -0400 Subject: [PATCH 18/22] image component stretch/scale/x/y options --- components/image.py | 24 ++++++++++++++-- components/image.ui | 68 +++++++++++++++++++++++++++++++++++++++++++-- components/video.ui | 43 +++++++++++++++++++++++++--- core.py | 23 ++++++++------- 4 files changed, 139 insertions(+), 19 deletions(-) diff --git a/components/image.py b/components/image.py index cdf10c7..a2f0521 100644 --- a/components/image.py +++ b/components/image.py @@ -20,12 +20,20 @@ class Component(__base__.Component): page.lineEdit_image.textChanged.connect(self.update) page.pushButton_image.clicked.connect(self.pickImage) + page.spinBox_scale.valueChanged.connect(self.update) + page.checkBox_stretch.stateChanged.connect(self.update) + page.spinBox_x.valueChanged.connect(self.update) + page.spinBox_y.valueChanged.connect(self.update) self.page = page return page def update(self): self.imagePath = self.page.lineEdit_image.text() + self.scale = self.page.spinBox_scale.value() + self.xPosition = self.page.spinBox_x.value() + self.yPosition = self.page.spinBox_y.value() + self.stretched = self.page.checkBox_stretch.isChecked() self.parent.drawPreview() super().update() @@ -47,19 +55,31 @@ class Component(__base__.Component): frame = Image.new("RGBA", (width, height), (0, 0, 0, 0)) if self.imagePath and os.path.exists(self.imagePath): image = Image.open(self.imagePath) - if image.size != (width, height): + if self.stretched and image.size != (width, height): image = image.resize((width, height), Image.ANTIALIAS) - frame.paste(image) + if self.scale != 100: + newHeight = int((image.height / 100) * self.scale) + newWidth = int((image.width / 100) * self.scale) + image = image.resize((newWidth, newHeight), Image.ANTIALIAS) + frame.paste(image, box=(self.xPosition, self.yPosition)) return frame def loadPreset(self, pr, presetName=None): super().loadPreset(pr, presetName) self.page.lineEdit_image.setText(pr['image']) + self.page.spinBox_scale.setValue(pr['scale']) + self.page.spinBox_x.setValue(pr['x']) + self.page.spinBox_y.setValue(pr['y']) + self.page.checkBox_stretch.setChecked(pr['stretched']) def savePreset(self): return { 'preset': self.currentPreset, 'image': self.imagePath, + 'scale': self.scale, + 'stretched': self.stretched, + 'x': self.xPosition, + 'y': self.yPosition, } def pickImage(self): diff --git a/components/image.ui b/components/image.ui index 3cd5b1b..685e997 100644 --- a/components/image.ui +++ b/components/image.ui @@ -124,8 +124,11 @@ 16777215 + + -10000 + - 999999999 + 10000 @@ -163,10 +166,10 @@ - 0 + -1000 - 999999999 + 1000 0 @@ -177,6 +180,65 @@ + + + + + + Stretch + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 5 + 20 + + + + + + + + Scale + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + QAbstractSpinBox::UpDownArrows + + + % + + + 10 + + + 200 + + + 100 + + + + + diff --git a/components/video.ui b/components/video.ui index 6a01368..aca46b4 100644 --- a/components/video.ui +++ b/components/video.ui @@ -111,7 +111,7 @@ - + 0 @@ -124,8 +124,11 @@ 16777215 + + -10000 + - 999999999 + 10000 @@ -163,10 +166,10 @@ - 0 + -10000 - 999999999 + 10000 0 @@ -202,6 +205,35 @@ + + + + Scale + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + QAbstractSpinBox::UpDownArrows + + + % + + + 10 + + + 200 + + + 100 + + + @@ -217,6 +249,9 @@ + + + diff --git a/core.py b/core.py index ef369c9..9276f4a 100644 --- a/core.py +++ b/core.py @@ -124,16 +124,19 @@ class Core(): # insert component into the loader loader.insertComponent( self.moduleIndexFor(name), -1) - - if 'preset' in preset and preset['preset'] != None: - self.selectedComponents[-1].loadPreset( - preset - ) - else: - self.selectedComponents[-1].loadPreset( - preset, - preset['preset'] - ) + try: + if 'preset' in preset and preset['preset'] != None: + self.selectedComponents[-1].loadPreset( + preset + ) + else: + self.selectedComponents[-1].loadPreset( + preset, + preset['preset'] + ) + except KeyError as e: + print('%s missing value %s' % + (self.selectedComponents[-1], e)) elif errcode == 1: typ, value, _ = data From cb639e5c7ccf42e654f1dda1b75b082478cf73d9 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 15 Jun 2017 11:36:26 -0400 Subject: [PATCH 19/22] clear preset button, disable New Project during export enable preset manager during export, and clear deleted presets from project files when opened --- core.py | 17 ++++++++++++++++- mainwindow.py | 20 ++++++++++++++------ presetmanager.py | 21 ++++++++++++++++++--- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/core.py b/core.py index 9276f4a..8eb7d16 100644 --- a/core.py +++ b/core.py @@ -82,6 +82,12 @@ class Core(): index = compNames.index(compName) return self.moduleIndexes[index] + def clearPreset(self, compIndex, loader=None): + '''Clears a preset from a component''' + self.selectedComponents[compIndex].currentPreset = None + if loader: + loader.updateComponentTitle(compIndex) + def openPreset(self, filepath, compIndex, presetName): '''Applies a preset to a specific component''' saveValueStore = self.getPreset(filepath) @@ -112,6 +118,7 @@ class Core(): if errcode == 0: for i, tup in enumerate(data['Components']): name, vers, preset = tup + clearThis = False # add loaded named presets to savedPresets dict if 'preset' in preset and preset['preset'] != None: @@ -119,7 +126,11 @@ class Core(): filepath2 = os.path.join( self.presetDir, name, str(vers), nam) origSaveValueStore = self.getPreset(filepath2) - self.savedPresets[nam] = dict(origSaveValueStore) + if origSaveValueStore: + self.savedPresets[nam] = dict(origSaveValueStore) + else: + # saved preset was renamed or deleted + clearThis = True # insert component into the loader loader.insertComponent( @@ -138,6 +149,10 @@ class Core(): print('%s missing value %s' % (self.selectedComponents[-1], e)) + if clearThis: + self.clearPreset(-1, loader) + + elif errcode == 1: typ, value, _ = data if typ.__name__ == KeyError: diff --git a/mainwindow.py b/mainwindow.py index 4b2d567..5004bf6 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -67,6 +67,7 @@ class MainWindow(QtCore.QObject): os.path.join(os.path.dirname(os.path.realpath(__file__)), 'presetmanager.ui')), self) + if not os.path.exists(self.dataDir): os.makedirs(self.dataDir) for neededDirectory in ( @@ -189,11 +190,11 @@ class MainWindow(QtCore.QObject): # Configure the Projects Menu self.projectMenu = QMenu() - action = self.projectMenu.addAction("New Project") - action.triggered[()].connect(self.createNewProject) + self.ui_newProject = self.projectMenu.addAction("New Project") + self.ui_newProject.triggered[()].connect(self.createNewProject) - action = self.projectMenu.addAction("Open Project") - action.triggered[()].connect(self.openOpenProjectDialog) + self.ui_openProject = self.projectMenu.addAction("Open Project") + self.ui_openProject.triggered[()].connect(self.openOpenProjectDialog) action = self.projectMenu.addAction("Save Project") action.triggered[()].connect(self.saveCurrentProject) @@ -373,8 +374,9 @@ class MainWindow(QtCore.QObject): self.window.pushButton_removeComponent.setEnabled(False) self.window.pushButton_listMoveDown.setEnabled(False) self.window.pushButton_listMoveUp.setEnabled(False) - self.window.pushButton_presets.setEnabled(False) self.window.listWidget_componentList.setEnabled(False) + self.ui_newProject.setEnabled(False) + self.ui_openProject.setEnabled(False) else: self.window.pushButton_createVideo.setEnabled(True) self.window.pushButton_Cancel.setEnabled(False) @@ -391,8 +393,9 @@ class MainWindow(QtCore.QObject): self.window.pushButton_removeComponent.setEnabled(True) self.window.pushButton_listMoveDown.setEnabled(True) self.window.pushButton_listMoveUp.setEnabled(True) - self.window.pushButton_presets.setEnabled(True) self.window.listWidget_componentList.setEnabled(True) + self.ui_newProject.setEnabled(True) + self.ui_openProject.setEnabled(True) def progressBarUpdated(self, value): self.window.progressBar_createVideo.setValue(value) @@ -604,5 +607,10 @@ class MainWindow(QtCore.QObject): except KeyError: pass + menuItem = self.menu.addAction("Clear Preset") + menuItem.triggered.connect( + self.presetManager.clearPreset + ) + self.menu.move(parentPosition + QPos) self.menu.show() diff --git a/presetmanager.py b/presetmanager.py index 2083483..49a6336 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -12,6 +12,11 @@ class PresetManager(QtGui.QDialog): self.core = parent.core self.settings = parent.settings self.presetDir = self.core.presetDir + if not self.settings.value('presetDir'): + self.settings.setValue( + "presetDir", + os.path.join(self.core.dataDir, 'projects')) + self.findPresets() # window @@ -103,10 +108,16 @@ class PresetManager(QtGui.QDialog): for component in self.presets: self.window.comboBox_filter.addItem(component) + def clearPreset(self, compI=None): + '''Functions on mainwindow level from the context menu''' + compI = self.parent.window.listWidget_componentList.currentRow() + self.core.clearPreset(compI, self) + + def openSavePresetDialog(self): '''Functions on mainwindow level from the context menu''' window = self.parent.window - selectedComponents = self.parent.core.selectedComponents + selectedComponents = self.core.selectedComponents componentList = self.parent.window.listWidget_componentList if componentList.currentRow() == -1: @@ -242,9 +253,10 @@ class PresetManager(QtGui.QDialog): def openImportDialog(self): filename = QtGui.QFileDialog.getOpenFileName( self.window, "Import Preset File", - self.settings.value("projectDir"), + self.settings.value("presetDir"), "Preset Files (*.avl)") if filename: + # get installed path & ask user to overwrite if needed path = '' while True: if path: @@ -256,15 +268,17 @@ class PresetManager(QtGui.QDialog): success, path = self.core.importPreset(filename) if success: break + self.findPresets() self.drawPresetList() + self.settings.setValue("presetDir", os.path.dirname(filename)) def openExportDialog(self): if not self.window.listWidget_presets.selectedItems(): return filename = QtGui.QFileDialog.getSaveFileName( self.window, "Export Preset", - self.settings.value("projectDir"), + self.settings.value("presetDir"), "Preset Files (*.avl)") if filename: index = self.window.listWidget_presets.currentRow() @@ -274,3 +288,4 @@ class PresetManager(QtGui.QDialog): msg='Couldn\'t export %s.' % filename, parent=self.window ) + self.settings.setValue("presetDir", os.path.dirname(filename)) From 8603fa12e3d63705e3cbc202bd0635e5ac977225 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 15 Jun 2017 15:09:45 -0400 Subject: [PATCH 20/22] video scaling, position and distortion --- components/video.py | 80 +++++++++++++++++++++++++++++++++++++++------ components/video.ui | 7 ++++ mainwindow.py | 3 +- 3 files changed, 79 insertions(+), 11 deletions(-) diff --git a/components/video.py b/components/video.py index 841f77b..5f55211 100644 --- a/components/video.py +++ b/components/video.py @@ -11,13 +11,15 @@ class Video: '''Video Component Frame-Fetcher''' def __init__(self, **kwargs): mandatoryArgs = [ - 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN + 'ffmpeg', # path to ffmpeg, usually core.FFMPEG_BIN 'videoPath', 'width', 'height', + 'scale', # percentage scale 'frameRate', # frames per second 'chunkSize', # number of bytes in one frame - 'parent' + 'parent', # mainwindow object + 'component', # component object ] for arg in mandatoryArgs: try: @@ -39,7 +41,8 @@ class Video: '-i', self.videoPath, '-f', 'image2pipe', '-pix_fmt', 'rgba', - '-filter:v', 'scale='+str(self.width)+':'+str(self.height), + '-filter:v', 'scale=%s:%s' % + scale(self.scale, self.width, self.height, str), '-vcodec', 'rawvideo', '-', ] @@ -58,7 +61,9 @@ class Video: while True: if num in self.finishedFrames: image = self.finishedFrames.pop(num) - return Image.frombytes('RGBA', (self.width, self.height), image) + return finalizeFrame( + self.component, image, self.width, self.height) + i, image = self.frameBuffer.get() self.finishedFrames[i] = image self.frameBuffer.task_done() @@ -104,6 +109,10 @@ class Component(__base__.Component): page.lineEdit_video.textChanged.connect(self.update) page.pushButton_video.clicked.connect(self.pickVideo) page.checkBox_loop.stateChanged.connect(self.update) + page.checkBox_distort.stateChanged.connect(self.update) + page.spinBox_scale.valueChanged.connect(self.update) + page.spinBox_x.valueChanged.connect(self.update) + page.spinBox_y.valueChanged.connect(self.update) self.page = page return page @@ -111,13 +120,17 @@ class Component(__base__.Component): def update(self): self.videoPath = self.page.lineEdit_video.text() self.loopVideo = self.page.checkBox_loop.isChecked() + self.distort = self.page.checkBox_distort.isChecked() + self.scale = self.page.spinBox_scale.value() + self.xPosition = self.page.spinBox_x.value() + self.yPosition = self.page.spinBox_y.value() self.parent.drawPreview() super().update() def previewRender(self, previewWorker): width = int(previewWorker.core.settings.value('outputWidth')) height = int(previewWorker.core.settings.value('outputHeight')) - self.chunkSize = 4*width*height + self.updateChunksize(width, height) frame = self.getPreviewFrame(width, height) if not frame: return Image.new("RGBA", (width, height), (0, 0, 0, 0)) @@ -128,12 +141,13 @@ class Component(__base__.Component): super().preFrameRender(**kwargs) width = int(self.worker.core.settings.value('outputWidth')) height = int(self.worker.core.settings.value('outputHeight')) - self.chunkSize = 4*width*height + self.updateChunksize(width, height) self.video = Video( ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath, width=width, height=height, chunkSize=self.chunkSize, frameRate=int(self.settings.value("outputFrameRate")), - parent=self.parent, loopVideo=self.loopVideo + parent=self.parent, loopVideo=self.loopVideo, + component=self, scale=self.scale ) def frameRender(self, moduleNo, arrayNo, frameNo): @@ -143,12 +157,20 @@ class Component(__base__.Component): super().loadPreset(pr, presetName) self.page.lineEdit_video.setText(pr['video']) self.page.checkBox_loop.setChecked(pr['loop']) + self.page.checkBox_distort.setChecked(pr['distort']) + self.page.spinBox_scale.setValue(pr['scale']) + self.page.spinBox_x.setValue(pr['x']) + self.page.spinBox_y.setValue(pr['y']) def savePreset(self): return { 'preset': self.currentPreset, 'video': self.videoPath, 'loop': self.loopVideo, + 'distort': self.distort, + 'scale': self.scale, + 'x': self.xPosition, + 'y': self.yPosition, } def pickVideo(self): @@ -165,13 +187,15 @@ class Component(__base__.Component): def getPreviewFrame(self, width, height): if not self.videoPath or not os.path.exists(self.videoPath): return + command = [ self.parent.core.FFMPEG_BIN, '-thread_queue_size', '512', '-i', self.videoPath, '-f', 'image2pipe', '-pix_fmt', 'rgba', - '-filter:v', 'scale='+str(width)+':'+str(height), + '-filter:v', 'scale=%s:%s' % + scale(self.scale, width, height, str), '-vcodec', 'rawvideo', '-', '-ss', '90', '-vframes', '1', @@ -181,7 +205,43 @@ class Component(__base__.Component): stderr=subprocess.DEVNULL, bufsize=10**8 ) byteFrame = pipe.stdout.read(self.chunkSize) - image = Image.frombytes('RGBA', (width, height), byteFrame) + frame = finalizeFrame(self, byteFrame, width, height) pipe.stdout.close() pipe.kill() - return image + + return frame + + def updateChunksize(self, width, height): + if self.scale != 100 and not self.distort: + width, height = scale(self.scale, width, height, int) + self.chunkSize = 4*width*height + +def scale(scale, width, height, returntype=None): + width = (float(width) / 100.0) * float(scale) + height = (float(height) / 100.0) * float(scale) + if returntype == str: + return (str(int(width)), str(int(height))) + elif returntype == int: + return (int(width), int(height)) + else: + return (width, height) + +def finalizeFrame(self, imageData, width, height): + if self.distort: + image = Image.frombytes( + 'RGBA', + (width, height), + imageData) + else: + image = Image.frombytes( + 'RGBA', + scale(self.scale, width, height, int), + imageData) + + if self.scale != 100 \ + or self.xPosition != 0 or self.yPosition != 0: + frame = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + frame.paste(image, box=(self.xPosition, self.yPosition)) + else: + frame = image + return frame diff --git a/components/video.ui b/components/video.ui index aca46b4..fa088fa 100644 --- a/components/video.ui +++ b/components/video.ui @@ -205,6 +205,13 @@ + + + + Distort by scale + + + diff --git a/mainwindow.py b/mainwindow.py index 5004bf6..ad1df10 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -321,7 +321,8 @@ class MainWindow(QtCore.QObject): fileName = QtGui.QFileDialog.getSaveFileName( self.window, "Set Output Video File", - outputDir, "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv)") + outputDir, + "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv);; All Files (*)") if not fileName == "": self.settings.setValue("outputDir", os.path.dirname(fileName)) From c05efc73ee069fe2eb8776a27b503ada2adb4af6 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 15 Jun 2017 22:15:03 -0400 Subject: [PATCH 21/22] various bugfixes, blankFrame method for components don't crash from broken project files or nonexistent videopaths, and shareable common paths in core.py --- components/__base__.py | 4 ++ components/color.py | 2 +- components/image.py | 6 ++- components/image.ui | 2 +- components/original.py | 4 +- components/text.py | 2 +- components/video.py | 27 +++++++---- components/video.ui | 2 +- core.py | 102 ++++++++++++++++++++++++++++------------- mainwindow.py | 4 +- preview_thread.py | 5 +- 11 files changed, 105 insertions(+), 55 deletions(-) diff --git a/components/__base__.py b/components/__base__.py index bc6644b..88f22d4 100644 --- a/components/__base__.py +++ b/components/__base__.py @@ -1,4 +1,5 @@ from PyQt4 import QtGui, QtCore +from PIL import Image class Component(QtCore.QObject): @@ -45,6 +46,9 @@ class Component(QtCore.QObject): for var, value in kwargs.items(): exec('self.%s = value' % var) + def blankFrame(self, width, height): + return Image.new("RGBA", (width, height), (0, 0, 0, 0)) + def pickColor(self): dialog = QtGui.QColorDialog() dialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) diff --git a/components/color.py b/components/color.py index 5912bfa..36f3906 100644 --- a/components/color.py +++ b/components/color.py @@ -71,7 +71,7 @@ class Component(__base__.Component): def drawFrame(self, width, height): r, g, b = self.color1 - return Image.new("RGBA", (width, height), (r, g, b, 255)) + return self.blankFrame(width, height) def loadPreset(self, pr, presetName=None): super().loadPreset(pr, presetName) diff --git a/components/image.py b/components/image.py index a2f0521..b6aa29b 100644 --- a/components/image.py +++ b/components/image.py @@ -38,6 +38,7 @@ class Component(__base__.Component): super().update() def previewRender(self, previewWorker): + self.imageFormats = previewWorker.core.imageFormats width = int(previewWorker.core.settings.value('outputWidth')) height = int(previewWorker.core.settings.value('outputHeight')) return self.drawFrame(width, height) @@ -52,7 +53,7 @@ class Component(__base__.Component): return self.drawFrame(width, height) def drawFrame(self, width, height): - frame = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + frame = self.blankFrame(width, height) if self.imagePath and os.path.exists(self.imagePath): image = Image.open(self.imagePath) if self.stretched and image.size != (width, height): @@ -85,7 +86,8 @@ class Component(__base__.Component): def pickImage(self): imgDir = self.settings.value("backgroundDir", os.path.expanduser("~")) filename = QtGui.QFileDialog.getOpenFileName( - self.page, "Choose Image", imgDir, "Image Files (*.jpg *.png)") + self.page, "Choose Image", imgDir, + "Image Files (%s)" % " ".join(self.imageFormats)) if filename: self.settings.setValue("backgroundDir", os.path.dirname(filename)) self.page.lineEdit_image.setText(filename) diff --git a/components/image.ui b/components/image.ui index 685e997..6df03a5 100644 --- a/components/image.ui +++ b/components/image.ui @@ -230,7 +230,7 @@ 10 - 200 + 400 100 diff --git a/components/original.py b/components/original.py index 9df2815..5e2f9d4 100644 --- a/components/original.py +++ b/components/original.py @@ -145,7 +145,7 @@ class Component(__base__.Component): bF = width / 64 bH = bF / 2 bQ = bF / 4 - imTop = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + imTop = self.blankFrame(width, height) draw = ImageDraw.Draw(imTop) r, g, b = color color2 = (r, g, b, 125) @@ -163,7 +163,7 @@ class Component(__base__.Component): imBottom = imTop.transpose(Image.FLIP_TOP_BOTTOM) - im = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + im = self.blankFrame(width, height) if layout == 0: y = 0 - int(height/100*43) diff --git a/components/text.py b/components/text.py index 165a093..f8ef7b3 100644 --- a/components/text.py +++ b/components/text.py @@ -126,7 +126,7 @@ class Component(__base__.Component): def addText(self, width, height): x, y = self.getXY() - im = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + im = self.blankFrame(width, height) image = ImageQt(im) painter = QPainter(image) diff --git a/components/video.py b/components/video.py index 5f55211..3d43a18 100644 --- a/components/video.py +++ b/components/video.py @@ -128,12 +128,13 @@ class Component(__base__.Component): super().update() def previewRender(self, previewWorker): + self.videoFormats = previewWorker.core.videoFormats width = int(previewWorker.core.settings.value('outputWidth')) height = int(previewWorker.core.settings.value('outputHeight')) self.updateChunksize(width, height) frame = self.getPreviewFrame(width, height) if not frame: - return Image.new("RGBA", (width, height), (0, 0, 0, 0)) + return self.blankFrame(width, height) else: return frame @@ -141,6 +142,7 @@ class Component(__base__.Component): super().preFrameRender(**kwargs) width = int(self.worker.core.settings.value('outputWidth')) height = int(self.worker.core.settings.value('outputHeight')) + self.blankFrame_ = self.blankFrame(width, height) self.updateChunksize(width, height) self.video = Video( ffmpeg=self.parent.core.FFMPEG_BIN, videoPath=self.videoPath, @@ -148,10 +150,13 @@ class Component(__base__.Component): frameRate=int(self.settings.value("outputFrameRate")), parent=self.parent, loopVideo=self.loopVideo, component=self, scale=self.scale - ) + ) if os.path.exists(self.videoPath) else None def frameRender(self, moduleNo, arrayNo, frameNo): - return self.video.frame(frameNo) + if self.video: + return self.video.frame(frameNo) + else: + return self.blankFrame_ def loadPreset(self, pr, presetName=None): super().loadPreset(pr, presetName) @@ -177,7 +182,7 @@ class Component(__base__.Component): imgDir = self.settings.value("backgroundDir", os.path.expanduser("~")) filename = QtGui.QFileDialog.getOpenFileName( self.page, "Choose Video", - imgDir, "Video Files (*.mp4 *.mov)" + imgDir, "Video Files (%s)" % " ".join(self.videoFormats) ) if filename: self.settings.setValue("backgroundDir", os.path.dirname(filename)) @@ -228,10 +233,14 @@ def scale(scale, width, height, returntype=None): def finalizeFrame(self, imageData, width, height): if self.distort: - image = Image.frombytes( - 'RGBA', - (width, height), - imageData) + try: + image = Image.frombytes( + 'RGBA', + (width, height), + imageData) + except ValueError: + print('#### ignored invalid data caused by distortion ####') + image = self.blankFrame(width, height) else: image = Image.frombytes( 'RGBA', @@ -240,7 +249,7 @@ def finalizeFrame(self, imageData, width, height): if self.scale != 100 \ or self.xPosition != 0 or self.yPosition != 0: - frame = Image.new("RGBA", (width, height), (0, 0, 0, 0)) + frame = self.blankFrame(width, height) frame.paste(image, box=(self.xPosition, self.yPosition)) else: frame = image diff --git a/components/video.ui b/components/video.ui index fa088fa..f05e8a5 100644 --- a/components/video.ui +++ b/components/video.ui @@ -234,7 +234,7 @@ 10 - 200 + 400 100 diff --git a/core.py b/core.py index 8eb7d16..3fca7bf 100644 --- a/core.py +++ b/core.py @@ -24,6 +24,32 @@ class Core(): self.presetDir = os.path.join(self.dataDir, 'presets') self.wd = os.path.dirname(os.path.realpath(__file__)) self.loadEncoderOptions() + self.videoFormats = Core.appendUppercase([ + '*.mp4', + '*.mov', + '*.mkv', + '*.avi', + '*.webm', + '*.flv', + ]) + self.audioFormats = Core.appendUppercase([ + '*.mp3', + '*.wav', + '*.ogg', + '*.fla', + '*.aac', + ]) + self.imageFormats = Core.appendUppercase([ + '*.png', + '*.jpg', + '*.tif', + '*.tiff', + '*.gif', + '*.bmp', + '*.ico', + '*.xbm', + '*.xpm', + ]) self.findComponents() self.selectedComponents = [] @@ -116,44 +142,48 @@ class Core(): which implements an insertComponent method''' errcode, data = self.parseAvFile(filepath) if errcode == 0: - for i, tup in enumerate(data['Components']): - name, vers, preset = tup - clearThis = False + try: + for i, tup in enumerate(data['Components']): + name, vers, preset = tup + clearThis = False - # add loaded named presets to savedPresets dict - if 'preset' in preset and preset['preset'] != None: - nam = preset['preset'] - filepath2 = os.path.join( - self.presetDir, name, str(vers), nam) - origSaveValueStore = self.getPreset(filepath2) - if origSaveValueStore: - self.savedPresets[nam] = dict(origSaveValueStore) - else: - # saved preset was renamed or deleted - clearThis = True - - # insert component into the loader - loader.insertComponent( - self.moduleIndexFor(name), -1) - try: + # add loaded named presets to savedPresets dict if 'preset' in preset and preset['preset'] != None: - self.selectedComponents[-1].loadPreset( - preset - ) - else: - self.selectedComponents[-1].loadPreset( - preset, - preset['preset'] - ) - except KeyError as e: - print('%s missing value %s' % - (self.selectedComponents[-1], e)) + nam = preset['preset'] + filepath2 = os.path.join( + self.presetDir, name, str(vers), nam) + origSaveValueStore = self.getPreset(filepath2) + if origSaveValueStore: + self.savedPresets[nam] = dict(origSaveValueStore) + else: + # saved preset was renamed or deleted + clearThis = True - if clearThis: - self.clearPreset(-1, loader) + # insert component into the loader + loader.insertComponent( + self.moduleIndexFor(name), -1) + try: + if 'preset' in preset and preset['preset'] != None: + self.selectedComponents[-1].loadPreset( + preset + ) + else: + self.selectedComponents[-1].loadPreset( + preset, + preset['preset'] + ) + except KeyError as e: + print('%s missing value %s' % + (self.selectedComponents[-1], e)) + + if clearThis: + self.clearPreset(-1, loader) + except: + errcode = 1 + data = sys.exc_info() - elif errcode == 1: + if errcode == 1: typ, value, _ = data if typ.__name__ == KeyError: # probably just an old version, still loadable @@ -398,3 +428,9 @@ class Core(): def presetFromString(string): '''Turns a string repr of OrderedDict into a regular dict''' return dict(eval(string)) + + @staticmethod + def appendUppercase(lst): + for form, i in zip(lst, range(len(lst))): + lst.append(form.upper()) + return lst diff --git a/mainwindow.py b/mainwindow.py index ad1df10..f1959cb 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -310,7 +310,7 @@ class MainWindow(QtCore.QObject): fileName = QtGui.QFileDialog.getOpenFileName( self.window, "Open Music File", - inputDir, "Music Files (*.mp3 *.wav *.ogg *.fla *.aac)") + inputDir, "Music Files (%s)" % " ".join(self.core.audioFormats)) if not fileName == "": self.settings.setValue("inputDir", os.path.dirname(fileName)) @@ -322,7 +322,7 @@ class MainWindow(QtCore.QObject): fileName = QtGui.QFileDialog.getSaveFileName( self.window, "Set Output Video File", outputDir, - "Video Files (*.mp4 *.mov *.mkv *.avi *.webm *.flv);; All Files (*)") + "Video Files (%s);; All Files (*)" % " ".join(self.core.videoFormats)) if not fileName == "": self.settings.setValue("outputDir", os.path.dirname(fileName)) diff --git a/preview_thread.py b/preview_thread.py index d54dba5..e3e8279 100644 --- a/preview_thread.py +++ b/preview_thread.py @@ -1,11 +1,9 @@ from PyQt4 import QtCore, QtGui, uic from PyQt4.QtCore import pyqtSignal, pyqtSlot -from PIL import Image, ImageDraw, ImageFont +from PIL import Image from PIL.ImageQt import ImageQt import core -import time from queue import Queue, Empty -import numpy import os from copy import copy @@ -18,6 +16,7 @@ class Worker(QtCore.QObject): QtCore.QObject.__init__(self) parent.newTask.connect(self.createPreviewImage) parent.processTask.connect(self.process) + self.parent = parent self.core = core.Core() self.queue = queue self.core.settings = parent.settings From ee8031925fcd93d7bedceff6e98a06f3806426b3 Mon Sep 17 00:00:00 2001 From: tassaron Date: Thu, 15 Jun 2017 23:13:36 -0400 Subject: [PATCH 22/22] drag events for component list now working! --- core.py | 14 +++++++++----- mainwindow.py | 34 ++++++++++++++++++++++++++-------- mainwindow.ui | 9 ++++++--- presetmanager.py | 3 +-- 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/core.py b/core.py index 3fca7bf..dcea783 100644 --- a/core.py +++ b/core.py @@ -68,7 +68,8 @@ class Core(): yield name self.modules = [ import_module('components.%s' % name) - for name in findComponents()] + for name in findComponents() + ] self.moduleIndexes = [i for i in range(len(self.modules))] def componentListChanged(self): @@ -119,11 +120,14 @@ class Core(): saveValueStore = self.getPreset(filepath) if not saveValueStore: return False + try: + self.selectedComponents[compIndex].loadPreset( + saveValueStore, + presetName + ) + except KeyError as e: + print('preset missing value: %s' % e) - self.selectedComponents[compIndex].loadPreset( - saveValueStore, - presetName - ) self.savedPresets[presetName] = dict(saveValueStore) return True diff --git a/mainwindow.py b/mainwindow.py index f1959cb..fb9ebfd 100644 --- a/mainwindow.py +++ b/mainwindow.py @@ -157,7 +157,7 @@ class MainWindow(QtCore.QObject): self.window.pushButton_addComponent.setMenu(self.compMenu) - componentList.dropEvent = self.componentListChanged + componentList.dropEvent = self.dragComponent componentList.itemSelectionChanged.connect( self.changeComponentWidget) @@ -479,9 +479,26 @@ class MainWindow(QtCore.QObject): stackedWidget.setCurrentIndex(newRow) self.drawPreview() - def componentListChanged(self, *args): - '''Update all our tracking variables to match the widget''' - pass + 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() @@ -608,10 +625,11 @@ class MainWindow(QtCore.QObject): except KeyError: pass - menuItem = self.menu.addAction("Clear Preset") - menuItem.triggered.connect( - self.presetManager.clearPreset - ) + 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() diff --git a/mainwindow.ui b/mainwindow.ui index af47cee..e892959 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -6,8 +6,8 @@ 0 0 - 1008 - 575 + 1028 + 592 @@ -175,6 +175,9 @@ 16777215 + + true + QFrame::StyledPanel @@ -188,7 +191,7 @@ true - false + true false diff --git a/presetmanager.py b/presetmanager.py index 49a6336..3b02714 100644 --- a/presetmanager.py +++ b/presetmanager.py @@ -111,8 +111,7 @@ class PresetManager(QtGui.QDialog): def clearPreset(self, compI=None): '''Functions on mainwindow level from the context menu''' compI = self.parent.window.listWidget_componentList.currentRow() - self.core.clearPreset(compI, self) - + self.core.clearPreset(compI, self.parent) def openSavePresetDialog(self): '''Functions on mainwindow level from the context menu'''