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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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