Add missing files, make PropertyEditor scrollable (closes #32)

This commit is contained in:
Mika Fischer
2011-06-28 11:17:32 +02:00
parent 20ad715411
commit 9feed46729
2 changed files with 261 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
from sloth.core.exceptions import ImproperlyConfigured
from sloth.annotations.model import AnnotationModelItem
from sloth.gui.floatinglayout import FloatingLayout
from sloth.utils.bind import bind
import sys
from PyQt4.QtCore import pyqtSignal, QSize, Qt
from PyQt4.QtGui import QApplication, QWidget, QGroupBox, QVBoxLayout, QPushButton, QButtonGroup, QScrollArea
import logging
LOG = logging.getLogger(__name__)
# This is really really ugly, but the QDockWidget for some reason does not notice when
# its child widget becomes smaller...
# Therefore we manually set its minimum size when our own minimum size changes
class MyVBoxLayout(QVBoxLayout):
def __init__(self, parent=None):
QVBoxLayout.__init__(self, parent)
self._last_size = QSize(0, 0)
def setGeometry(self, r):
QVBoxLayout.setGeometry(self, r)
try:
wid = self.parentWidget().parentWidget()
new_size = self.minimumSize()
if new_size == self._last_size: return
self._last_size = new_size
twid = wid.titleBarWidget()
if twid is not None:
theight = twid.sizeHint().height()
else:
theight = 0
new_size += QSize(0, theight)
wid.setMinimumSize(new_size)
except Exception:
pass
class LabelEditor(QScrollArea):
def __init__(self, items, parent=None):
QScrollArea.__init__(self, parent)
self._content = QWidget()
self._editor = parent
self._items = items
# Find all classes
self._label_classes = set([item.get('class', item['type']) for item in items])
n_classes = len(self._label_classes)
LOG.debug("Creating editor for %d item classes: %s" % (n_classes, ", ".join(list(self._label_classes))))
# Widget layout
self._layout = QVBoxLayout()
self._content.setLayout(self._layout)
self._boxes = {}
self._layouts = {}
self._buttons = {}
if n_classes == 0:
pass
elif n_classes == 1:
# Just display all properties
lc = self._label_classes.copy().pop()
for attr in self._editor.getLabelClassAttributes(lc):
if attr == 'class' or attr == 'type': continue
self.addAttributeEditor(item, lc, attr, self._editor.getLabelClassAttributeChoices(lc, attr))
else:
# TODO
# Find common properties of all classes
properties = None
for c in self._label_classes:
if properties is None:
properties = set(self._editor.getLabelClassAttributes(c))
else:
properties &= set(self._editor.getLabelClassAttributes(c))
# TODO: Remove properties with per-class value lists if more than one class selected
# TODO: Order this somehow
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setWidgetResizable(True)
self.setWidget(self._content)
def sizeHint(self):
minsz = self.minimumSize()
sz = self._layout.minimumSize()
left, top, right, bottom = self.getContentsMargins()
return QSize(max(minsz.width(), sz.width() + left + right), max(minsz.height(), sz.height() + top + bottom))
def onButtonClicked(self, attr, val):
LOG.debug("Button %s: %s clicked" % (attr, val))
button = self._buttons[attr][val]
# Unpress all other buttons
for v, but in self._buttons[attr].items():
if but is not button:
but.setChecked(False)
# Update model item
for item in self._items:
if button.isChecked():
item[attr] = val
else:
item[attr] = None
def addAttributeEditor(self, item, lc, attr, vals):
box = QGroupBox(attr, self)
layout = FloatingLayout()
box.setLayout(layout)
self._boxes[attr] = box
self._layouts[attr] = layout
self._buttons[attr] = {}
for v in vals:
button = QPushButton(v, box)
button.setFlat(True)
button.setCheckable(True)
button.setChecked(attr in item and item[attr] == v)
self._buttons[attr][v] = button
layout.addWidget(button)
button.clicked.connect(bind(self.onButtonClicked, attr, v))
self._layout.addWidget(box)
def labelClasses(self):
return self._label_classes
def currentProperties(self):
if len(self._items) > 1:
return {}
else:
return self._items[0]
class PropertyEditor(QWidget):
# Signals
insertionModeStarted = pyqtSignal(str)
insertionModeEnded = pyqtSignal()
insertionPropertiesChanged = pyqtSignal(object)
editPropertiesChanged = pyqtSignal(object)
def __init__(self, config, parent=None):
QWidget.__init__(self, parent)
self._class_config = {}
self._class_items = {}
self._setupGUI()
# Add label classes from config
for label in config:
self.addLabelClass(label)
def addLabelClass(self, label_config):
# Check label configuration
if 'attributes' not in label_config:
raise ImproperlyConfigured("Label with no 'attributes' dict found")
attrs = label_config['attributes']
if 'type' not in attrs:
raise ImproperlyConfigured("Labels must have an attribute 'type'")
# TODO: Maybe don't do this?
if 'class' not in attrs:
attrs['class'] = attrs['type']
label_class = attrs['class']
if label_class in self._class_config:
raise ImproperlyConfigured("Label with class '%s' defined more than once" % label_class)
# Store config
# TODO: Handle special properties
self._class_config[label_class] = label_config
# Add dummy item for insertion
# TODO: Put stuff into dict first
self._class_items[label_class] = AnnotationModelItem(label_config['attributes'])
# Add label class button
button = QPushButton(label_class, self)
button.setCheckable(True)
button.setFlat(True)
button.clicked.connect(self.onClassButtonPressed)
self._class_buttons[label_class] = button
self._classbox_layout.addWidget(button)
def getLabelClassAttributes(self, label_class):
return self._class_config[label_class]['attributes']
def getLabelClassAttributeChoices(self, label_class, attribute):
return self._class_config[label_class]['attributes'][attribute]
def onClassButtonPressed(self):
if self.sender().isChecked():
self.startInsertionMode(str(self.sender().text()))
else:
self.endInsertionMode()
def startInsertionMode(self, label_class):
self.endInsertionMode(False)
for lc, button in self._class_buttons.items():
button.setChecked(lc == label_class)
LOG.debug("Starting insertion mode for %s" % label_class)
self._label_editor = LabelEditor([self._class_items[label_class]], self)
self._layout.insertWidget(1, self._label_editor, 0)
self.insertionModeStarted.emit(label_class)
def endInsertionMode(self, uncheck_buttons=True):
if self._label_editor is not None:
LOG.debug("Ending insertion mode")
self._label_editor.hide()
self._layout.removeWidget(self._label_editor)
self._label_editor = None
self.uncheckAllButtons()
self.insertionModeEnded.emit()
def uncheckAllButtons(self):
for lc, button in self._class_buttons.items():
button.setChecked(False)
def markEditButtons(self, label_classes):
for lc, button in self._class_buttons.items():
button.setFlat(lc not in label_classes)
def currentEditorProperties(self):
if self._label_editor is None:
return None
else:
return self._label_editor.currentProperties()
def startEditMode(self, model_items):
self.endInsertionMode()
LOG.debug("Starting edit mode for items: %s" % model_items)
self._label_editor = LabelEditor(model_items, self)
self.markEditButtons(self._label_editor.labelClasses())
self._layout.insertWidget(1, self._label_editor, 0)
def _setupGUI(self):
self._class_buttons = {}
self._label_editor = None
# Label class buttons
self._classbox = QGroupBox("Labels", self)
self._classbox_layout = FloatingLayout()
self._classbox.setLayout(self._classbox_layout)
# Global widget
self._layout = MyVBoxLayout()
self.setLayout(self._layout)
self._layout.addWidget(self._classbox, 0)
self._layout.addStretch(1)
def main():
from sloth.conf import config
config.update("/home/mfischer/videmo_config_new_simple")
app = QApplication(sys.argv)
ba = PropertyEditor(config.LABELS)
ba.show()
return app.exec_()
if __name__ == '__main__':
sys.exit(main())
+2
View File
@@ -0,0 +1,2 @@
def bind(fun, *args):
return lambda: fun(*args)