/* * @(#)AbstractSelectedAction.java * * Copyright (c) 2003-2008 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.action; import edu.umd.cs.findbugs.annotations.Nullable; import org.jhotdraw.draw.Drawing; import org.jhotdraw.draw.DrawingEditor; import org.jhotdraw.draw.DrawingView; import org.jhotdraw.draw.event.FigureSelectionEvent; import org.jhotdraw.draw.event.FigureSelectionListener; import javax.swing.*; import java.beans.*; import java.io.Serializable; import javax.swing.undo.*; import org.jhotdraw.util.*; import org.jhotdraw.app.Disposable; import org.jhotdraw.beans.WeakPropertyChangeListener; /** * This abstract class can be extended to implement an {@code Action} that acts * on behalf of the selected figures of a {@link org.jhotdraw.draw.DrawingView}. * <p> * By default the enabled state of this action reflects the enabled state of the * active {@code DrawingView}. If no drawing view is active, this action is * disabled. When many actions listen to the enabled state of the active drawing * views this can considerably slow down the editor. If updating the enabled * state is not necessary, you can prevent the action from doing so using * {@link #setUpdateEnabledState}. * <p> * {@code AbstractDrawingEditorAction} listens using a * {@link WeakPropertyChangeListener} on the {@code DrawingEditor} and thus may * become garbage collected if it is not referenced by any other object. * * @author Werner Randelshofer * @version $Id$ */ public abstract class AbstractSelectedAction extends AbstractAction implements Disposable { private static final long serialVersionUID = 1L; @Nullable private DrawingEditor editor; @Nullable transient private DrawingView activeView; private class EventHandler implements PropertyChangeListener, FigureSelectionListener, Serializable { private static final long serialVersionUID = 1L; @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName() == DrawingEditor.ACTIVE_VIEW_PROPERTY) { if (activeView != null) { activeView.removeFigureSelectionListener(this); activeView.removePropertyChangeListener(this); } if (evt.getNewValue() != null) { activeView = ((DrawingView) evt.getNewValue()); activeView.addFigureSelectionListener(this); activeView.addPropertyChangeListener(this); } updateEnabledState(); } else if ("enabled".equals(evt.getPropertyName())) { updateEnabledState(); } } @Override public String toString() { return AbstractSelectedAction.this + " " + this.getClass() + "@" + hashCode(); } @Override public void selectionChanged(FigureSelectionEvent evt) { updateEnabledState(); } }; @Nullable private EventHandler eventHandler = new EventHandler(); /** Creates an action which acts on the selected figures on the current view * of the specified editor. */ public AbstractSelectedAction(DrawingEditor editor) { setEditor(editor); //updateEnabledState(); } /** Updates the enabled state of this action to reflect the enabled state * of the active {@code DrawingView}. If no drawing view is active, this * action is disabled. */ protected void updateEnabledState() { if (getView() != null) { setEnabled(getView().isEnabled() && getView().getSelectionCount() > 0); } else { setEnabled(false); } } @Override public void dispose() { setEditor(null); } public void setEditor(@Nullable DrawingEditor editor) { if (eventHandler != null) { unregisterEventHandler(); } this.editor = editor; if (editor != null && eventHandler != null) { registerEventHandler(); updateEnabledState(); } } @Nullable public DrawingEditor getEditor() { return editor; } @Nullable protected DrawingView getView() { return (editor == null) ? null : editor.getActiveView(); } @Nullable protected Drawing getDrawing() { return (getView() == null) ? null : getView().getDrawing(); } protected void fireUndoableEditHappened(UndoableEdit edit) { getDrawing().fireUndoableEditHappened(edit); } /** By default, the enabled state of this action is updated to reflect * the enabled state of the active {@code DrawingView}. * Since this is not always necessary, and since many listening actions * may considerably slow down the drawing editor, you can switch this * behavior off here. * * @param newValue Specify false to prevent automatic updating of the * enabled state. */ public void setUpdateEnabledState(boolean newValue) { // Note: eventHandler != null yields true, if we are currently updating // the enabled state. if (eventHandler != null != newValue) { if (newValue) { eventHandler = new EventHandler(); registerEventHandler(); } else { unregisterEventHandler(); eventHandler = null; } } if (newValue) { updateEnabledState(); } } /** Returns true, if this action automatically updates its enabled * state to reflect the enabled state of the active {@code DrawingView}. */ public boolean isUpdatEnabledState() { return eventHandler != null; } /** Unregisters the event handler from the drawing editor and the * active drawing view. */ private void unregisterEventHandler() { if (editor != null) { editor.removePropertyChangeListener(eventHandler); } if (activeView != null) { activeView.removeFigureSelectionListener(eventHandler); activeView.removePropertyChangeListener(eventHandler); activeView = null; } } /** Registers the event handler from the drawing editor and the * active drawing view. */ private void registerEventHandler() { if (editor != null) { editor.addPropertyChangeListener(new WeakPropertyChangeListener(eventHandler)); if (activeView != null) { activeView.removeFigureSelectionListener(eventHandler); activeView.removePropertyChangeListener(eventHandler); } activeView = editor.getActiveView(); if (activeView != null) { activeView.addFigureSelectionListener(eventHandler); activeView.addPropertyChangeListener(eventHandler); } } } }