/*
* Freeplane - mind map editor
* Copyright (C) 2008 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitry Polivaev
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeplane.features.map;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.MouseWheelEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JPopupMenu;
import org.freeplane.core.extension.IExtension;
import org.freeplane.core.ui.AFreeplaneAction;
import org.freeplane.core.ui.AMultipleNodeAction;
import org.freeplane.core.ui.IMouseWheelEventHandler;
import org.freeplane.features.mode.Controller;
import org.freeplane.features.mode.ModeController;
/**
* @author foltin
*/
public class FoldingController implements IMouseWheelEventHandler, IExtension {
@SuppressWarnings("serial")
private class FoldAllAction extends AMultipleNodeAction {
public FoldAllAction() {
super("FoldAllAction");
}
@Override
public void actionPerformed(final ActionEvent e, final NodeModel node) {
foldAll(node);
}
}
@SuppressWarnings("serial")
private class FoldOneLevelAction extends AMultipleNodeAction {
public FoldOneLevelAction() {
super("FoldOneLevelAction");
}
@Override
public void actionPerformed(final ActionEvent e, final NodeModel node) {
foldOneStage(node);
}
}
@SuppressWarnings("serial")
private class UnfoldAllAction extends AMultipleNodeAction {
public UnfoldAllAction() {
super("UnfoldAllAction");
}
@Override
public void actionPerformed(final ActionEvent e, final NodeModel node) {
unfoldAll(node);
}
}
@SuppressWarnings("serial")
private class UnfoldOneLevelAction extends AMultipleNodeAction {
public UnfoldOneLevelAction() {
super("UnfoldOneLevelAction");
}
@Override
public void actionPerformed(final ActionEvent e, final NodeModel node) {
unfoldOneStage(node);
}
}
protected static Insets nullInsets = new Insets(0, 0, 0, 0);
@SuppressWarnings("serial")
private class FoldingPopupMenu extends JPopupMenu{
final private NodeModel node;
FoldingPopupMenu(NodeModel node){
this.node = node;
addAction(new UnfoldOneLevelPopupAction());
addAction(new FoldOneLevelPopupAction());
addAction(new UnfoldAllPopupAction());
addAction(new FoldAllPopupAction());
}
private JButton addAction(Action a) {
final JButton menuItem = new JButton(a);
menuItem.setToolTipText(menuItem.getText());
menuItem.setText(null);
add(menuItem);
menuItem.setMargin(nullInsets);
return menuItem;
}
@SuppressWarnings("serial")
private class FoldAllPopupAction extends FoldAllAction{
@Override
public void actionPerformed(final ActionEvent e){
actionPerformed(e, node);
}
}
@SuppressWarnings("serial")
private class FoldOneLevelPopupAction extends FoldOneLevelAction{
@Override
public void actionPerformed(final ActionEvent e){
actionPerformed(e, node);
}
}
@SuppressWarnings("serial")
private class UnfoldAllPopupAction extends UnfoldAllAction{
@Override
public void actionPerformed(final ActionEvent e){
actionPerformed(e, node);
}
}
@SuppressWarnings("serial")
private class UnfoldOneLevelPopupAction extends UnfoldOneLevelAction{
@Override
public void actionPerformed(final ActionEvent e){
actionPerformed(e, node);
}
}
}
// // final private Controller controller;
public static void install( final FoldingController foldingController) {
Controller.getCurrentModeController().addExtension(FoldingController.class, foldingController);
}
public FoldingController() {
super();
final ModeController modeController = Controller.getCurrentModeController();
modeController.getUserInputListenerFactory().addMouseWheelEventHandler(this);
for (final AFreeplaneAction annotatedAction : getAnnotatedActions()) {
modeController.addAction(annotatedAction);
}
}
private List<AMultipleNodeAction> getAnnotatedActions() {
final ArrayList<AMultipleNodeAction> result = new ArrayList<AMultipleNodeAction>();
result.add(new UnfoldAllAction());
result.add(new FoldAllAction());
result.add(new UnfoldOneLevelAction());
result.add(new FoldOneLevelAction());
return result;
}
protected void foldAll(final NodeModel node) {
final MapController modeController = Controller.getCurrentModeController().getMapController();
for (NodeModel child : modeController.childrenUnfolded(node)) {
foldAll(child);
}
setFolded(node, true);
}
/**
* Unfolds every node that has only children which themselves have children.
* As this function is a bit difficult to describe and perhaps not so
* useful, it is currently not introduced into the menus.
*
* @param node
* node to start from.
*/
public void foldLastBranches(final NodeModel node) {
final MapController mapController = Controller.getCurrentModeController().getMapController();
boolean nodeHasChildWhichIsLeave = false;
for (final NodeModel child : mapController.childrenUnfolded(node)) {
if (child.getChildCount() == 0) {
nodeHasChildWhichIsLeave = true;
}
}
setFolded(node, nodeHasChildWhichIsLeave);
for (final NodeModel child : mapController.childrenUnfolded(node)) {
foldLastBranches(child);
}
}
protected void foldOneStage(final NodeModel node) {
foldStageN(node, getMaxDepth(node) - 1);
}
public void foldStageN(final NodeModel node, final int stage) {
final int k = depth(node);
if (k < stage) {
setFolded(node, false);
final MapController mapController = Controller.getCurrentModeController().getMapController();
for (final NodeModel child : mapController.childrenUnfolded(node)) {
foldStageN(child, stage);
}
}
else {
foldAll(node);
}
}
protected int getMaxDepth(final NodeModel node) {
final MapController mapController = Controller.getCurrentModeController().getMapController();
if (mapController.isFolded(node) || !mapController.hasChildren(node)) {
return depth(node);
}
int k = 0;
for (final NodeModel child : mapController.childrenUnfolded(node)) {
final int l = getMaxDepth(child);
if (l > k) {
k = l;
}
}
return k;
}
public int getMinDepth(final NodeModel node) {
final EncryptionModel encryptionModel = EncryptionModel.getModel(node);
if (encryptionModel != null && !encryptionModel.isAccessible() ) {
return Integer.MAX_VALUE;
}
final MapController mapController = Controller.getCurrentModeController().getMapController();
if (mapController.isFolded(node)) {
return depth(node);
}
if (!mapController.hasChildren(node)||AlwaysUnfoldedNode.isConnectorNode(node)) {
return Integer.MAX_VALUE;
}
int k = Integer.MAX_VALUE;
for (final NodeModel child : mapController.childrenUnfolded(node)) {
final int l = getMinDepth(child);
if (l < k) {
k = l;
}
}
return k;
}
public boolean handleMouseWheelEvent(final MouseWheelEvent e) {
if ((e.getModifiers() & InputEvent.ALT_MASK) != 0) {
Controller controller = Controller.getCurrentController();
final NodeModel rootNode = controller.getMap().getRootNode();
if (e.getWheelRotation() > 0) {
unfoldOneStage(rootNode);
}
else {
final ModeController modeController = controller.getModeController();
modeController.getMapController().select(controller.getMap().getRootNode());
foldOneStage(rootNode);
}
return true;
}
return false;
}
private void setFolded(final NodeModel node, final boolean state) {
final MapController mapController = Controller.getCurrentModeController().getMapController();
if (! node.isRoot() && mapController.hasChildren(node) && (mapController.isFolded(node) != state)) {
mapController.setFolded(node, state);
}
}
public void unfoldAll(final NodeModel node) {
setFolded(node, false);
final MapController mapController = Controller.getCurrentModeController().getMapController();
for (final NodeModel child : mapController.childrenUnfolded(node)) {
unfoldAll(child);
}
}
protected void unfoldOneStage(final NodeModel node) {
int minDepth = getMinDepth(node);
if (minDepth < Integer.MAX_VALUE) {
minDepth++;
}
unfoldStageN(node, minDepth);
}
public void unfoldStageN(final NodeModel node, final int stage) {
final int k = depth(node);
if (k < stage) {
setFolded(node, false);
final MapController mapController = Controller.getCurrentModeController().getMapController();
for (final NodeModel child : mapController.childrenUnfolded(node)) {
unfoldStageN(child, stage);
}
}
else {
foldAll(node);
}
}
private int depth(NodeModel node) {
if (node.isRoot())
return 0;
final int parentDepth = depth(node.getParentNode());
if (! node.isVisible() || AlwaysUnfoldedNode.isConnectorNode(node)) {
return parentDepth;
}
else
return parentDepth + 1;
}
public JPopupMenu createFoldingPopupMenu(NodeModel node){
return new FoldingPopupMenu(node);
}
}