/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.selection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openflexo.foundation.FlexoEditor;
import org.openflexo.foundation.FlexoModelObject;
import org.openflexo.inspector.InspectableObject;
import org.openflexo.inspector.selection.EmptySelection;
import org.openflexo.inspector.selection.MultipleSelection;
import org.openflexo.inspector.selection.UniqueSelection;
import org.openflexo.view.controller.FlexoController;
import org.openflexo.view.palette.PalettePanel;
/**
* Abstract selection manager: there is one SelectionManager for each FlexoModule. This manager is responsible of:
* <UL>
* <LI>Selection management: it holds a list of all currently selected objects, as FlexoModelObject instances</LI>
* <LI>Synchonization of all components synchronized with itself objects, as FlexoModelObject instances</LI>
* <LI>Focused and last selected objects management</LI>
* <LI>Inspection management</LI>
* <LI>Copy/Cut/Paste/SelectAll management by storing and managing a {@link org.openflexo.selection.FlexoClipboard}</LI>
* </UL>
*
* @author bmangez, sguerin
*/
public abstract class SelectionManager extends Observable {
private static final Logger logger = Logger.getLogger(SelectionManager.class.getPackage().getName());
protected FlexoClipboard _clipboard;
private InspectableObject _inspectedObject;
protected FocusableView _focusedPanel;
private FlexoModelObject lastSelectedObject;
private final FlexoController _controller;
protected ContextualMenuManager _contextualMenuManager;
/**
* This represents the selection, as a Vector of FlexoModelObject
*/
private final Vector<FlexoModelObject> _selection;
/**
* This represents all registered SelectionListener instances
*/
private final Vector<SelectionListener> _selectionListeners;
private final Hashtable<String, Object> inspectionContext;
public static final String MODULE_KEY = "MODULE";
// ==========================================================================
// ============================= Constructor
// ================================
// ==========================================================================
public SelectionManager(FlexoController controller) {
super();
_controller = controller;
_selection = new Vector<FlexoModelObject>();
_selectionListeners = new Vector<SelectionListener>();
inspectionContext = new Hashtable<String, Object>();
inspectionContext.put(MODULE_KEY, getController().getModule().getModule().getClassName());
}
public FlexoController getController() {
return _controller;
}
public FlexoEditor getEditor() {
return _controller.getEditor();
}
@Override
public void addObserver(Observer o) {
super.addObserver(o);
// (new Exception("ici")).printStackTrace();
}
// ==========================================================================
// =================== Selection Management, public A.P.I
// ===================
// ==========================================================================
public void addToSelectionListeners(SelectionListener listener) {
if (!_selectionListeners.contains(listener)) {
_selectionListeners.add(listener);
}
}
public void removeFromSelectionListeners(SelectionListener listener) {
_selectionListeners.remove(listener);
}
public void addToSelectionListeners(List<SelectionListener> listeners) {
for (SelectionListener listener : listeners) {
addToSelectionListeners(listener);
}
}
public void removeFromSelectionListeners(List<SelectionListener> listeners) {
_selectionListeners.removeAll(listeners);
}
public int getSelectionListenersCount() {
return _selectionListeners.size();
}
public void debugSelectionListeners() {
for (SelectionListener sl : _selectionListeners) {
System.out.println(" * " + sl);
}
}
public int getSelectionSize() {
return getSelection().size();
}
/**
* Return the current selection, as a Vector of FlexoModelObject
*
* @return a Vector of FlexoModelObject
*/
public Vector<FlexoModelObject> getSelection() {
return _selection;
}
/**
* Returns boolean indicating if current selection contains supplied object
*
* @param object
* @return a boolean
*/
public boolean selectionContains(FlexoModelObject object) {
return _selection.contains(object);
}
/**
* Set supplied object to be the current selection
*
* @param object
* : the object to add to selection
*/
public void setSelectedObject(FlexoModelObject object) {
resetSelection();
addToSelected(object);
}
/**
* Reset selection
*/
public void resetSelection() {
_selection.clear();
for (Enumeration<SelectionListener> e = _selectionListeners.elements(); e.hasMoreElements();) {
e.nextElement().fireResetSelection();
}
setLastSelectedObject(null);
fireSelectionBecomesEmpty();
updateInspectorManagement();
}
/**
* Add supplied object to current selection
*
* @param object
* : the object to add to selection
*/
public void addToSelected(FlexoModelObject object) {
internallyAddToSelected(object, true);
updateInspectorManagement();
}
/**
* Remove supplied object from current selection
*
* @param object
* : the object to remove from selection
*/
public void removeFromSelected(FlexoModelObject object) {
internallyRemoveFromSelected(object);
updateInspectorManagement();
}
/**
* Add supplied object to current selection
*
* @param object
* : the object to add to selection
*/
public void updateSelectionForMaster(SelectionSynchronizedComponent master) {
for (Enumeration<FlexoModelObject> en = getSelection().elements(); en.hasMoreElements();) {
FlexoModelObject obj = en.nextElement();
if (!master.mayRepresents(obj)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Removing " + obj + " because master view is not able to show it");
}
internallyRemoveFromSelected(obj);
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Keeping " + obj + " because master view is able to show it");
}
}
}
updateInspectorManagement();
}
/**
* Add supplied objects to current selection
*
* @param objects
* : objects to add to selection, as a Vector of FlexoModelObject
*/
public void addToSelected(List<? extends FlexoModelObject> objects) {
if (objects == null || objects.isEmpty()) {
return;
}
for (SelectionListener sl : _selectionListeners) {
sl.fireBeginMultipleSelection();
}
for (FlexoModelObject nextObj : objects) {
internallyAddToSelected(nextObj, objects.size() == 1);
}
for (SelectionListener sl : _selectionListeners) {
sl.fireEndMultipleSelection();
}
updateInspectorManagement();
}
/**
* Remove supplied objects from current selection
*
* @param objects
* : objects to remove from selection, as a Vector of FlexoModelObject
*/
public void removeFromSelected(Vector<? extends FlexoModelObject> objects) {
if (objects == null || objects.isEmpty()) {
return;
}
for (Enumeration<SelectionListener> e = _selectionListeners.elements(); e.hasMoreElements();) {
SelectionListener sl = e.nextElement();
sl.fireBeginMultipleSelection();
}
internallyRemoveFromSelected(objects);
for (Enumeration<SelectionListener> e = _selectionListeners.elements(); e.hasMoreElements();) {
SelectionListener sl = e.nextElement();
sl.fireEndMultipleSelection();
}
updateInspectorManagement();
}
/**
* Sets supplied vector of FlexoModelObjects to be the current Selection
*
* @param objects
* : the object to set for current selection, as a Vector of FlexoModelObject
*/
public void setSelectedObjects(List<? extends FlexoModelObject> objects) {
resetSelection();
addToSelected(objects);
}
private FlexoModelObject _focusedObject;
/**
* Return currently focused object
*/
public FlexoModelObject getFocusedObject() {
return _focusedObject;
}
/**
* Sets currently focused object
*/
protected void setFocusedObject(FlexoModelObject focusedObject) {
_focusedObject = focusedObject;
}
public FlexoModelObject getLastSelectedObject() {
return lastSelectedObject;
}
public void setLastSelectedObject(FlexoModelObject lastSelectedObject) {
this.lastSelectedObject = lastSelectedObject;
}
/**
* Request to all registered SelectionListener of this SelectionManager to be back-synchronized with current selection
*/
public void fireUpdateSelection() {
for (Enumeration<SelectionListener> e = _selectionListeners.elements(); e.hasMoreElements();) {
SelectionListener sl = e.nextElement();
fireUpdateSelection(sl);
}
}
/**
* Request to supplied SelectionListener of this SelectionManager to be back-synchronized with current selection
*/
public void fireUpdateSelection(SelectionListener selectionListenerToSynchronize) {
selectionListenerToSynchronize.fireResetSelection();
for (Enumeration<FlexoModelObject> e = getSelection().elements(); e.hasMoreElements();) {
FlexoModelObject o = e.nextElement();
if (!o.isDeleted()) {
selectionListenerToSynchronize.fireObjectSelected(o);
}
}
}
// ==========================================================================
// ================ Selection Management, protected A.P.I
// ===================
// ==========================================================================
/**
* Provides a hook called when selection becomes empty
*
*/
protected void fireSelectionBecomesEmpty() {
_clipboard.setCopyEnabled(false);
_clipboard.setCutEnabled(false);
}
/**
* Provides a hook called when selection is no more empty
*
*/
protected void fireSelectionIsNoMoreEmpty() {
_clipboard.setCopyEnabled(true);
_clipboard.setCutEnabled(true);
}
/**
* Add supplied object to current selection
*
* @param object
* : the object to add to selection
*/
protected void internallyAddToSelected(FlexoModelObject object) {
internallyAddToSelected(object, true);
}
/**
* Add supplied object to current selection
*
* @param isNewFocusedObject
* TODO
*
* @param object
* : the object to add to selection
*/
protected void internallyAddToSelected(FlexoModelObject object, boolean isNewFocusedObject) {
if (!isSelectable(object)) {
return;
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("internallyAddToSelected with " + object);
}
boolean selectionWasEmpty = getSelectionSize() == 0;
if (!selectionContains(object)) {
_selection.add(object);
for (Enumeration<SelectionListener> e = _selectionListeners.elements(); e.hasMoreElements();) {
SelectionListener sl = e.nextElement();
sl.fireObjectSelected(object);
}
if (selectionWasEmpty && getSelectionSize() > 0) {
fireSelectionIsNoMoreEmpty();
}
}
if (isNewFocusedObject) {
setLastSelectedObject(object);
setFocusedObject(object);
}
}
/**
* Remove supplied object from current selection
*
* @param object
* : the object to remove from selection
*/
protected void internallyRemoveFromSelected(FlexoModelObject object) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("internallyRemoveFromSelected with " + object);
}
if (_focusedObject == object) {
_focusedObject = null;
}
if (_inspectedObject == object) {
_inspectedObject = null;
}
if (lastSelectedObject == object) {
lastSelectedObject = null;
}
if (selectionContains(object)) {
_selection.remove(object);
for (Enumeration<SelectionListener> e = _selectionListeners.elements(); e.hasMoreElements();) {
SelectionListener sl = e.nextElement();
sl.fireObjectDeselected(object);
}
if (getSelectionSize() == 0) {
fireSelectionBecomesEmpty();
}
}
fireUpdateSelection();
}
/**
* Remove supplied objects from current selection
*
* @param objects
* : the objects to remove from selection
*/
protected void internallyRemoveFromSelected(Vector<? extends FlexoModelObject> objects) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("internallyRemoveFromSelected with " + objects);
}
for (FlexoModelObject object : objects) {
if (_focusedObject == object) {
_focusedObject = null;
}
if (_inspectedObject == object) {
_inspectedObject = null;
}
if (lastSelectedObject == object) {
lastSelectedObject = null;
}
if (selectionContains(object)) {
_selection.remove(object);
for (Enumeration<SelectionListener> e = _selectionListeners.elements(); e.hasMoreElements();) {
SelectionListener sl = e.nextElement();
sl.fireObjectDeselected(object);
}
}
}
if (getSelectionSize() == 0) {
fireSelectionBecomesEmpty();
}
fireUpdateSelection();
}
@Override
public String toString() {
String returned = super.toString() + "\n";
for (Enumeration<FlexoModelObject> en = _selection.elements(); en.hasMoreElements();) {
FlexoModelObject o = en.nextElement();
StringTokenizer st = new StringTokenizer(o.getClass().getName(), ".");
String className = "";
while (st.hasMoreTokens()) {
className = st.nextToken();
}
returned += "> " + className + ":" + o + "\n";
}
return returned;
}
// ==========================================================================
// ============================= Cut&Paste Management
// =======================
// ==========================================================================
public boolean performSelectionCopy() {
if (logger.isLoggable(Level.FINE)) {
logger.fine("performSelectionCopy in " + getClass().getName());
}
return _clipboard.performSelectionCopy(getSelection());
}
public boolean performSelectionPaste() {
if (logger.isLoggable(Level.FINE)) {
logger.fine("performSelectionPaste in " + getClass().getName());
}
return _clipboard.performSelectionPaste();
}
public boolean performSelectionCut() {
if (logger.isLoggable(Level.WARNING)) {
logger.warning("Perform selection cut is not implemented");
}
return false;
}
public boolean hasCopiedData() {
return _clipboard.hasCopiedData();
}
public abstract boolean performSelectionSelectAll();
public abstract FlexoModelObject getPasteContext();
public PastingGraphicalContext getPastingGraphicalContext() {
return null;
}
// ============================================================
// ======================= Inspector ==========================
// ============================================================
@Deprecated
public Hashtable<String, Object> getInspectionContext() {
return inspectionContext;
}
@Deprecated
public Object setInspectionContext(String key, Object value) {
Object returned = inspectionContext.get(key);
inspectionContext.put(key, value);
return returned;
}
@Deprecated
public Object removeInspectionContext(String key) {
Object returned = inspectionContext.get(key);
inspectionContext.remove(key);
return returned;
}
/**
* override this method in your Module's selectionManager if you want to specify another inspector for an object.
*/
/*
* protected String getInspectorNameForObject(InspectableObject inspectable)
* { return null; }
*/
private void setCurrentInspectedObject(InspectableObject inspectable) {
// logger.info("Inspect "+inspectable);
if (!isInspectable(inspectable)) {
return;
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("setCurrentInspectedObject: " + countObservers() + " observers");
}
if (_inspectedObject != null) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Inspected object was: " + _inspectedObject.getClass().getName() + " with "
+ ((FlexoModelObject) _inspectedObject).countObservers() + " observers");
}
}
if (inspectable != null) {
if (_inspectedObject == null || !_inspectedObject.equals(inspectable)) {
_inspectedObject = inspectable;
if (logger.isLoggable(Level.FINE)) {
logger.fine("Inspected object is now: " + _inspectedObject.getClass().getName() + " with "
+ ((FlexoModelObject) _inspectedObject).countObservers() + " observers");
}
setChanged();
// Component focusOwner =
// KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
notifyObservers(new UniqueSelection(_inspectedObject, getInspectionContext()));
}
}
if (inspectable instanceof FlexoModelObject) {
setFocusedObject((FlexoModelObject) inspectable);
}
}
private void setCurrentInspectedObjectToNone() {
_inspectedObject = null;
setChanged();
notifyObservers(new EmptySelection());
// InspectorController._inspectorWindow.toFront();
}
private void setCurrentInspectedObjectToMultiple() {
_inspectedObject = null;
setChanged();
notifyObservers(new MultipleSelection());
// InspectorController._inspectorWindow.toFront();
}
// Note that the updateInspectorManagement method is quite expensive in CPU
private void updateInspectorManagement() {
if (getSelectionSize() == 0) {
setCurrentInspectedObjectToNone();
} else if (getSelectionSize() == 1) {
FlexoModelObject selection = getSelection().firstElement();
if (selection instanceof InspectableObject) {
setCurrentInspectedObject((InspectableObject) selection);
} else {
setCurrentInspectedObjectToNone();
}
} else if (getSelectionSize() > 1) {
setCurrentInspectedObjectToMultiple();
}
if (logger.isLoggable(Level.FINE)) {
logger.fine("Current selection is now: " + toString());
}
}
// ==========================================================================
// ============================= Deletion
// ===================================
// ==========================================================================
/**
* Returns the root object that can be currently edited
*
* @return FlexoModelObject
*/
public abstract FlexoModelObject getRootFocusedObject();
protected boolean isInspectable(InspectableObject object) {
if (!(object instanceof FlexoModelObject)) {
return true;
}
FlexoModelObject obj = (FlexoModelObject) object;
if (obj.getContext() != null) {
if (obj.getContext() instanceof PalettePanel) {
return ((PalettePanel) obj.getContext()).isEdited();
}
}
return true;
}
protected boolean isSelectable(FlexoModelObject object) {
if (object == null) {
return false;
}
if (object.isDeleted()) {
return false;
}
if (object.getContext() != null) {
if (object.getContext() instanceof PalettePanel) {
return ((PalettePanel) object.getContext()).isEdited();
}
}
return true;
}
public ContextualMenuManager getContextualMenuManager() {
return _contextualMenuManager;
}
}