workcraft/workcraft

View on GitHub
workcraft/WorkcraftCore/src/org/workcraft/workspace/WorkspaceEntry.java

Summary

Maintainability
A
2 hrs
Test Coverage
package org.workcraft.workspace;

import org.workcraft.Framework;
import org.workcraft.dom.Container;
import org.workcraft.dom.Node;
import org.workcraft.dom.visual.VisualModel;
import org.workcraft.dom.visual.VisualModelTransformer;
import org.workcraft.dom.visual.VisualNode;
import org.workcraft.dom.visual.VisualTransformableNode;
import org.workcraft.exceptions.DeserialisationException;
import org.workcraft.gui.MainWindow;
import org.workcraft.gui.MainWindowActions;
import org.workcraft.gui.workspace.Path;
import org.workcraft.observation.*;
import org.workcraft.plugins.builtin.settings.EditorCommonSettings;
import org.workcraft.utils.DialogUtils;
import org.workcraft.utils.Hierarchy;
import org.workcraft.utils.WorkUtils;

import java.awt.geom.Point2D;
import java.util.*;

public class WorkspaceEntry implements ObservableState {

    private static final Point2D DEFAULT_PASTE_OFFSET = new Point2D.Double(1.0, 1.0);

    private final Map<String, Resource> resources = new HashMap<>();
    private ModelEntry modelEntry = null;
    private boolean changed = true;

    private boolean canSelect = true;
    private boolean canModify = true;
    private boolean canCopy = true;

    private final MementoManager history = new MementoManager();
    private Resource capturedMemento = null;
    private Resource savedMemento = null;

    private VisualNode templateNode = null;
    private VisualNode defaultNode = null;

    private Point2D pastePosition = null;
    private String details = null;
    private boolean temporary = false;

    public void setChanged(boolean changed) {
        if (this.changed != changed) {
            this.changed = changed;
            if (!changed) {
                savedMemento = null;
            }
            Framework framework = Framework.getInstance();
            Workspace workspace = framework.getWorkspace();
            workspace.fireEntryChanged(this);
            MainWindow mainWindow = framework.getMainWindow();
            if (mainWindow != null) {
                mainWindow.refreshWorkspaceEntryTitle(this);
            }
        }
    }

    public boolean isChanged() {
        return changed;
    }

    public void setTemporary(boolean temporary) {
        this.temporary = temporary;
    }

    public ModelEntry getModelEntry() {
        return modelEntry;
    }

    public Collection<Resource> getResources() {
        return new ArrayList<>(resources.values());
    }

    public Resource addResource(Resource resource) {
        return resources.put(resource.getName(), resource);
    }

    public Resource getResource(String name) {
        return resources.get(name);
    }

    public Resource removeResource(String name) {
        return resources.remove(name);
    }

    private final StateObserver modelObserver = new StateObserver() {
        @Override
        public void notify(StateEvent e) {
            if (e instanceof ModelModifiedEvent) {
                setChanged(true);
            }
            observableState.sendNotification(e);
        }
    };

    public void setModelEntry(ModelEntry modelEntry) {
        if (this.modelEntry != null) {
            if (this.modelEntry.isVisual()) {
                this.modelEntry.getVisualModel().removeObserver(modelObserver);
            }
        }
        this.modelEntry = modelEntry;

        observableState.sendNotification(new StateEvent() {
            @Override
            public Object getSender() {
                return this;
            }
        });

        if (this.modelEntry.isVisual()) {
            this.modelEntry.getVisualModel().addObserver(modelObserver);
        }
    }

    public String getModelTitle() {
        return getModelEntry().getModel().getTitle();
    }

    public String getTitle() {
        Path<String> workspacePath = getWorkspacePath();
        if (workspacePath == null) {
            return null;
        }
        String node = workspacePath.getNode();
        int extensionIndex = node.lastIndexOf(FileFilters.DOCUMENT_EXTENSION);
        if (extensionIndex < 0) {
            return node;
        }
        return node.substring(0, extensionIndex);
    }

    public void setDetails(String value) {
        details = value;
    }

    public void clearDetails() {
        setDetails(null);
    }

    public String getDetails() {
        return details == null ? "" : " : " + details;
    }

    public String getTitleAndModelType() {
        return getTitle() + getModelTypeSuffix();
    }

    public String getHtmlDetailedTitle() {
        String prefix = isChanged() ? "*" : "";
        String suffix = getModelTypeSuffix();
        String formattedTitle = temporary ? "<i>" + getTitle() + "</i>" : getTitle();
        return "<html>" + prefix + formattedTitle + getDetails() + suffix + "</html>";
    }

    private String getModelTypeSuffix() {
        String suffix = "";
        VisualModel model = getModelEntry().getVisualModel();
        if (model != null) {
            switch (EditorCommonSettings.getTitleStyle()) {
            case LONG:
                suffix = " - " + model.getDisplayName();
                break;
            case SHORT:
                suffix = " [" + model.getShortName() + "]";
                break;
            default:
                suffix = "";
                break;
            }
        }
        return suffix;
    }

    public String getFileName() {
        String fileName = getTitle();
        if ((fileName == null) || fileName.isEmpty()) {
            fileName = "Untitled";
        }
        return fileName
                .replace('\\', '_')
                .replace('/', '_')
                .replace(':', '_')
                .replace('"', '_')
                .replace('<', '_')
                .replace('>', '_')
                .replace('|', '_');
    }

    public Path<String> getWorkspacePath() {
        return Framework.getInstance().getWorkspace().getPath(this);
    }

    private final ObservableStateImpl observableState = new ObservableStateImpl();

    @Override
    public void addObserver(StateObserver obs) {
        observableState.addObserver(obs);
    }

    @Override
    public void removeObserver(StateObserver obs) {
        observableState.removeObserver(obs);
    }

    @Override
    public void sendNotification(StateEvent e) {
        observableState.sendNotification(e);
    }

    public void updateActionState() {
        MainWindowActions.MERGE_WORK_ACTION.setEnabled(canModify);
        MainWindowActions.EDIT_UNDO_ACTION.setEnabled(canModify && history.canUndo());
        MainWindowActions.EDIT_REDO_ACTION.setEnabled(canModify && history.canRedo());
        MainWindowActions.EDIT_CUT_ACTION.setEnabled(canModify && canSelect && canCopy);
        MainWindowActions.EDIT_COPY_ACTION.setEnabled(canModify && canSelect && canCopy);
        MainWindowActions.EDIT_PASTE_ACTION.setEnabled(canModify && canSelect && canCopy);
        MainWindowActions.EDIT_DELETE_ACTION.setEnabled(canModify && canSelect);
        MainWindowActions.EDIT_SELECT_ALL_ACTION.setEnabled(canModify && canSelect);
        MainWindowActions.EDIT_SELECT_INVERSE_ACTION.setEnabled(canModify && canSelect);
        MainWindowActions.EDIT_SELECT_NONE_ACTION.setEnabled(canModify && canSelect);
        MainWindow mainWindow = Framework.getInstance().getMainWindow();
        if (mainWindow != null) {
            mainWindow.updateMainMenuState(canModify);
        }
    }

    public void setCanModify(boolean canModify) {
        this.canModify = canModify;
        updateActionState();
    }

    public void setCanSelect(boolean canSelect) {
        this.canSelect = canSelect;
        updateActionState();
    }

    public void setCanCopy(boolean canCopy) {
        this.canCopy = canCopy;
        updateActionState();
    }

    public void captureMemento() {
        capturedMemento = WorkUtils.mementoModel(modelEntry);
        if (!changed) {
            savedMemento = capturedMemento;
        }
    }

    public void uncaptureMemento() {
        capturedMemento = null;
    }

    public void cancelMemento() {
        if (capturedMemento != null) {
            setModelEntry(WorkUtils.loadModel(capturedMemento));
            setChanged(savedMemento != capturedMemento);
            capturedMemento = null;
        }
    }

    public void saveMemento() {
        Resource currentMemento = capturedMemento;
        capturedMemento = null;
        if (currentMemento == null) {
            currentMemento = WorkUtils.mementoModel(modelEntry);
        }
        if (!changed) {
            savedMemento = currentMemento;
        }
        history.pushUndo(currentMemento);
        history.clearRedo();
        updateActionState();
    }

    public void undo() {
        if (history.canUndo()) {
            Resource undoMemento = history.pullUndo();
            if (undoMemento != null) {
                Resource currentMemento = WorkUtils.mementoModel(modelEntry);
                if (!changed) {
                    savedMemento = currentMemento;
                }
                history.pushRedo(currentMemento);
                setModelEntry(WorkUtils.loadModel(undoMemento));
                setChanged(undoMemento != savedMemento);
            }
        }
        updateActionState();
    }

    public void redo() {
        if (history.canRedo()) {
            Resource redoMemento = history.pullRedo();
            if (redoMemento != null) {
                Resource currentMemento = WorkUtils.mementoModel(modelEntry);
                if (!changed) {
                    savedMemento = currentMemento;
                }
                history.pushUndo(currentMemento);
                setModelEntry(WorkUtils.loadModel(redoMemento));
                setChanged(redoMemento != savedMemento);
            }
        }
        updateActionState();
    }

    public void insert(ModelEntry me) {
        try {
            Resource currentMemento = WorkUtils.mementoModel(modelEntry);
            Resource insertMemento = WorkUtils.mementoModel(me);
            ModelEntry result = WorkUtils.loadModel(currentMemento.toStream(), insertMemento.toStream());
            saveMemento();
            setModelEntry(result);
            setChanged(true);
        } catch (DeserialisationException e) {
            DialogUtils.showError(e.getMessage());
        }
    }

    public void copy() {
        VisualModel model = modelEntry.getVisualModel();
        if (model.getSelection().size() > 0) {
            captureMemento();
            // Remember the current level, selected nodes, and then jump to the root level.
            Container currentLevel = model.getCurrentLevel();
            Collection<VisualNode> selectedNodes = new HashSet<>(model.getSelection());
            model.setCurrentLevel(model.getRoot());
            // Starting from the root, delete irrelevant containers and ungroup the containers of the selected nodes.
            for (Node container : Hierarchy.getPath(currentLevel)) {
                if ((container != model.getRoot()) && (container instanceof VisualNode)) {
                    model.select((VisualNode) container);
                    model.selectInverse();
                    model.deleteSelection();
                    model.select((VisualNode) container);
                    model.ungroupSelection();
                }
            }
            // Now the selected nodes should be at the root level; delete everything except the selected nodes.
            model.select(selectedNodes);
            model.selectInverse();
            model.deleteSelection();
            // Save the remaining nodes to clipboard.
            final Framework framework = Framework.getInstance();
            framework.clipboard = WorkUtils.mementoModel(modelEntry);
            cancelMemento();
        }
    }

    public void cut() {
        copy();
        delete();
    }

    public void paste() {
        final Framework framework = Framework.getInstance();
        if (framework.clipboard != null) {
            try {
                Resource memento = WorkUtils.mementoModel(modelEntry);
                ModelEntry me = WorkUtils.loadModel(memento.toStream(), framework.clipboard.toStream());

                VisualModel model = me.getVisualModel();
                Point2D offset = getPasteOffset(model);
                if (offset != null) {
                    VisualModelTransformer.translateSelection(model, offset.getX(), offset.getY());
                }

                model.afterPaste();
                saveMemento();
                setModelEntry(me);
                setChanged(true);
            } catch (DeserialisationException e) {
                DialogUtils.showError(e.getMessage());
            }
        }
    }

    private Point2D getPasteOffset(VisualModel model) {
        Point2D result = null;
        if (pastePosition != null) {
            Point2D pos = getSelectionPivotPosition(model);
            if (pos != null) {
                double dx = pastePosition.getX() - pos.getX();
                double dy = pastePosition.getY() - pos.getY();
                result = new Point2D.Double(dx, dy);
            }
        }
        if (result == null) {
            result = DEFAULT_PASTE_OFFSET;
        }
        return result;
    }

    private Point2D getSelectionPivotPosition(VisualModel model) {
        Point2D result = null;
        for (VisualNode node : model.getSelection()) {
            if (node instanceof VisualTransformableNode) {
                Point2D pos = ((VisualTransformableNode) node).getRootSpacePosition();
                if ((result == null) || (result.getY() > pos.getY())) {
                    result = pos;
                }
            }
        }
        return result;
    }

    public void delete() {
        VisualModel model = modelEntry.getVisualModel();
        if (model.getSelection().size() > 0) {
            saveMemento();
            model.deleteSelection();
            setChanged(true);
        }
    }

    public void setTemplateNode(VisualNode value) {
        if (templateNode != value) {
            templateNode = value;
            VisualModel visualModel = getModelEntry().getVisualModel();
            visualModel.sendNotification(new SelectionChangedEvent(visualModel, null));
        }
    }

    public VisualNode getTemplateNode() {
        return templateNode;
    }

    public void setDefaultNode(VisualNode node) {
        defaultNode = node;
    }

    public VisualNode getDefaultNode() {
        return defaultNode;
    }

    public void setPastePosition(Point2D pastePosition) {
        this.pastePosition = pastePosition;
    }

}