2017-07-02 21:38:19 -04:00
|
|
|
'''
|
2017-07-20 20:31:38 -04:00
|
|
|
Base classes for components to import. Read comments for some documentation
|
|
|
|
on making a valid component.
|
2017-07-02 21:38:19 -04:00
|
|
|
'''
|
2017-07-02 14:19:15 -04:00
|
|
|
from PyQt5 import uic, QtCore, QtWidgets
|
2017-06-22 18:40:34 -04:00
|
|
|
import os
|
2017-07-27 17:49:08 -04:00
|
|
|
import time
|
|
|
|
|
|
|
|
from toolkit.frame import BlankFrame
|
2017-05-29 20:39:11 -04:00
|
|
|
|
2017-07-23 01:53:54 -04:00
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
class ComponentMetaclass(type(QtCore.QObject)):
|
|
|
|
'''
|
|
|
|
Checks the validity of each Component class imported, and
|
|
|
|
mutates some attributes for easier use by the core program.
|
|
|
|
E.g., takes only major version from version string & decorates methods
|
|
|
|
'''
|
2017-07-23 22:55:41 -04:00
|
|
|
|
2017-07-24 21:22:04 -04:00
|
|
|
def initializationWrapper(func):
|
|
|
|
def initializationWrapper(self, *args, **kwargs):
|
|
|
|
try:
|
|
|
|
return func(self, *args, **kwargs)
|
2017-07-25 17:44:59 -04:00
|
|
|
except Exception:
|
2017-07-24 21:22:04 -04:00
|
|
|
try:
|
2017-07-25 22:02:47 -04:00
|
|
|
raise ComponentError(self, 'initialization process')
|
2017-07-24 21:22:04 -04:00
|
|
|
except ComponentError:
|
|
|
|
return
|
|
|
|
return initializationWrapper
|
|
|
|
|
2017-07-23 22:55:41 -04:00
|
|
|
def renderWrapper(func):
|
2017-07-24 21:22:04 -04:00
|
|
|
def renderWrapper(self, *args, **kwargs):
|
2017-07-23 22:55:41 -04:00
|
|
|
try:
|
|
|
|
return func(self, *args, **kwargs)
|
2017-07-27 17:49:08 -04:00
|
|
|
except Exception as e:
|
2017-07-23 22:55:41 -04:00
|
|
|
try:
|
2017-07-27 17:49:08 -04:00
|
|
|
if e.__name__.startswith('Component'):
|
|
|
|
raise
|
|
|
|
else:
|
|
|
|
raise ComponentError(self, 'renderer')
|
2017-07-23 22:55:41 -04:00
|
|
|
except ComponentError:
|
|
|
|
return BlankFrame()
|
2017-07-24 21:22:04 -04:00
|
|
|
return renderWrapper
|
2017-07-23 22:55:41 -04:00
|
|
|
|
|
|
|
def commandWrapper(func):
|
2017-07-24 21:22:04 -04:00
|
|
|
'''Intercepts the command() method to check for global args'''
|
|
|
|
def commandWrapper(self, arg):
|
2017-07-23 22:55:41 -04:00
|
|
|
if arg.startswith('preset='):
|
|
|
|
from presetmanager import getPresetDir
|
|
|
|
_, preset = arg.split('=', 1)
|
|
|
|
path = os.path.join(getPresetDir(self), preset)
|
|
|
|
if not os.path.exists(path):
|
|
|
|
print('Couldn\'t locate preset "%s"' % preset)
|
|
|
|
quit(1)
|
|
|
|
else:
|
|
|
|
print('Opening "%s" preset on layer %s' % (
|
|
|
|
preset, self.compPos)
|
|
|
|
)
|
|
|
|
self.core.openPreset(path, self.compPos, preset)
|
|
|
|
# Don't call the component's command() method
|
|
|
|
return
|
|
|
|
else:
|
|
|
|
return func(self, arg)
|
2017-07-24 21:22:04 -04:00
|
|
|
return commandWrapper
|
2017-07-23 22:55:41 -04:00
|
|
|
|
|
|
|
def propertiesWrapper(func):
|
|
|
|
'''Intercepts the usual properties if the properties are locked.'''
|
2017-07-24 21:22:04 -04:00
|
|
|
def propertiesWrapper(self):
|
2017-07-23 22:55:41 -04:00
|
|
|
if self._lockedProperties is not None:
|
|
|
|
return self._lockedProperties
|
|
|
|
else:
|
2017-07-25 22:02:47 -04:00
|
|
|
try:
|
|
|
|
return func(self)
|
|
|
|
except Exception:
|
|
|
|
try:
|
|
|
|
raise ComponentError(self, 'properties')
|
|
|
|
except ComponentError:
|
|
|
|
return []
|
2017-07-24 21:22:04 -04:00
|
|
|
return propertiesWrapper
|
2017-07-23 22:55:41 -04:00
|
|
|
|
|
|
|
def errorWrapper(func):
|
|
|
|
'''Intercepts the usual error message if it is locked.'''
|
2017-07-24 21:22:04 -04:00
|
|
|
def errorWrapper(self):
|
2017-07-23 22:55:41 -04:00
|
|
|
if self._lockedError is not None:
|
|
|
|
return self._lockedError
|
|
|
|
else:
|
|
|
|
return func(self)
|
2017-07-24 21:22:04 -04:00
|
|
|
return errorWrapper
|
2017-07-23 22:55:41 -04:00
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
def __new__(cls, name, parents, attrs):
|
2017-07-23 01:53:54 -04:00
|
|
|
if 'ui' not in attrs:
|
2017-07-23 17:14:21 -04:00
|
|
|
# Use module name as ui filename by default
|
2017-07-23 01:53:54 -04:00
|
|
|
attrs['ui'] = '%s.ui' % os.path.splitext(
|
|
|
|
attrs['__module__'].split('.')[-1]
|
|
|
|
)[0]
|
2017-07-20 20:31:38 -04:00
|
|
|
|
2017-07-23 17:14:21 -04:00
|
|
|
# if parents[0] == QtCore.QObject: else:
|
2017-07-23 22:55:41 -04:00
|
|
|
decorate = (
|
|
|
|
'names', # Class methods
|
|
|
|
'error', 'audio', 'properties', # Properties
|
2017-07-24 21:22:04 -04:00
|
|
|
'preFrameRender', 'previewRender',
|
2017-07-27 17:49:08 -04:00
|
|
|
'frameRender', 'command',
|
2017-07-23 22:55:41 -04:00
|
|
|
)
|
2017-07-20 20:31:38 -04:00
|
|
|
|
2017-07-23 17:14:21 -04:00
|
|
|
# Auto-decorate methods
|
|
|
|
for key in decorate:
|
2017-07-20 20:31:38 -04:00
|
|
|
if key not in attrs:
|
|
|
|
continue
|
|
|
|
|
2017-07-23 17:14:21 -04:00
|
|
|
if key in ('names'):
|
|
|
|
attrs[key] = classmethod(attrs[key])
|
|
|
|
|
|
|
|
if key in ('audio'):
|
|
|
|
attrs[key] = property(attrs[key])
|
|
|
|
|
|
|
|
if key == 'command':
|
2017-07-23 22:55:41 -04:00
|
|
|
attrs[key] = cls.commandWrapper(attrs[key])
|
|
|
|
|
2017-07-27 17:49:08 -04:00
|
|
|
if key in ('previewRender', 'frameRender'):
|
2017-07-23 22:55:41 -04:00
|
|
|
attrs[key] = cls.renderWrapper(attrs[key])
|
2017-07-23 17:14:21 -04:00
|
|
|
|
2017-07-24 21:22:04 -04:00
|
|
|
if key == 'preFrameRender':
|
|
|
|
attrs[key] = cls.initializationWrapper(attrs[key])
|
|
|
|
|
2017-07-23 17:14:21 -04:00
|
|
|
if key == 'properties':
|
2017-07-23 22:55:41 -04:00
|
|
|
attrs[key] = cls.propertiesWrapper(attrs[key])
|
2017-07-23 17:14:21 -04:00
|
|
|
|
|
|
|
if key == 'error':
|
2017-07-23 22:55:41 -04:00
|
|
|
attrs[key] = cls.errorWrapper(attrs[key])
|
2017-07-23 01:53:54 -04:00
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
# Turn version string into a number
|
|
|
|
try:
|
|
|
|
if 'version' not in attrs:
|
|
|
|
print(
|
|
|
|
'No version attribute in %s. Defaulting to 1' %
|
|
|
|
attrs['name'])
|
|
|
|
attrs['version'] = 1
|
|
|
|
else:
|
|
|
|
attrs['version'] = int(attrs['version'].split('.')[0])
|
|
|
|
except ValueError:
|
|
|
|
print('%s component has an invalid version string:\n%s' % (
|
|
|
|
attrs['name'], str(attrs['version'])))
|
|
|
|
except KeyError:
|
|
|
|
print('%s component has no version string.' % attrs['name'])
|
|
|
|
else:
|
|
|
|
return super().__new__(cls, name, parents, attrs)
|
|
|
|
quit(1)
|
|
|
|
|
|
|
|
|
|
|
|
class Component(QtCore.QObject, metaclass=ComponentMetaclass):
|
2017-07-09 01:10:06 -04:00
|
|
|
'''
|
2017-07-20 20:31:38 -04:00
|
|
|
The base class for components to inherit.
|
2017-07-09 01:10:06 -04:00
|
|
|
'''
|
2017-06-12 22:34:37 -04:00
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
name = 'Component'
|
2017-07-24 21:22:04 -04:00
|
|
|
# ui = 'name_Of_Non_Default_Ui_File'
|
2017-07-23 17:14:21 -04:00
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
version = '1.0.0'
|
2017-07-23 01:53:54 -04:00
|
|
|
# The major version (before the first dot) is used to determine
|
2017-07-20 20:31:38 -04:00
|
|
|
# preset compatibility; the rest is ignored so it can be non-numeric.
|
|
|
|
|
|
|
|
modified = QtCore.pyqtSignal(int, dict)
|
2017-07-23 17:14:21 -04:00
|
|
|
_error = QtCore.pyqtSignal(str, str)
|
2017-07-20 20:31:38 -04:00
|
|
|
|
2017-07-23 01:53:54 -04:00
|
|
|
def __init__(self, moduleIndex, compPos, core):
|
2017-06-12 22:34:37 -04:00
|
|
|
super().__init__()
|
|
|
|
self.moduleIndex = moduleIndex
|
|
|
|
self.compPos = compPos
|
2017-07-23 01:53:54 -04:00
|
|
|
self.core = core
|
|
|
|
self.currentPreset = None
|
|
|
|
|
|
|
|
self._trackedWidgets = {}
|
|
|
|
self._presetNames = {}
|
2017-07-23 17:14:21 -04:00
|
|
|
self._commandArgs = {}
|
|
|
|
self._lockedProperties = None
|
|
|
|
self._lockedError = None
|
2017-07-20 20:31:38 -04:00
|
|
|
|
|
|
|
# Stop lengthy processes in response to this variable
|
|
|
|
self.canceled = False
|
2017-06-07 23:22:55 -04:00
|
|
|
|
2017-05-29 20:39:11 -04:00
|
|
|
def __str__(self):
|
2017-07-20 20:31:38 -04:00
|
|
|
return self.__class__.name
|
2017-05-30 19:31:10 -04:00
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
def __repr__(self):
|
|
|
|
return '%s\n%s\n%s' % (
|
|
|
|
self.__class__.name, str(self.__class__.version), self.savePreset()
|
|
|
|
)
|
|
|
|
|
2017-07-27 17:49:08 -04:00
|
|
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
|
|
|
# Critical Methods
|
|
|
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
|
|
|
|
|
|
|
def previewRender(self):
|
|
|
|
image = BlankFrame(self.width, self.height)
|
|
|
|
return image
|
|
|
|
|
|
|
|
def preFrameRender(self, **kwargs):
|
|
|
|
'''
|
|
|
|
Must call super() when subclassing
|
|
|
|
Triggered only before a video is exported (video_thread.py)
|
|
|
|
self.worker = the video thread worker
|
|
|
|
self.completeAudioArray = a list of audio samples
|
|
|
|
self.sampleSize = number of audio samples per video frame
|
|
|
|
self.progressBarUpdate = signal to set progress bar number
|
|
|
|
self.progressBarSetText = signal to set progress bar text
|
|
|
|
Use the latter two signals to update the MainWindow if needed
|
|
|
|
for a long initialization procedure (i.e., for a visualizer)
|
|
|
|
'''
|
|
|
|
for key, value in kwargs.items():
|
|
|
|
setattr(self, key, value)
|
|
|
|
|
|
|
|
def frameRender(self, frameNo):
|
|
|
|
audioArrayIndex = frameNo * self.sampleSize
|
|
|
|
image = BlankFrame(self.width, self.height)
|
|
|
|
return image
|
|
|
|
|
|
|
|
def renderFinished(self):
|
|
|
|
pass
|
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
|
|
|
# Properties
|
|
|
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
2017-06-04 13:00:36 -04:00
|
|
|
|
2017-07-09 21:27:29 -04:00
|
|
|
def properties(self):
|
|
|
|
'''
|
|
|
|
Return a list of properties to signify if your component is
|
2017-07-11 06:06:22 -04:00
|
|
|
non-animated ('static'), returns sound ('audio'), or has
|
|
|
|
encountered an error in configuration ('error').
|
2017-07-09 21:27:29 -04:00
|
|
|
'''
|
|
|
|
return []
|
|
|
|
|
2017-07-11 06:06:22 -04:00
|
|
|
def error(self):
|
|
|
|
'''
|
|
|
|
Return a string containing an error message, or None for a default.
|
2017-07-23 17:14:21 -04:00
|
|
|
Or tuple of two strings for a message with details.
|
2017-07-27 17:49:08 -04:00
|
|
|
Alternatively use lockError(msgString) within properties()
|
|
|
|
to skip this method entirely.
|
2017-07-11 06:06:22 -04:00
|
|
|
'''
|
|
|
|
return
|
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
def audio(self):
|
2017-07-13 21:59:23 -04:00
|
|
|
'''
|
2017-07-20 20:31:38 -04:00
|
|
|
Return audio to mix into master as a tuple with two elements:
|
|
|
|
The first element can be:
|
|
|
|
- A string (path to audio file),
|
|
|
|
- Or an object that returns audio data through a pipe
|
|
|
|
The second element must be a dictionary of ffmpeg filters/options
|
|
|
|
to apply to the input stream. See the filter docs for ideas:
|
|
|
|
https://ffmpeg.org/ffmpeg-filters.html
|
2017-07-13 21:59:23 -04:00
|
|
|
'''
|
2017-06-04 13:00:36 -04:00
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
2017-07-27 17:49:08 -04:00
|
|
|
# Idle Methods
|
2017-07-20 20:31:38 -04:00
|
|
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
|
|
|
|
2017-07-23 01:53:54 -04:00
|
|
|
def widget(self, parent):
|
|
|
|
'''
|
|
|
|
Call super().widget(*args) to create the component widget
|
|
|
|
which also auto-connects any common widgets (e.g., checkBoxes)
|
|
|
|
to self.update(). Then in a subclass connect special actions
|
|
|
|
(e.g., pushButtons to select a file/colour) and initialize
|
|
|
|
'''
|
|
|
|
self.parent = parent
|
|
|
|
self.settings = parent.settings
|
|
|
|
self.page = self.loadUi(self.__class__.ui)
|
|
|
|
|
|
|
|
# Connect widget signals
|
|
|
|
widgets = {
|
|
|
|
'lineEdit': self.page.findChildren(QtWidgets.QLineEdit),
|
|
|
|
'checkBox': self.page.findChildren(QtWidgets.QCheckBox),
|
|
|
|
'spinBox': self.page.findChildren(QtWidgets.QSpinBox),
|
|
|
|
'comboBox': self.page.findChildren(QtWidgets.QComboBox),
|
|
|
|
}
|
|
|
|
widgets['spinBox'].extend(
|
|
|
|
self.page.findChildren(QtWidgets.QDoubleSpinBox)
|
|
|
|
)
|
|
|
|
for widget in widgets['lineEdit']:
|
|
|
|
widget.textChanged.connect(self.update)
|
|
|
|
for widget in widgets['checkBox']:
|
|
|
|
widget.stateChanged.connect(self.update)
|
|
|
|
for widget in widgets['spinBox']:
|
|
|
|
widget.valueChanged.connect(self.update)
|
|
|
|
for widget in widgets['comboBox']:
|
|
|
|
widget.currentIndexChanged.connect(self.update)
|
|
|
|
|
|
|
|
def update(self):
|
2017-07-09 01:10:06 -04:00
|
|
|
'''
|
2017-07-23 01:53:54 -04:00
|
|
|
Reads all tracked widget values into instance attributes
|
|
|
|
and tells the MainWindow that the component was modified.
|
2017-07-27 17:49:08 -04:00
|
|
|
Call super() at the END if you need to subclass this.
|
2017-07-23 01:53:54 -04:00
|
|
|
'''
|
|
|
|
for attr, widget in self._trackedWidgets.items():
|
|
|
|
if type(widget) == QtWidgets.QLineEdit:
|
|
|
|
setattr(self, attr, widget.text())
|
|
|
|
elif type(widget) == QtWidgets.QSpinBox \
|
|
|
|
or type(widget) == QtWidgets.QDoubleSpinBox:
|
|
|
|
setattr(self, attr, widget.value())
|
|
|
|
elif type(widget) == QtWidgets.QCheckBox:
|
|
|
|
setattr(self, attr, widget.isChecked())
|
|
|
|
elif type(widget) == QtWidgets.QComboBox:
|
|
|
|
setattr(self, attr, widget.currentIndex())
|
|
|
|
if not self.core.openingProject:
|
|
|
|
self.parent.drawPreview()
|
|
|
|
saveValueStore = self.savePreset()
|
|
|
|
saveValueStore['preset'] = self.currentPreset
|
|
|
|
self.modified.emit(self.compPos, saveValueStore)
|
|
|
|
|
|
|
|
def loadPreset(self, presetDict, presetName=None):
|
|
|
|
'''
|
|
|
|
Subclasses should take (presetDict, *args) as args.
|
|
|
|
Must use super().loadPreset(presetDict, *args) first,
|
2017-07-09 01:10:06 -04:00
|
|
|
then update self.page widgets using the preset dict.
|
2017-06-22 18:40:34 -04:00
|
|
|
'''
|
2017-06-12 22:34:37 -04:00
|
|
|
self.currentPreset = presetName \
|
2017-06-23 23:00:24 -04:00
|
|
|
if presetName is not None else presetDict['preset']
|
2017-07-23 01:53:54 -04:00
|
|
|
for attr, widget in self._trackedWidgets.items():
|
|
|
|
val = presetDict[
|
|
|
|
attr if attr not in self._presetNames
|
|
|
|
else self._presetNames[attr]
|
|
|
|
]
|
|
|
|
if type(widget) == QtWidgets.QLineEdit:
|
|
|
|
widget.setText(val)
|
|
|
|
elif type(widget) == QtWidgets.QSpinBox \
|
|
|
|
or type(widget) == QtWidgets.QDoubleSpinBox:
|
|
|
|
widget.setValue(val)
|
|
|
|
elif type(widget) == QtWidgets.QCheckBox:
|
|
|
|
widget.setChecked(val)
|
|
|
|
elif type(widget) == QtWidgets.QComboBox:
|
|
|
|
widget.setCurrentIndex(val)
|
|
|
|
|
|
|
|
def savePreset(self):
|
|
|
|
saveValueStore = {}
|
|
|
|
for attr, widget in self._trackedWidgets.items():
|
|
|
|
saveValueStore[
|
|
|
|
attr if attr not in self._presetNames
|
|
|
|
else self._presetNames[attr]
|
|
|
|
] = getattr(self, attr)
|
|
|
|
return saveValueStore
|
2017-06-22 18:40:34 -04:00
|
|
|
|
2017-07-23 01:53:54 -04:00
|
|
|
def commandHelp(self):
|
|
|
|
'''Help text as string for this component's commandline arguments'''
|
|
|
|
|
|
|
|
def command(self, arg=''):
|
2017-07-09 01:10:06 -04:00
|
|
|
'''
|
2017-07-23 01:53:54 -04:00
|
|
|
Configure a component using an arg from the commandline. This is
|
|
|
|
never called if global args like 'preset=' are found in the arg.
|
|
|
|
So simply check for any non-global args in your component and
|
|
|
|
call super().command() at the end to get a Help message.
|
2017-06-22 18:40:34 -04:00
|
|
|
'''
|
2017-07-23 01:53:54 -04:00
|
|
|
print(
|
|
|
|
self.__class__.name, 'Usage:\n'
|
|
|
|
'Open a preset for this component:\n'
|
|
|
|
' "preset=Preset Name"'
|
|
|
|
)
|
|
|
|
self.commandHelp()
|
|
|
|
quit(0)
|
2017-06-22 18:40:34 -04:00
|
|
|
|
2017-07-23 17:14:21 -04:00
|
|
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
|
|
|
# "Private" Methods
|
|
|
|
# =~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~=~==~=~=~=~=~=~=~=~=~=~=~=~=~=~
|
|
|
|
|
2017-07-27 17:49:08 -04:00
|
|
|
def trackWidgets(self, trackDict, **kwargs):
|
|
|
|
'''
|
|
|
|
Name widgets to track in update(), savePreset(), loadPreset(), and
|
|
|
|
command(). Requires a dict of attr names as keys, widgets as values
|
|
|
|
|
|
|
|
Optional args:
|
|
|
|
'presetNames': preset variable names to replace attr names
|
|
|
|
'commandArgs': arg keywords that differ from attr names
|
|
|
|
|
|
|
|
NOTE: Any kwarg key set to None will selectively disable tracking.
|
|
|
|
'''
|
|
|
|
self._trackedWidgets = trackDict
|
|
|
|
for kwarg in kwargs:
|
|
|
|
try:
|
|
|
|
if kwarg in ('presetNames', 'commandArgs'):
|
|
|
|
setattr(self, '_%s' % kwarg, kwargs[kwarg])
|
|
|
|
else:
|
|
|
|
raise ComponentError(
|
|
|
|
self, 'Nonsensical keywords to trackWidgets.')
|
|
|
|
except ComponentError:
|
|
|
|
continue
|
|
|
|
|
2017-07-23 17:14:21 -04:00
|
|
|
def lockProperties(self, propList):
|
|
|
|
self._lockedProperties = propList
|
|
|
|
|
|
|
|
def lockError(self, msg):
|
|
|
|
self._lockedError = msg
|
|
|
|
|
|
|
|
def unlockProperties(self):
|
|
|
|
self._lockedProperties = None
|
|
|
|
|
|
|
|
def unlockError(self):
|
|
|
|
self._lockedError = None
|
|
|
|
|
2017-06-24 23:12:41 -04:00
|
|
|
def loadUi(self, filename):
|
2017-07-20 20:31:38 -04:00
|
|
|
'''Load a Qt Designer ui file to use for this component's widget'''
|
2017-07-23 01:53:54 -04:00
|
|
|
return uic.loadUi(os.path.join(self.core.componentsPath, filename))
|
2017-07-20 20:31:38 -04:00
|
|
|
|
2017-07-27 17:49:08 -04:00
|
|
|
@property
|
|
|
|
def width(self):
|
|
|
|
return int(self.settings.value('outputWidth'))
|
|
|
|
|
|
|
|
@property
|
|
|
|
def height(self):
|
|
|
|
return int(self.settings.value('outputHeight'))
|
|
|
|
|
2017-07-20 20:31:38 -04:00
|
|
|
def cancel(self):
|
|
|
|
'''Stop any lengthy process in response to this variable.'''
|
|
|
|
self.canceled = True
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
self.canceled = False
|
2017-07-23 17:14:21 -04:00
|
|
|
self.unlockProperties()
|
|
|
|
self.unlockError()
|
2017-06-24 23:12:41 -04:00
|
|
|
|
2017-06-23 23:00:24 -04:00
|
|
|
|
2017-07-25 22:02:47 -04:00
|
|
|
class ComponentError(RuntimeError):
|
|
|
|
'''Gives the MainWindow a traceback to display, and cancels the export.'''
|
2017-07-25 17:44:59 -04:00
|
|
|
|
2017-07-25 22:02:47 -04:00
|
|
|
prevErrors = []
|
2017-07-27 17:49:08 -04:00
|
|
|
lastTime = time.time()
|
2017-07-25 17:44:59 -04:00
|
|
|
|
2017-07-25 22:02:47 -04:00
|
|
|
def __init__(self, caller, name):
|
2017-07-27 17:49:08 -04:00
|
|
|
print('##### ComponentError by %s: %s' % (caller.name, name))
|
2017-07-25 22:02:47 -04:00
|
|
|
if len(ComponentError.prevErrors) > 1:
|
|
|
|
ComponentError.prevErrors.pop()
|
|
|
|
ComponentError.prevErrors.insert(0, name)
|
2017-07-27 17:49:08 -04:00
|
|
|
curTime = time.time()
|
|
|
|
if name in ComponentError.prevErrors[1:] \
|
|
|
|
and curTime - ComponentError.lastTime < 0.2:
|
|
|
|
# Don't create multiple windows for quickly repeated messages
|
2017-07-25 17:44:59 -04:00
|
|
|
return
|
2017-07-27 17:49:08 -04:00
|
|
|
ComponentError.lastTime = time.time()
|
2017-07-25 17:44:59 -04:00
|
|
|
|
2017-07-23 17:14:21 -04:00
|
|
|
from toolkit import formatTraceback
|
|
|
|
import sys
|
|
|
|
if sys.exc_info()[0] is not None:
|
|
|
|
string = (
|
|
|
|
"%s component's %s encountered %s %s." % (
|
|
|
|
caller.__class__.name,
|
|
|
|
name,
|
|
|
|
'an' if any([
|
|
|
|
sys.exc_info()[0].__name__.startswith(vowel)
|
|
|
|
for vowel in ('A', 'I')
|
|
|
|
]) else 'a',
|
|
|
|
sys.exc_info()[0].__name__,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
detail = formatTraceback(sys.exc_info()[2])
|
|
|
|
else:
|
|
|
|
string = name
|
|
|
|
detail = "Methods:\n%s" % (
|
|
|
|
"\n".join(
|
|
|
|
[m for m in dir(caller) if not m.startswith('_')]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2017-07-27 17:49:08 -04:00
|
|
|
super().__init__(string)
|
2017-07-25 22:02:47 -04:00
|
|
|
caller._error.emit(string, detail)
|