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 import (
|
||||
getWidgetValue, setWidgetValue, connectWidget, rgbFromString
|
||||
getWidgetValue, setWidgetValue, connectWidget, rgbFromString, blockSignals
|
||||
)
|
||||
|
||||
|
||||
|
@ -305,14 +305,46 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
|||
|
||||
def update(self):
|
||||
'''
|
||||
Reads all tracked widget values into instance attributes
|
||||
and tells the MainWindow that the component was modified.
|
||||
Call super() at the END if you need to subclass this.
|
||||
A component update triggered by the user changing a widget value
|
||||
Call super() at the END when subclassing 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:
|
||||
# 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 = (
|
||||
"QPushButton { background-color : %s; outline: none; }"
|
||||
% QColor(*rgbTuple).name())
|
||||
|
@ -322,12 +354,11 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
|||
elif attr in self._relativeWidgets:
|
||||
# Relative widgets: number scales to fit export resolution
|
||||
self.updateRelativeWidget(attr)
|
||||
setattr(self, attr, self._trackedWidgets[attr].value())
|
||||
setattr(self, attr, val)
|
||||
|
||||
else:
|
||||
# Normal tracked widget
|
||||
setattr(self, attr, getWidgetValue(widget))
|
||||
self.sendUpdateSignal()
|
||||
setattr(self, attr, val)
|
||||
|
||||
def sendUpdateSignal(self):
|
||||
if not self.core.openingProject:
|
||||
|
@ -541,7 +572,6 @@ class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
|||
pixelVal = self.pixelValForAttr(attr, floatVal)
|
||||
self._trackedWidgets[attr].setValue(pixelVal)
|
||||
|
||||
|
||||
def updateRelativeWidget(self, attr):
|
||||
try:
|
||||
oldUserValue = getattr(self, attr)
|
||||
|
@ -628,3 +658,30 @@ class ComponentError(RuntimeError):
|
|||
super().__init__(string)
|
||||
caller.lockError(string)
|
||||
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
|
||||
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
|
||||
self.page.lineEdit_color2.setDisabled(True)
|
||||
self.page.pushButton_color2.setDisabled(True)
|
||||
|
|
|
@ -73,6 +73,9 @@
|
|||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0,0,0</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>12</number>
|
||||
</property>
|
||||
|
@ -146,6 +149,9 @@
|
|||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>133,133,133</string>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>12</number>
|
||||
</property>
|
||||
|
|
|
@ -13,8 +13,6 @@ class Component(Component):
|
|||
|
||||
def widget(self, *args):
|
||||
super().widget(*args)
|
||||
self.textColor = (255, 255, 255)
|
||||
self.strokeColor = (0, 0, 0)
|
||||
self.title = 'Text'
|
||||
self.alignment = 1
|
||||
self.titleFont = QFont()
|
||||
|
@ -25,8 +23,6 @@ class Component(Component):
|
|||
self.page.comboBox_textAlign.addItem("Right")
|
||||
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.lineEdit_title.setText(self.title)
|
||||
|
||||
|
|
|
@ -427,6 +427,9 @@
|
|||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>255,255,255</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -485,6 +488,9 @@
|
|||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0,0,0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
|
20
src/core.py
20
src/core.py
|
@ -94,12 +94,11 @@ class Core:
|
|||
compPos,
|
||||
component
|
||||
)
|
||||
self.componentListChanged()
|
||||
if moduleIndex > -1:
|
||||
self.updateComponent(compPos)
|
||||
|
||||
if hasattr(loader, 'insertComponent'):
|
||||
loader.insertComponent(compPos)
|
||||
|
||||
self.componentListChanged()
|
||||
self.updateComponent(compPos)
|
||||
return compPos
|
||||
|
||||
def moveComponent(self, startI, endI):
|
||||
|
@ -119,7 +118,7 @@ class Core:
|
|||
|
||||
def updateComponent(self, i):
|
||||
log.debug('Updating %s #%s' % (self.selectedComponents[i], str(i)))
|
||||
self.selectedComponents[i].update()
|
||||
self.selectedComponents[i]._update()
|
||||
|
||||
def moduleIndexFor(self, compName):
|
||||
try:
|
||||
|
@ -540,6 +539,7 @@ class Core:
|
|||
"projectDir": os.path.join(cls.dataDir, 'projects'),
|
||||
"pref_insertCompAtTop": True,
|
||||
"pref_genericPreview": True,
|
||||
"pref_undoLimit": 10,
|
||||
}
|
||||
|
||||
for parm, value in cls.defaultSettings.items():
|
||||
|
@ -552,8 +552,14 @@ class Core:
|
|||
if not key.startswith('pref_'):
|
||||
continue
|
||||
val = cls.settings.value(key)
|
||||
if val in ('true', 'false'):
|
||||
cls.settings.setValue(key, True if val == 'true' else False)
|
||||
try:
|
||||
val = int(val)
|
||||
except ValueError:
|
||||
if val == 'true':
|
||||
val = True
|
||||
elif val == 'false':
|
||||
val = False
|
||||
cls.settings.setValue(key, val)
|
||||
|
||||
@staticmethod
|
||||
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):
|
||||
QtWidgets.QMainWindow.__init__(self)
|
||||
log.debug(
|
||||
'Main thread id: {}'.format(int(QtCore.QThread.currentThreadId())))
|
||||
self.window = window
|
||||
self.core = Core()
|
||||
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)
|
||||
undoLimit = self.settings.value("pref_undoLimit")
|
||||
self.undoStack.setUndoLimit(undoLimit)
|
||||
|
||||
# widgets of component settings
|
||||
self.pages = []
|
||||
|
@ -58,12 +67,6 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
self.autosaveCooldown = 0.2
|
||||
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(
|
||||
uic.loadUi(
|
||||
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+O", self.window, self.openOpenProjectDialog)
|
||||
QtWidgets.QShortcut("Ctrl+N", self.window, self.createNewProject)
|
||||
|
||||
QtWidgets.QShortcut("Ctrl+Z", self.window, self.undoStack.undo)
|
||||
QtWidgets.QShortcut("Ctrl+Y", 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(
|
||||
"Ctrl+Alt+Shift+F", self.window, self.showFfmpegCommand
|
||||
)
|
||||
QtWidgets.QShortcut(
|
||||
"Ctrl+Alt+Shift+U", self.window, self.showUndoStack
|
||||
)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def cleanUp(self, *args):
|
||||
|
@ -658,6 +665,14 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
def showPreviewImage(self, 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):
|
||||
from textwrap import wrap
|
||||
from toolkit.ffmpeg import createFfmpegCommand
|
||||
|
@ -784,6 +799,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
field.blockSignals(False)
|
||||
self.progressBarUpdated(0)
|
||||
self.progressBarSetText('')
|
||||
self.undoStack.clear()
|
||||
|
||||
@disableWhenEncoding
|
||||
def createNewProject(self, prompt=True):
|
||||
|
@ -847,7 +863,7 @@ class MainWindow(QtWidgets.QMainWindow):
|
|||
|
||||
def openProject(self, filepath, prompt=True):
|
||||
if not filepath or not os.path.exists(filepath) \
|
||||
or not filepath.endswith('.avp'):
|
||||
or not filepath.endswith('.avp'):
|
||||
return
|
||||
|
||||
self.clear()
|
||||
|
|
|
@ -9,6 +9,18 @@ import subprocess
|
|||
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):
|
||||
'''Returns whether a name contains non-alphanumeric chars'''
|
||||
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))
|
||||
image = FloodFrame(1920, 1080, (0, 0, 0, 0))
|
||||
image.paste(Image.open(
|
||||
os.path.join(core.Core.wd, "background.png")),
|
||||
os.path.join(core.Core.wd, 'gui', "background.png")),
|
||||
(0, 0)
|
||||
)
|
||||
image = image.resize((width, height))
|
||||
|
|
Reference in New Issue