/*
* (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.awt.Component;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreePath;
import org.openflexo.AdvancedPrefs;
import org.openflexo.FlexoCst;
import org.openflexo.antar.binding.TypeUtils;
import org.openflexo.components.browser.BrowserElement;
import org.openflexo.components.browser.view.BrowserView;
import org.openflexo.components.tabular.TabularView;
import org.openflexo.components.tabularbrowser.JTreeTable;
import org.openflexo.components.tabularbrowser.TabularBrowserView;
import org.openflexo.foundation.FlexoEditor;
import org.openflexo.foundation.FlexoModelObject;
import org.openflexo.foundation.action.ActionGroup;
import org.openflexo.foundation.action.ActionMenu;
import org.openflexo.foundation.action.FlexoAction;
import org.openflexo.foundation.action.FlexoActionType;
import org.openflexo.foundation.ontology.EditionPatternReference;
import org.openflexo.foundation.view.action.ActionSchemeActionType;
import org.openflexo.foundation.view.action.NavigationSchemeActionType;
import org.openflexo.foundation.viewpoint.ActionScheme;
import org.openflexo.foundation.viewpoint.NavigationScheme;
import org.openflexo.toolbox.ToolBox;
import org.openflexo.view.controller.FlexoController;
import org.openflexo.view.controller.action.EditionAction;
public abstract class ContextualMenuManager {
protected static final Logger logger = Logger.getLogger(ContextualMenuManager.class.getPackage().getName());
private static final MenuFilter ALL = new MenuFilter() {
@Override
public boolean acceptActionType(FlexoActionType<?, ?, ?> actionType) {
return true;
}
};
private final SelectionManager _selectionManager;
private boolean _isPopupMenuDisplayed = false;
private boolean _isPopupTriggering = false;
private Component _invoker = null;
private final FlexoController controller;
public ContextualMenuManager(SelectionManager selectionManager, FlexoController controller) {
super();
_selectionManager = selectionManager;
this.controller = controller;
}
public FlexoEditor getEditor() {
return controller.getEditor();
}
public void processMousePressed(MouseEvent e) {
resetContextualMenuTriggering();
_isPopupTriggering = e.isPopupTrigger() || e.getButton() == MouseEvent.BUTTON3;
if (e.getSource() instanceof Component) {
_invoker = (Component) e.getSource();
if (_isPopupTriggering) {
FlexoModelObject o = getFocusedObject(_invoker, e);
if (o == null) {
return;
}
// Return now when no selection manager defined
if (_selectionManager == null) {
return;
}
if (_selectionManager.selectionContains(o)) {
e.consume();
return;
} else {
boolean isCtrlDown = (e.getModifiersEx() & FlexoCst.MULTI_SELECTION_MASK) == FlexoCst.MULTI_SELECTION_MASK;
// BMA : mega hack
// le probleme : sous OSX : click-droit renvoie e.isMetaDown==true
// le symptaume : multiple-select dans IE lorsqu'on a une selection et que l'on click-droit sur un widget
// si qqun veut un jour modifier ce hack il faut :
// 1) verifier qu'on ne refait pas de multiple-select dans IE
// 2) vérifier que le multiple-select fonctionne dans le browserr du GC pour notament faire un "compare with each other"
// avec un seul bouton : la manip a faire est
// click sur file1
// pomme+click sur file2
// pomme+ctrl+click sur file1 ou file2 pour avoir le menu contextuel avec l'item "compare with each other"
if (ToolBox.getPLATFORM() == ToolBox.MACOS) {
isCtrlDown = e.getButton() == MouseEvent.BUTTON1 && e.isMetaDown();
}
// fin du mega hack
if (isCtrlDown) {
_selectionManager.addToSelected(o);
} else {
_selectionManager.setSelectedObject(o);
}
}
}
}
}
public void processMouseReleased(MouseEvent e) {
_isPopupTriggering = _isPopupTriggering || e.isPopupTrigger();
if (_isPopupTriggering) {
if (e.getSource() == _invoker /* && (hasSelection()) */) {
displayPopupMenu((Component) e.getSource(), e);
e.consume();
resetContextualMenuTriggering();
}
}
}
public void processMouseMoved(MouseEvent e) {
if (_isPopupMenuDisplayed && AdvancedPrefs.getCloseOnMouseOut()) {
Rectangle menuBounds = _popupMenu.getBounds();
menuBounds.width = menuBounds.width + 40;
menuBounds.height = menuBounds.height + 40;
menuBounds.x = menuBounds.x - 20;
menuBounds.y = menuBounds.y - 20;
if (e.getSource() instanceof Component) {
Point pointRelatingToMenu = SwingUtilities.convertPoint((Component) e.getSource(), e.getPoint(), _popupMenu);
// logger.info ("event="+e);
// logger.info ("pointRelatingToMenu="+pointRelatingToMenu);
if (!menuBounds.contains(pointRelatingToMenu)) {
// Mouse leaves menu
hidePopupMenu();
}
}
}
}
public void resetContextualMenuTriggering() {
_isPopupTriggering = false;
_invoker = null;
}
// ==========================================================================
// ============================= Filters ===================================
// ==========================================================================
public boolean acceptAction(FlexoActionType<?, ?, ?> action) {
// override this method to exclude some actions.
return true;
}
@SuppressWarnings("unchecked")
public <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> List<FlexoActionType<A, T1, T2>> getActionTypesWithAddType(
FlexoModelObject focusedObject, Vector<? extends FlexoModelObject> globalSelection) {
List<FlexoActionType<A, T1, T2>> returned = new ArrayList<FlexoActionType<A, T1, T2>>();
if (getEditor() == null) {
return returned;
}
for (FlexoActionType<?, ?, ?> actionType : focusedObject.getActionList()) {
if (TypeUtils.isAssignableTo(focusedObject, actionType.getFocusedObjectType())
&& (globalSelection == null || TypeUtils.isAssignableTo(globalSelection, actionType.getGlobalSelectionType()))) {
FlexoActionType<A, T1, T2> cast = (FlexoActionType<A, T1, T2>) actionType;
if (cast.getActionCategory() == FlexoActionType.ADD_ACTION_TYPE) {
if (getEditor().isActionVisible(cast, (T1) focusedObject, (Vector<T2>) globalSelection)) {
if (getEditor().isActionEnabled(cast, (T1) focusedObject, (Vector<T2>) globalSelection)) {
returned.add(cast);
}
}
}
}
}
return returned;
}
@SuppressWarnings("unchecked")
public <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> List<FlexoActionType<A, T1, T2>> getActionTypesWithDeleteType(
FlexoModelObject focusedObject, Vector<? extends FlexoModelObject> globalSelection) {
List<FlexoActionType<A, T1, T2>> returned = new ArrayList<FlexoActionType<A, T1, T2>>();
if (getEditor() == null) {
return returned;
}
for (FlexoActionType<?, ?, ?> actionType : focusedObject.getActionList()) {
if (TypeUtils.isAssignableTo(focusedObject, actionType.getFocusedObjectType())
&& (globalSelection == null || TypeUtils.isAssignableTo(globalSelection, actionType.getGlobalSelectionType()))) {
@SuppressWarnings("unchecked")
FlexoActionType<A, T1, T2> cast = (FlexoActionType<A, T1, T2>) actionType;
if (cast.getActionCategory() == FlexoActionType.DELETE_ACTION_TYPE) {
if (getEditor().isActionVisible(cast, (T1) focusedObject, (Vector<T2>) globalSelection)) {
if (getEditor().isActionEnabled(cast, (T1) focusedObject, (Vector<T2>) globalSelection)) {
returned.add(cast);
}
}
}
}
}
return returned;
}
// ==========================================================================
// ============================= Jpopup ===================================
// ==========================================================================
public void displayPopupMenu(Component invoker, MouseEvent e) {
_invoker = invoker;
if (getFocusedObject(invoker, e) != null) {
makePopupMenu(invoker, e);
// if (hasSelection())
if (logger.isLoggable(Level.FINE)) {
logger.fine("displayPopupMenu() for " + getFocusedObject(invoker, e));
}
if (_popupMenu.getComponentCount() > 0) {
_popupMenu.show(_invoker, e.getPoint().x, e.getPoint().y);
_isPopupMenuDisplayed = true;
} else {
resetContextualMenuTriggering();
}
}
}
public void hidePopupMenu() {
if (logger.isLoggable(Level.FINE)) {
logger.fine("hidePopupMenu()");
}
resetContextualMenuTriggering();
_popupMenu.setVisible(false);
_isPopupMenuDisplayed = false;
_popupMenu = null;
}
private JPopupMenu makePopupMenu(Component invoker, MouseEvent e) {
_popupMenu = null;
return makePopupMenu(getFocusedObject(invoker, e), ALL);
}
public JPopupMenu makePopupMenu(FlexoModelObject focusedObject, MenuFilter filter) {
if (focusedObject != null) {
ContextualMenu contextualMenu = new ContextualMenu();
for (FlexoActionType next : focusedObject.getActionList()) {
if (filter.acceptActionType(next)
&& getEditor().isActionVisible(next, focusedObject,
_selectionManager != null ? _selectionManager.getSelection() : null)) {
contextualMenu.putAction(next);
}
}
if (focusedObject.getEditionPatternReferences() != null) {
for (EditionPatternReference epr : focusedObject.getEditionPatternReferences()) {
if (epr != null && epr.getEditionPattern() != null && epr.getEditionPattern().hasActionScheme()) {
for (ActionScheme as : epr.getEditionPattern().getActionSchemes()) {
contextualMenu.putAction(new ActionSchemeActionType(as, epr));
}
}
}
for (EditionPatternReference epr : focusedObject.getEditionPatternReferences()) {
if (epr != null && epr.getEditionPattern() != null && epr.getEditionPattern().hasNavigationScheme()) {
for (NavigationScheme ns : epr.getEditionPattern().getNavigationSchemes()) {
contextualMenu.putAction(new NavigationSchemeActionType(ns, epr));
}
}
}
}
_popupMenu = contextualMenu.makePopupMenu(focusedObject);
} else {
_popupMenu = new JPopupMenu();
}
return _popupMenu;
}
public void showPopupMenuForObject(FlexoModelObject focusedObject, Component invoker, Point position) {
_invoker = invoker;
if (focusedObject != null) {
makePopupMenu(focusedObject, ALL);
// if (hasSelection())
if (logger.isLoggable(Level.FINE)) {
logger.fine("displayPopupMenu() for " + focusedObject);
}
if (_popupMenu.getComponentCount() > 0) {
_popupMenu.show(_invoker, position.x, position.y);
_isPopupMenuDisplayed = true;
} else {
resetContextualMenuTriggering();
}
}
}
protected class ContextualMenu extends Hashtable<ActionGroup, ContextualMenuGroup> {
private final Hashtable<ActionMenu, ContextualSubMenu> _subMenus = new Hashtable<ActionMenu, ContextualSubMenu>();
public Enumeration<ContextualMenuGroup> orderedGroups() {
Vector<ContextualMenuGroup> orderedGroups = new Vector<ContextualMenuGroup>(values());
Collections.sort(orderedGroups, new Comparator<ContextualMenuGroup>() {
@Override
public int compare(ContextualMenuGroup o1, ContextualMenuGroup o2) {
return o1.getActionGroup().getIndex() - o2.getActionGroup().getIndex();
}
});
return orderedGroups.elements();
}
public void putAction(FlexoActionType actionType) {
if (acceptAction(actionType)) {
if (actionType.getActionMenu() != null) {
ContextualSubMenu subMenu = _subMenus.get(actionType.getActionMenu());
if (subMenu == null) {
subMenu = new ContextualSubMenu(actionType.getActionMenu());
addSubMenu(subMenu);
_subMenus.put(actionType.getActionMenu(), subMenu);
}
subMenu.addAction(actionType);
} else {
addAction(actionType);
}
}
}
public void addAction(FlexoActionType actionType) {
if (acceptAction(actionType)) {
ContextualMenuGroup contextualMenuGroup = get(actionType.getActionGroup());
if (contextualMenuGroup == null) {
contextualMenuGroup = new ContextualMenuGroup(actionType.getActionGroup());
put(actionType.getActionGroup(), contextualMenuGroup);
}
contextualMenuGroup.addAction(actionType);
}
}
public void addSubMenu(ContextualSubMenu subMenu) {
ContextualMenuGroup contextualMenuGroup = get(subMenu.getActionMenu().getActionGroup());
if (contextualMenuGroup == null) {
contextualMenuGroup = new ContextualMenuGroup(subMenu.getActionMenu().getActionGroup());
put(subMenu.getActionMenu().getActionGroup(), contextualMenuGroup);
}
contextualMenuGroup.addSubMenu(subMenu);
}
public JPopupMenu makePopupMenu(FlexoModelObject focusedObject) {
boolean addSeparator = false;
JPopupMenu returned = new JPopupMenu();
for (Enumeration<ContextualMenuGroup> en = orderedGroups(); en.hasMoreElements();) {
ContextualMenuGroup menuGroup = en.nextElement();
if (addSeparator) {
returned.addSeparator();
// System.out.println("------- Ajout de separator -------");
}
addSeparator = true;
// System.out.println("============= Groupe
// "+menuGroup._actionGroup.getLocalizedName());
for (Enumeration en2 = menuGroup.elements(); en2.hasMoreElements();) {
Object nextElement = en2.nextElement();
if (nextElement instanceof FlexoActionType) {
// System.out.println("Ajout de "+nextElement);
makeMenuItem((FlexoActionType) nextElement, focusedObject, returned);
} else if (nextElement instanceof ContextualSubMenu) {
// System.out.println("Ajout de "+nextElement);
JMenuItem item = ((ContextualSubMenu) nextElement).makeMenu(focusedObject);
returned.add(item);
}
}
}
return returned;
}
}
protected class ContextualMenuGroup extends Vector {
private final ActionGroup _actionGroup;
public ContextualMenuGroup(ActionGroup actionGroup) {
_actionGroup = actionGroup;
}
public void addAction(FlexoActionType actionType) {
// should have already been checked, but it's more secure.
if (acceptAction(actionType)) {
add(actionType);
}
}
public void addSubMenu(ContextualSubMenu subMenu) {
add(subMenu);
}
public ActionGroup getActionGroup() {
return _actionGroup;
}
}
protected class ContextualSubMenu extends ContextualMenu {
private final ActionMenu _actionMenu;
public ContextualSubMenu(ActionMenu actionMenu) {
_actionMenu = actionMenu;
}
public ActionMenu getActionMenu() {
return _actionMenu;
}
public JMenu makeMenu(FlexoModelObject focusedObject) {
boolean addSeparator = false;
JMenu returned = new JMenu();
returned.setText(getActionMenu().getLocalizedName());
if (getActionMenu().getSmallIcon() != null) {
returned.setIcon(getActionMenu().getSmallIcon());
}
for (Enumeration en = orderedGroups(); en.hasMoreElements();) {
ContextualMenuGroup menuGroup = (ContextualMenuGroup) en.nextElement();
if (addSeparator) {
returned.addSeparator();
}
addSeparator = true;
for (Enumeration en2 = menuGroup.elements(); en2.hasMoreElements();) {
Object nextElement = en2.nextElement();
if (nextElement instanceof FlexoActionType) {
makeMenuItem((FlexoActionType) nextElement, focusedObject, returned);
}
}
}
return returned;
}
}
public interface MenuFilter {
public boolean acceptActionType(FlexoActionType<?, ?, ?> actionType);
}
private <A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> JMenuItem makeMenuItem(
FlexoActionType<A, T1, T2> actionType, FlexoModelObject focusedObject, JPopupMenu menu) {
try {
Vector<T2> globalSelection = new Vector<T2>();
if (_selectionManager != null) {
for (FlexoModelObject o : _selectionManager.getSelection()) {
try {
globalSelection.add((T2) o);
} catch (ClassCastException e) {
// This is not good type, discard this object
logger.warning("Discard from selection " + o);
}
}
}
EditionAction<A, T1, T2> action = new EditionAction<A, T1, T2>(actionType, (T1) focusedObject, globalSelection, getEditor());
JMenuItem item = menu.add(action);
item.setText(actionType.getLocalizedName());
if (getEditor().getKeyStrokeFor(actionType) != null) {
item.setAccelerator(getEditor().getKeyStrokeFor(actionType));
}
if (getEditor().getEnabledIconFor(actionType) != null) {
item.setIcon(getEditor().getEnabledIconFor(actionType));
}
if (getEditor().getDisabledIconFor(actionType) != null) {
item.setDisabledIcon(getEditor().getDisabledIconFor(actionType));
}
return item;
} catch (ClassCastException exception) {
logger.warning("ClassCastException raised while trying to build FlexoAction " + actionType + " Exception: "
+ exception.getMessage());
return null;
}
}
<A extends FlexoAction<A, T1, T2>, T1 extends FlexoModelObject, T2 extends FlexoModelObject> JMenuItem makeMenuItem(
FlexoActionType<A, T1, T2> actionType, T1 focusedObject, JMenu menu) {
try {
EditionAction<A, T1, T2> action = new EditionAction<A, T1, T2>(actionType, focusedObject,
_selectionManager != null ? (Vector<T2>) _selectionManager.getSelection() : null, getEditor());
JMenuItem item = menu.add(action);
item.setText(actionType.getLocalizedName());
if (getEditor().getKeyStrokeFor(actionType) != null) {
item.setAccelerator(getEditor().getKeyStrokeFor(actionType));
}
if (getEditor().getEnabledIconFor(actionType) != null) {
item.setIcon(getEditor().getEnabledIconFor(actionType));
}
if (getEditor().getDisabledIconFor(actionType) != null) {
item.setDisabledIcon(getEditor().getDisabledIconFor(actionType));
}
return item;
} catch (ClassCastException exception) {
logger.warning("ClassCastException raised while trying to build FlexoAction " + actionType + " Exception: "
+ exception.getMessage());
return null;
}
}
private JPopupMenu _popupMenu;
public boolean isPopupMenuDisplayed() {
return _isPopupMenuDisplayed;
}
public FlexoModelObject getFocusedObject(Component focusedComponent, MouseEvent e) {
// Try to handle TabularBrowserView
if (e.getSource() instanceof JTreeTable) {
Component c = (Component) e.getSource();
while (c != null && !(c instanceof TabularBrowserView)) {
c = c.getParent();
}
if (c != null && c instanceof TabularBrowserView) {
return ((TabularBrowserView) c).getFocusedObject();
}
}
// Try to handle TabularView
if (e.getSource() instanceof JTable) {
Component c = (Component) e.getSource();
while (c != null && !(c instanceof TabularView)) {
c = c.getParent();
}
if (c != null && c instanceof TabularView) {
return ((TabularView) c).getFocusedObject();
}
}
// Finally handle browsers
if (e.getSource() instanceof JTree) {
Component c = (Component) e.getSource();
while (c != null && !(c instanceof BrowserView)) {
c = c.getParent();
}
if (c instanceof BrowserView) {
TreePath path = ((BrowserView) c).getTreeView().getClosestPathForLocation(e.getX(), e.getY());
if (path != null) {
return ((BrowserElement) path.getLastPathComponent()).getObject();
}
return ((BrowserView) c).getSelectedObject();
}
}
return null;
}
}