undoable edits for normal component settings; TODO: merge small edits
This commit is contained in:
parent
733c005eea
commit
a1d7cbb984
|
@ -12,7 +12,7 @@ import logging
|
||||||
|
|
||||||
from toolkit.frame import BlankFrame
|
from toolkit.frame import BlankFrame
|
||||||
from toolkit import (
|
from toolkit import (
|
||||||
getWidgetValue, setWidgetValue, connectWidget, rgbFromString
|
getWidgetValue, setWidgetValue, connectWidget, rgbFromString, blockSignals
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -305,14 +305,46 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
'''
|
'''
|
||||||
Reads all tracked widget values into instance attributes
|
A component update triggered by the user changing a widget value
|
||||||
and tells the MainWindow that the component was modified.
|
Call super() at the END when subclassing this.
|
||||||
Call super() at the END if you need to subclass this.
|
|
||||||
'''
|
'''
|
||||||
for attr, widget in self._trackedWidgets.items():
|
oldWidgetVals = {
|
||||||
|
attr: getattr(self, attr)
|
||||||
|
for attr in self._trackedWidgets
|
||||||
|
}
|
||||||
|
newWidgetVals = {
|
||||||
|
attr: getWidgetValue(widget)
|
||||||
|
if attr not in self._colorWidgets else rgbFromString(widget.text())
|
||||||
|
for attr, widget in self._trackedWidgets.items()
|
||||||
|
}
|
||||||
|
if any([val != oldWidgetVals[attr]
|
||||||
|
for attr, val in newWidgetVals.items()
|
||||||
|
]):
|
||||||
|
action = ComponentUpdate(self, oldWidgetVals, newWidgetVals)
|
||||||
|
self.parent.undoStack.push(action)
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
'''An internal component update that is not undoable'''
|
||||||
|
|
||||||
|
newWidgetVals = {
|
||||||
|
attr: getWidgetValue(widget)
|
||||||
|
for attr, widget in self._trackedWidgets.items()
|
||||||
|
}
|
||||||
|
self.setAttrs(newWidgetVals)
|
||||||
|
self.sendUpdateSignal()
|
||||||
|
|
||||||
|
def setAttrs(self, attrDict):
|
||||||
|
'''
|
||||||
|
Sets attrs (linked to trackedWidgets) in this preset to
|
||||||
|
the values in the attrDict. Mutates certain widget values if needed
|
||||||
|
'''
|
||||||
|
for attr, val in attrDict.items():
|
||||||
if attr in self._colorWidgets:
|
if attr in self._colorWidgets:
|
||||||
# Color Widgets: text stored as tuple & update the button color
|
# Color Widgets: text stored as tuple & update the button color
|
||||||
rgbTuple = rgbFromString(widget.text())
|
if type(val) is tuple:
|
||||||
|
rgbTuple = val
|
||||||
|
else:
|
||||||
|
rgbTuple = rgbFromString(val)
|
||||||
btnStyle = (
|
btnStyle = (
|
||||||
"QPushButton { background-color : %s; outline: none; }"
|
"QPushButton { background-color : %s; outline: none; }"
|
||||||
% QColor(*rgbTuple).name())
|
% QColor(*rgbTuple).name())
|
||||||
|
@ -322,12 +354,11 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
elif attr in self._relativeWidgets:
|
elif attr in self._relativeWidgets:
|
||||||
# Relative widgets: number scales to fit export resolution
|
# Relative widgets: number scales to fit export resolution
|
||||||
self.updateRelativeWidget(attr)
|
self.updateRelativeWidget(attr)
|
||||||
setattr(self, attr, self._trackedWidgets[attr].value())
|
setattr(self, attr, val)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Normal tracked widget
|
# Normal tracked widget
|
||||||
setattr(self, attr, getWidgetValue(widget))
|
setattr(self, attr, val)
|
||||||
self.sendUpdateSignal()
|
|
||||||
|
|
||||||
def sendUpdateSignal(self):
|
def sendUpdateSignal(self):
|
||||||
if not self.core.openingProject:
|
if not self.core.openingProject:
|
||||||
|
@ -541,7 +572,6 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
||||||
pixelVal = self.pixelValForAttr(attr, floatVal)
|
pixelVal = self.pixelValForAttr(attr, floatVal)
|
||||||
self._trackedWidgets[attr].setValue(pixelVal)
|
self._trackedWidgets[attr].setValue(pixelVal)
|
||||||
|
|
||||||
|
|
||||||
def updateRelativeWidget(self, attr):
|
def updateRelativeWidget(self, attr):
|
||||||
try:
|
try:
|
||||||
oldUserValue = getattr(self, attr)
|
oldUserValue = getattr(self, attr)
|
||||||
|
@ -628,3 +658,30 @@ class ComponentError(RuntimeError):
|
||||||
super().__init__(string)
|
super().__init__(string)
|
||||||
caller.lockError(string)
|
caller.lockError(string)
|
||||||
caller._error.emit(string, detail)
|
caller._error.emit(string, detail)
|
||||||
|
|
||||||
|
|
||||||
|
class ComponentUpdate(QtWidgets.QUndoCommand):
|
||||||
|
'''Command object for making a component action undoable'''
|
||||||
|
def __init__(self, parent, oldWidgetVals, newWidgetVals):
|
||||||
|
super().__init__(
|
||||||
|
'Changed %s component #%s' % (
|
||||||
|
parent.name, parent.compPos
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.parent = parent
|
||||||
|
self.oldWidgetVals = oldWidgetVals
|
||||||
|
self.newWidgetVals = newWidgetVals
|
||||||
|
|
||||||
|
def redo(self):
|
||||||
|
self.parent.setAttrs(self.newWidgetVals)
|
||||||
|
self.parent.sendUpdateSignal()
|
||||||
|
|
||||||
|
def undo(self):
|
||||||
|
self.parent.setAttrs(self.oldWidgetVals)
|
||||||
|
with blockSignals(self.parent):
|
||||||
|
for attr, widget in self.parent._trackedWidgets.items():
|
||||||
|
val = self.oldWidgetVals[attr]
|
||||||
|
if attr in self.parent._colorWidgets:
|
||||||
|
val = '%s,%s,%s' % val
|
||||||
|
setWidgetValue(widget, val)
|
||||||
|
self.parent.sendUpdateSignal()
|
||||||
|
|
|
@ -17,9 +17,6 @@ class Component(Component):
|
||||||
self.y = 0
|
self.y = 0
|
||||||
super().widget(*args)
|
super().widget(*args)
|
||||||
|
|
||||||
self.page.lineEdit_color1.setText('0,0,0')
|
|
||||||
self.page.lineEdit_color2.setText('133,133,133')
|
|
||||||
|
|
||||||
# disable color #2 until non-default 'fill' option gets changed
|
# disable color #2 until non-default 'fill' option gets changed
|
||||||
self.page.lineEdit_color2.setDisabled(True)
|
self.page.lineEdit_color2.setDisabled(True)
|
||||||
self.page.pushButton_color2.setDisabled(True)
|
self.page.pushButton_color2.setDisabled(True)
|
||||||
|
|
|
@ -73,6 +73,9 @@
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>0,0,0</string>
|
||||||
|
</property>
|
||||||
<property name="maxLength">
|
<property name="maxLength">
|
||||||
<number>12</number>
|
<number>12</number>
|
||||||
</property>
|
</property>
|
||||||
|
@ -146,6 +149,9 @@
|
||||||
<height>0</height>
|
<height>0</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>133,133,133</string>
|
||||||
|
</property>
|
||||||
<property name="maxLength">
|
<property name="maxLength">
|
||||||
<number>12</number>
|
<number>12</number>
|
||||||
</property>
|
</property>
|
||||||
|
|
|
@ -13,8 +13,6 @@ class Component(Component):
|
||||||
|
|
||||||
def widget(self, *args):
|
def widget(self, *args):
|
||||||
super().widget(*args)
|
super().widget(*args)
|
||||||
self.textColor = (255, 255, 255)
|
|
||||||
self.strokeColor = (0, 0, 0)
|
|
||||||
self.title = 'Text'
|
self.title = 'Text'
|
||||||
self.alignment = 1
|
self.alignment = 1
|
||||||
self.titleFont = QFont()
|
self.titleFont = QFont()
|
||||||
|
@ -25,8 +23,6 @@ class Component(Component):
|
||||||
self.page.comboBox_textAlign.addItem("Right")
|
self.page.comboBox_textAlign.addItem("Right")
|
||||||
self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
|
self.page.comboBox_textAlign.setCurrentIndex(int(self.alignment))
|
||||||
|
|
||||||
self.page.lineEdit_textColor.setText('%s,%s,%s' % self.textColor)
|
|
||||||
self.page.lineEdit_strokeColor.setText('%s,%s,%s' % self.strokeColor)
|
|
||||||
self.page.spinBox_fontSize.setValue(int(self.fontSize))
|
self.page.spinBox_fontSize.setValue(int(self.fontSize))
|
||||||
self.page.lineEdit_title.setText(self.title)
|
self.page.lineEdit_title.setText(self.title)
|
||||||
|
|
||||||
|
|
|
@ -427,6 +427,9 @@
|
||||||
<property name="focusPolicy">
|
<property name="focusPolicy">
|
||||||
<enum>Qt::NoFocus</enum>
|
<enum>Qt::NoFocus</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>255,255,255</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
@ -485,6 +488,9 @@
|
||||||
<property name="focusPolicy">
|
<property name="focusPolicy">
|
||||||
<enum>Qt::NoFocus</enum>
|
<enum>Qt::NoFocus</enum>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>0,0,0</string>
|
||||||
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
|
|
20
src/core.py
20
src/core.py
|
@ -94,12 +94,11 @@ class Core:
|
||||||
compPos,
|
compPos,
|
||||||
component
|
component
|
||||||
)
|
)
|
||||||
self.componentListChanged()
|
|
||||||
if moduleIndex > -1:
|
|
||||||
self.updateComponent(compPos)
|
|
||||||
|
|
||||||
if hasattr(loader, 'insertComponent'):
|
if hasattr(loader, 'insertComponent'):
|
||||||
loader.insertComponent(compPos)
|
loader.insertComponent(compPos)
|
||||||
|
|
||||||
|
self.componentListChanged()
|
||||||
|
self.updateComponent(compPos)
|
||||||
return compPos
|
return compPos
|
||||||
|
|
||||||
def moveComponent(self, startI, endI):
|
def moveComponent(self, startI, endI):
|
||||||
|
@ -119,7 +118,7 @@ class Core:
|
||||||
|
|
||||||
def updateComponent(self, i):
|
def updateComponent(self, i):
|
||||||
log.debug('Updating %s #%s' % (self.selectedComponents[i], str(i)))
|
log.debug('Updating %s #%s' % (self.selectedComponents[i], str(i)))
|
||||||
self.selectedComponents[i].update()
|
self.selectedComponents[i]._update()
|
||||||
|
|
||||||
def moduleIndexFor(self, compName):
|
def moduleIndexFor(self, compName):
|
||||||
try:
|
try:
|
||||||
|
@ -540,6 +539,7 @@ class Core:
|
||||||
"projectDir": os.path.join(cls.dataDir, 'projects'),
|
"projectDir": os.path.join(cls.dataDir, 'projects'),
|
||||||
"pref_insertCompAtTop": True,
|
"pref_insertCompAtTop": True,
|
||||||
"pref_genericPreview": True,
|
"pref_genericPreview": True,
|
||||||
|
"pref_undoLimit": 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
for parm, value in cls.defaultSettings.items():
|
for parm, value in cls.defaultSettings.items():
|
||||||
|
@ -552,8 +552,14 @@ class Core:
|
||||||
if not key.startswith('pref_'):
|
if not key.startswith('pref_'):
|
||||||
continue
|
continue
|
||||||
val = cls.settings.value(key)
|
val = cls.settings.value(key)
|
||||||
if val in ('true', 'false'):
|
try:
|
||||||
cls.settings.setValue(key, True if val == 'true' else False)
|
val = int(val)
|
||||||
|
except ValueError:
|
||||||
|
if val == 'true':
|
||||||
|
val = True
|
||||||
|
elif val == 'false':
|
||||||
|
val = False
|
||||||
|
cls.settings.setValue(key, val)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def makeLogger():
|
def makeLogger():
|
||||||
|
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
@ -42,13 +42,22 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
def __init__(self, window, project):
|
def __init__(self, window, project):
|
||||||
QtWidgets.QMainWindow.__init__(self)
|
QtWidgets.QMainWindow.__init__(self)
|
||||||
|
log.debug(
|
||||||
|
'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
|
||||||
self.window = window
|
self.window = window
|
||||||
self.core = Core()
|
self.core = Core()
|
||||||
Core.mode = 'GUI'
|
Core.mode = 'GUI'
|
||||||
log.debug(
|
|
||||||
'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
|
|
||||||
|
|
||||||
|
# Find settings created by Core object
|
||||||
|
self.dataDir = Core.dataDir
|
||||||
|
self.presetDir = Core.presetDir
|
||||||
|
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
|
||||||
|
self.settings = Core.settings
|
||||||
|
|
||||||
|
# Create stack of undoable user actions
|
||||||
self.undoStack = QtWidgets.QUndoStack(self)
|
self.undoStack = QtWidgets.QUndoStack(self)
|
||||||
|
undoLimit = self.settings.value("pref_undoLimit")
|
||||||
|
self.undoStack.setUndoLimit(undoLimit)
|
||||||
|
|
||||||
# widgets of component settings
|
# widgets of component settings
|
||||||
self.pages = []
|
self.pages = []
|
||||||
|
@ -58,12 +67,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
self.autosaveCooldown = 0.2
|
self.autosaveCooldown = 0.2
|
||||||
self.encoding = False
|
self.encoding = False
|
||||||
|
|
||||||
# Find settings created by Core object
|
|
||||||
self.dataDir = Core.dataDir
|
|
||||||
self.presetDir = Core.presetDir
|
|
||||||
self.autosavePath = os.path.join(self.dataDir, 'autosave.avp')
|
|
||||||
self.settings = Core.settings
|
|
||||||
|
|
||||||
self.presetManager = PresetManager(
|
self.presetManager = PresetManager(
|
||||||
uic.loadUi(
|
uic.loadUi(
|
||||||
os.path.join(Core.wd, 'gui', 'presetmanager.ui')), self)
|
os.path.join(Core.wd, 'gui', 'presetmanager.ui')), self)
|
||||||
|
@ -302,6 +305,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
|
QtWidgets.QShortcut("Ctrl+A", self.window, self.openSaveProjectDialog)
|
||||||
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
|
QtWidgets.QShortcut("Ctrl+O", self.window, self.openOpenProjectDialog)
|
||||||
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
|
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
|
||||||
|
|
||||||
QtWidgets.QShortcut("Ctrl+Z", self.window, self.undoStack.undo)
|
QtWidgets.QShortcut("Ctrl+Z", self.window, self.undoStack.undo)
|
||||||
QtWidgets.QShortcut("Ctrl+Y", self.window, self.undoStack.redo)
|
QtWidgets.QShortcut("Ctrl+Y", self.window, self.undoStack.redo)
|
||||||
QtWidgets.QShortcut("Ctrl+Shift+Z", self.window, self.undoStack.redo)
|
QtWidgets.QShortcut("Ctrl+Shift+Z", self.window, self.undoStack.redo)
|
||||||
|
@ -353,6 +357,9 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
QtWidgets.QShortcut(
|
QtWidgets.QShortcut(
|
||||||
"Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
|
"Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
|
||||||
)
|
)
|
||||||
|
QtWidgets.QShortcut(
|
||||||
|
"Ctrl+Alt+Shift+U", self.window, self.showUndoStack
|
||||||
|
)
|
||||||
|
|
||||||
@QtCore.pyqtSlot()
|
@QtCore.pyqtSlot()
|
||||||
def cleanUp(self, *args):
|
def cleanUp(self, *args):
|
||||||
|
@ -658,6 +665,14 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
def showPreviewImage(self, image):
|
def showPreviewImage(self, image):
|
||||||
self.previewWindow.changePixmap(image)
|
self.previewWindow.changePixmap(image)
|
||||||
|
|
||||||
|
def showUndoStack(self):
|
||||||
|
dialog = QtWidgets.QDialog(self.window)
|
||||||
|
undoView = QtWidgets.QUndoView(self.undoStack)
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
layout.addWidget(undoView)
|
||||||
|
dialog.setLayout(layout)
|
||||||
|
dialog.show()
|
||||||
|
|
||||||
def showFfmpegCommand(self):
|
def showFfmpegCommand(self):
|
||||||
from textwrap import wrap
|
from textwrap import wrap
|
||||||
from toolkit.ffmpeg import createFfmpegCommand
|
from toolkit.ffmpeg import createFfmpegCommand
|
||||||
|
@ -784,6 +799,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
field.blockSignals(False)
|
field.blockSignals(False)
|
||||||
self.progressBarUpdated(0)
|
self.progressBarUpdated(0)
|
||||||
self.progressBarSetText('')
|
self.progressBarSetText('')
|
||||||
|
self.undoStack.clear()
|
||||||
|
|
||||||
@disableWhenEncoding
|
@disableWhenEncoding
|
||||||
def createNewProject(self, prompt=True):
|
def createNewProject(self, prompt=True):
|
||||||
|
@ -847,7 +863,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
||||||
|
|
||||||
def openProject(self, filepath, prompt=True):
|
def openProject(self, filepath, prompt=True):
|
||||||
if not filepath or not os.path.exists(filepath) \
|
if not filepath or not os.path.exists(filepath) \
|
||||||
or not filepath.endswith('.avp'):
|
or not filepath.endswith('.avp'):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
|
@ -9,6 +9,18 @@ import subprocess
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
class blockSignals:
|
||||||
|
'''A context manager to temporarily block a Qt widget from updating'''
|
||||||
|
def __init__(self, widget):
|
||||||
|
self.widget = widget
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.widget.blockSignals(True)
|
||||||
|
|
||||||
|
def __exit__(self, *args):
|
||||||
|
self.widget.blockSignals(False)
|
||||||
|
|
||||||
|
|
||||||
def badName(name):
|
def badName(name):
|
||||||
'''Returns whether a name contains non-alphanumeric chars'''
|
'''Returns whether a name contains non-alphanumeric chars'''
|
||||||
return any([letter in string.punctuation for letter in name])
|
return any([letter in string.punctuation for letter in name])
|
||||||
|
|
|
@ -98,7 +98,7 @@ def Checkerboard(width, height):
|
||||||
log.debug('Creating new %s*%s checkerboard' % (width, height))
|
log.debug('Creating new %s*%s checkerboard' % (width, height))
|
||||||
image = FloodFrame(1920, 1080, (0, 0, 0, 0))
|
image = FloodFrame(1920, 1080, (0, 0, 0, 0))
|
||||||
image.paste(Image.open(
|
image.paste(Image.open(
|
||||||
os.path.join(core.Core.wd, "background.png")),
|
os.path.join(core.Core.wd, 'gui', "background.png")),
|
||||||
(0, 0)
|
(0, 0)
|
||||||
)
|
)
|
||||||
image = image.resize((width, height))
|
image = image.resize((width, height))
|
||||||
|
|
Reference in New Issue