import uuid from copy import deepcopy from collections import defaultdict from .pyside import QtCore from .shape import Shape from .templates import PICKER from .undo import UndoManager from .stack import count_panels class PickerDocument(QtCore.QObject): shapes_changed = QtCore.Signal() # origin: str ["editor"|"picker"], key: str general_option_changed = QtCore.Signal(str, str) data_changed = QtCore.Signal() changed = QtCore.Signal() def __init__(self, data): super(PickerDocument, self).__init__() self.data = data self.filename = None self.modified_state = False self.undo_manager = UndoManager(self.data) self.shapes = [] self.shapes_by_panel = {} self.shapes_by_id = {} self.shapes_by_layer = {} self.generate_shapes() self.shapes_changed.connect(self.emit_change) self.general_option_changed.connect(self.emit_change) self.data_changed.connect(self.emit_change) self.shapes_changed.connect(self.emit_change) def emit_change(self, *_): """ Signal allways emitted when any data of the model changed. """ self.changed.emit() @staticmethod def create(): data = { 'general': deepcopy(PICKER), 'shapes': []} return PickerDocument(data) def record_undo(self): self.undo_manager.set_data_modified(self.data) self.modified_state = True def undo(self): if self.undo_manager.undo(): self.data = self.undo_manager.data self.generate_shapes() self.data_changed.emit() self.modified_state = True def redo(self): if self.undo_manager.redo(): self.data = self.undo_manager.data self.generate_shapes() self.data_changed.emit() self.modified_state = True def panel_count(self): return count_panels(self.data['general']['panels']) def set_shapes_data(self, data): self.data['shapes'] = data self.generate_shapes() def generate_shapes(self): self.shapes = [Shape(options) for options in self.data['shapes']] self.sync_shapes_caches() def sync_shapes_caches(self): self.shapes_by_panel = defaultdict(list) self.shapes_by_id = {} self.shapes_by_layer = defaultdict(list) for shape in self.shapes: self.shapes_by_panel[shape.options['panel']].append(shape) self.shapes_by_id[shape.options['id']] = shape layer = shape.options['visibility_layer'] if layer: self.shapes_by_layer[layer].append(shape) def add_shapes(self, shapes_data, prepend=False, hierarchize=False): for options in shapes_data: options['id'] = str(uuid.uuid4()) options['children'] = [] shapes = [] parent_shape = None for options in shapes_data: shape = Shape(options) shapes.append(shape) if parent_shape and hierarchize: parent_shape.options['children'].append(shape.options['id']) parent_shape = shape if prepend: for shape in reversed(shapes): self.shapes.insert(0, shape) self.data['shapes'].insert(0, shape.options) else: self.shapes.extend(shapes) self.data['shapes'].extend(shapes_data) self.sync_shapes_caches() return shapes def remove_shapes(self, shapes): removed_ids = [shape.options['id'] for shape in shapes] self.data['shapes'] = [ s for s in self.data['shapes'] if s['id'] not in removed_ids] self.generate_shapes() def all_children(self, id_): if id_ not in self.shapes_by_id: return [] shape = self.shapes_by_id[id_] result = [] to_visit = [id_] visited = set() while to_visit: current_id = to_visit.pop(0) if current_id in visited: continue visited.add(current_id) shape = self.shapes_by_id.get(current_id) if shape: result.append(shape) children = shape.options.get('children', []) to_visit.extend(c for c in children if c not in visited) return result