/* * @(#)AbstractAttributeEditorHandler.java * * Copyright (c) 2009-2010 The authors and contributors of JHotDraw. * * You may not use, copy or modify this file, except in compliance with the * accompanying license terms. */ package org.jhotdraw.draw.event; import javax.annotation.Nullable; import org.jhotdraw.gui.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.Map; import java.util.Set; import javax.swing.undo.AbstractUndoableEdit; import javax.swing.undo.CannotRedoException; import javax.swing.undo.UndoableEdit; import org.jhotdraw.app.Disposable; import org.jhotdraw.beans.WeakPropertyChangeListener; import org.jhotdraw.draw.AttributeKey; import org.jhotdraw.draw.DrawingEditor; import org.jhotdraw.draw.DrawingView; import org.jhotdraw.draw.Figure; /** * AbstractAttributeEditorHandler mediates between an AttributeEditor and the * currently selected Figure's in a DrawingEditor. * * <hr> * <b>Design Patterns</b> * * <p><em>Observer</em><br> * Selection changes of {@code DrawingView} are observed by user interface * components:<br> * Subject: {@link org.jhotdraw.draw.DrawingView}; Observer: {@link FigureSelectionListener}; * Concrete-Observer: {@link AbstractAttributeEditorHandler}, * {@link SelectionComponentDisplayer}, {@link SelectionComponentRepainter}. * <hr> * * @author Werner Randelshofer * @version $Id$ */ public abstract class AbstractAttributeEditorHandler<T> implements Disposable { @Nullable protected DrawingEditor editor; @Nullable protected DrawingView view; @Nullable protected DrawingView activeView; protected AttributeEditor<T> attributeEditor; protected AttributeKey<T> attributeKey; protected int updateDepth; @Nullable protected LinkedList<Object> attributeRestoreData = new LinkedList<>(); protected Map<AttributeKey<?>, Object> defaultAttributes; /** * If this variable is put to true, the attribute editor updates the * default values of the drawing editor. */ private boolean isUpdateDrawingEditorDefaults; /** * To this figures we have registered the EventHandler as FigureListener * and as PropertyChangeListener. */ private Set<Figure> figuresOfInterest; protected class EventHandler extends FigureAdapter implements FigureSelectionListener, PropertyChangeListener { @Override public void selectionChanged(FigureSelectionEvent evt) { attributeRestoreData = null; if (figuresOfInterest != null) { for (Figure f : figuresOfInterest) { f.removeFigureListener(this); } } figuresOfInterest = getEditedFigures(); for (Figure f : figuresOfInterest) { f.addFigureListener(this); } updateAttributeEditor(); } @Override public void propertyChange(PropertyChangeEvent evt) { Object src = evt.getSource(); String name = evt.getPropertyName(); if (src == editor && name == DrawingEditor.ACTIVE_VIEW_PROPERTY) { updateActiveView(); } else if (src == editor && name.equals(DrawingEditor.DEFAULT_ATTRIBUTE_PROPERTY_PREFIX+attributeKey.getKey())) { updateAttributeEditor(); } else if (src == attributeEditor && name == AttributeEditor.ATTRIBUTE_VALUE_PROPERTY) { updateFigures(); } else if (src == activeView && name == DrawingView.DRAWING_PROPERTY) { updateActiveView(); } else if (figuresOfInterest != null && figuresOfInterest.contains(src)) { updateFigures(); } } @Override public void attributeChanged(FigureEvent e) { if (e.getAttribute() == attributeKey) { updateAttributeEditor(); } } } private EventHandler eventHandler; private static class UndoableAttributeEdit<T> extends AbstractUndoableEdit { private static final long serialVersionUID = 1L; private Set<Figure> editedFigures; private AttributeKey<T> attributeKey; private T editRedoValue; protected LinkedList<Object> editUndoData; public UndoableAttributeEdit(Set<Figure> editedFigures, AttributeKey<T> attributeKey, T editRedoValue, LinkedList<Object> editUndoData) { this.editedFigures = editedFigures; this.attributeKey = attributeKey; this.editRedoValue = editRedoValue; this.editUndoData = editUndoData; } @Override public String getPresentationName() { return attributeKey.getPresentationName(); } @Override public void undo() throws CannotRedoException { super.undo(); Iterator<Object> di = editUndoData.iterator(); for (Figure f : editedFigures) { f.willChange(); f.restoreAttributesTo(di.next()); f.changed(); } } @Override public void redo() throws CannotRedoException { super.redo(); for (Figure f : editedFigures) { f.set(attributeKey, editRedoValue); } } @Override public boolean replaceEdit(UndoableEdit anEdit) { if (anEdit instanceof UndoableAttributeEdit) { return ((UndoableAttributeEdit) anEdit).editUndoData == this.editUndoData; } return false; } } public AbstractAttributeEditorHandler(AttributeKey<T> key, AttributeEditor<T> attributeEditor, @Nullable DrawingEditor drawingEditor) { this(key, attributeEditor, drawingEditor, true); } public AbstractAttributeEditorHandler(AttributeKey<T> key, AttributeEditor<T> attributeEditor, @Nullable DrawingEditor drawingEditor, boolean updateDrawingEditorDefaults) { this(key, null, attributeEditor, drawingEditor, updateDrawingEditorDefaults); } @SuppressWarnings("unchecked") public AbstractAttributeEditorHandler(AttributeKey<T> key, @Nullable Map<AttributeKey<?>, Object> defaultAttributes, AttributeEditor<T> attributeEditor, @Nullable DrawingEditor drawingEditor, boolean updateDrawingEditorDefaults) { eventHandler = new EventHandler(); this.defaultAttributes = (Map<AttributeKey<?>, Object>) ((defaultAttributes == null) ? Collections.emptyMap() : defaultAttributes); attributeEditor.setAttributeValue(key.getDefaultValue()); setAttributeKey(key); setAttributeEditor(attributeEditor); setEditor(drawingEditor); isUpdateDrawingEditorDefaults = updateDrawingEditorDefaults; } /** * Attaches the FigureAttributeEditorHandler to the specified DrawingEditor. * <p> * The FigureAttributeEditorHandler listens to view changes and selection * changes of the drawing editor and calls setEnabled(boolean) and * updateField(Set<Figure>) on the field accordingly. * * @param newValue a drawing editor. */ public void setEditor(@Nullable DrawingEditor newValue) { DrawingEditor oldValue = editor; if (editor != null) { editor.removePropertyChangeListener(eventHandler); } this.editor = newValue; if (editor != null) { editor.addPropertyChangeListener(new WeakPropertyChangeListener(eventHandler)); } updateActiveView(); } /** * Returns the DrawingEditor to which this FigureAttributeEditorHandler is * attached. */ @Nullable public DrawingEditor getEditor() { return editor; } /** * Attaches the FigureAttributeEditorHandler to the specified DrawingView. * <p> * If a non-null value is provided, the FigureAttributeEditorHandler listens only * to selection changes of the specified view. If a null value is provided, * the FigureAttributeEditorHandler listens to all views of the drawing editor. * * @param newValue a drawing view. */ public void setView(@Nullable DrawingView newValue) { this.view = newValue; updateActiveView(); } /** * Returns the DrawingView to which this FigureAttributeEditorHandler is * attached. Returns null, if the FigureAttributeEditorHandler is attached * to all views of the DrawingEditor. */ @Nullable public DrawingView getView() { return view; } /** * Set this to true if you want the attribute editor to update the * default values of the drawing editor. * * @param newValue */ public void setUpdateDrawingEditorDefaults(boolean newValue) { isUpdateDrawingEditorDefaults = newValue; } /** * Returns true if the attribute editor updates the * default values of the drawing editor. */ public boolean isUpdateDrawingEditorDefaults() { return isUpdateDrawingEditorDefaults; } @Nullable protected DrawingView getActiveView() { if (getView() != null) { return getView(); } else { return editor.getActiveView(); } } /** * Attaches the FigureAttributeEditorHandler to the specified AttributeEditor. */ public void setAttributeEditor(AttributeEditor<T> newValue) { if (attributeEditor != null) { attributeEditor.removePropertyChangeListener(eventHandler); } this.attributeEditor = newValue; if (attributeEditor != null) { attributeEditor.addPropertyChangeListener(eventHandler); } } /** * Returns the AttributeEditor to which this FigureAttributeEditorHandler is * attached. */ public AttributeEditor<T> getAttributeEditor() { return attributeEditor; } public AttributeKey<T> getAttributeKey() { return attributeKey; } public void setAttributeKey(AttributeKey<T> newValue) { attributeKey = newValue; } protected void updateActiveView() { DrawingView newValue = (view != null) ? // view : // ((editor != null && editor.getActiveView() != null) ? // editor.getActiveView() : null); DrawingView oldValue = activeView; if (activeView != null) { activeView.removePropertyChangeListener(eventHandler); activeView.removeFigureSelectionListener(eventHandler); if (figuresOfInterest != null) { for (Figure f : figuresOfInterest) { f.removeFigureListener(eventHandler); } } } activeView = newValue; if (activeView != null) { activeView.addPropertyChangeListener(eventHandler); activeView.addFigureSelectionListener(eventHandler); figuresOfInterest = getEditedFigures(); for (Figure f : figuresOfInterest) { f.addFigureListener(eventHandler); } } attributeRestoreData = null; updateAttributeEditor(); } protected abstract Set<Figure> getEditedFigures(); protected void updateAttributeEditor() { if (updateDepth++ == 0) { Set<Figure> figures = getEditedFigures(); if (editor==null) { attributeEditor.getComponent().setEnabled(false); } else if (activeView == null || figures.isEmpty()) { attributeEditor.getComponent().setEnabled(true); T value = editor.getDefaultAttribute(attributeKey); attributeEditor.setAttributeValue(value); attributeEditor.setMultipleValues(false); } else { attributeEditor.getComponent().setEnabled(true); T value = figures.iterator().next().get(attributeKey); boolean isMultiple = false; for (Figure f : figures) { T v = f.get(attributeKey); if ((v == null || value == null) && v != value || // v != null && value != null && !v.equals(value)) { isMultiple = true; break; } } attributeEditor.setAttributeValue(value); attributeEditor.setMultipleValues(isMultiple); } } updateDepth--; } @SuppressWarnings("unchecked") protected void updateFigures() { if (updateDepth++ == 0) { Set<Figure> figures = getEditedFigures(); if (activeView == null || figures.isEmpty()) { } else { T value = attributeEditor.getAttributeValue(); if (attributeRestoreData == null) { attributeRestoreData = new LinkedList<>(); for (Figure f : figures) { attributeRestoreData.add(f.getAttributesRestoreData()); } } for (Figure f : figures) { f.willChange(); f.set(attributeKey, value); for (Map.Entry<AttributeKey<?>, Object> entry : defaultAttributes.entrySet()) { f.set((AttributeKey<Object>)entry.getKey(), entry.getValue()); } f.changed(); } if (editor != null && isUpdateDrawingEditorDefaults) { editor.setDefaultAttribute(attributeKey, value); } getActiveView().getDrawing().fireUndoableEditHappened(// new UndoableAttributeEdit<>(new HashSet<>(figures), attributeKey, value, attributeRestoreData)// ); if (!attributeEditor.getValueIsAdjusting()) { attributeRestoreData = null; } } } updateDepth--; } @Override public void dispose() { setEditor(null); } }