undoable edits for normal component settings; TODO: merge small edits

This commit is contained in:
tassaron 2017-08-15 22:20:25 -04:00
parent 733c005eea
commit a1d7cbb984
10 changed files with 130 additions and 34 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -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():

View File

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -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()

View File

@ -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])

View File

@ -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))