All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.vcs.log.graph.collapsing.CollapsedActionManager Maven / Gradle / Ivy

/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.vcs.log.graph.collapsing;

import com.intellij.openapi.util.Condition;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcs.log.graph.actions.GraphAction;
import com.intellij.vcs.log.graph.api.EdgeFilter;
import com.intellij.vcs.log.graph.api.LinearGraph;
import com.intellij.vcs.log.graph.api.elements.GraphEdge;
import com.intellij.vcs.log.graph.api.elements.GraphEdgeType;
import com.intellij.vcs.log.graph.api.elements.GraphElement;
import com.intellij.vcs.log.graph.api.elements.GraphNode;
import com.intellij.vcs.log.graph.api.permanent.PermanentGraphInfo;
import com.intellij.vcs.log.graph.impl.facade.GraphChanges;
import com.intellij.vcs.log.graph.impl.facade.GraphChangesUtil;
import com.intellij.vcs.log.graph.impl.facade.LinearGraphController.LinearGraphAction;
import com.intellij.vcs.log.graph.impl.facade.LinearGraphController.LinearGraphAnswer;
import com.intellij.vcs.log.graph.impl.visible.LinearFragmentGenerator;
import com.intellij.vcs.log.graph.impl.visible.LinearFragmentGenerator.GraphFragment;
import com.intellij.vcs.log.graph.utils.LinearGraphUtils;
import com.intellij.vcs.log.graph.utils.UnsignedBitSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.awt.*;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

class CollapsedActionManager {

  @Nullable
  public static LinearGraphAnswer performAction(@NotNull CollapsedController graphController,
                                                @NotNull LinearGraphAction action) {
    ActionContext context = new ActionContext(graphController.getCollapsedGraph(), graphController.getPermanentGraphInfo(), action);

    for (ActionCase actionCase : FILTER_ACTION_CASES) {
      if (actionCase.supportedActionTypes().contains(context.getActionType())) {
        LinearGraphAnswer graphAnswer = actionCase.performAction(context);
        if (graphAnswer != null) return graphAnswer;
      }
    }
    return null;
  }

  public static void expandNodes(@NotNull final CollapsedGraph collapsedGraph, Set nodesToShow) {
    FragmentGenerator generator =
      new FragmentGenerator(LinearGraphUtils.asLiteLinearGraph(collapsedGraph.getDelegatedGraph()), new Condition() {
        @Override
        public boolean value(Integer nodeIndex) {
          return collapsedGraph.isNodeVisible(nodeIndex);
        }
      });

    CollapsedGraph.Modification modification = collapsedGraph.startModification();

    for (Integer nodeToShow : nodesToShow) {
      if (modification.isNodeShown(nodeToShow)) continue;

      FragmentGenerator.GreenFragment fragment = generator.getGreenFragmentForCollapse(nodeToShow, Integer.MAX_VALUE);
      if (fragment.getUpRedNode() == null ||
          fragment.getDownRedNode() == null ||
          fragment.getUpRedNode().equals(fragment.getDownRedNode())) {
        continue;
      }

      for (Integer n : fragment.getMiddleGreenNodes()) {
        modification.showNode(n);
      }

      modification.removeEdge(GraphEdge.createNormalEdge(fragment.getUpRedNode(), fragment.getDownRedNode(), GraphEdgeType.DOTTED));
    }

    modification.apply();
  }


  private interface ActionCase {
    @Nullable
    LinearGraphAnswer performAction(@NotNull ActionContext context);

    @NotNull
    Set supportedActionTypes();
  }

  private static class ActionContext {
    @NotNull private final CollapsedGraph myCollapsedGraph;
    @NotNull private final LinearGraphAction myGraphAction;
    @NotNull private final FragmentGenerators myDelegatedFragmentGenerators;
    @NotNull private final FragmentGenerators myCompiledFragmentGenerators;

    private ActionContext(@NotNull CollapsedGraph collapsedGraph,
                          @NotNull PermanentGraphInfo permanentGraphInfo,
                          @NotNull LinearGraphAction graphAction) {
      myCollapsedGraph = collapsedGraph;
      myGraphAction = graphAction;
      myDelegatedFragmentGenerators =
        new FragmentGenerators(collapsedGraph.getDelegatedGraph(), permanentGraphInfo, collapsedGraph.getMatchedNodeId());
      myCompiledFragmentGenerators =
        new FragmentGenerators(collapsedGraph.getCompiledGraph(), permanentGraphInfo, collapsedGraph.getMatchedNodeId());
    }

    @NotNull
    GraphAction.Type getActionType() {
      return myGraphAction.getType();
    }

    @Nullable
    GraphElement getAffectedGraphElement() {
      return myGraphAction.getAffectedElement() == null ? null : myGraphAction.getAffectedElement().getGraphElement();
    }

    @NotNull
    LinearGraph getDelegatedGraph() {
      return myCollapsedGraph.getDelegatedGraph();
    }

    @NotNull
    LinearGraph getCompiledGraph() {
      return myCollapsedGraph.getCompiledGraph();
    }

    int convertToDelegateNodeIndex(int compiledNodeIndex) {
      return myCollapsedGraph.convertToDelegateNodeIndex(compiledNodeIndex);
    }

    @NotNull
    Set convertToDelegateNodeIndex(@NotNull Collection compiledNodeIndexes) {
      return ContainerUtil.map2Set(compiledNodeIndexes, new Function() {
        @Override
        public Integer fun(Integer nodeIndex) {
          return convertToDelegateNodeIndex(nodeIndex);
        }
      });
    }

    @NotNull
    GraphEdge convertToDelegateEdge(@NotNull GraphEdge compiledEdge) {
      Integer upNodeIndex = null, downNodeIndex = null;
      if (compiledEdge.getUpNodeIndex() != null) upNodeIndex = convertToDelegateNodeIndex(compiledEdge.getUpNodeIndex());
      if (compiledEdge.getDownNodeIndex() != null) downNodeIndex = convertToDelegateNodeIndex(compiledEdge.getDownNodeIndex());

      return new GraphEdge(upNodeIndex, downNodeIndex, compiledEdge.getTargetId(), compiledEdge.getType());
    }

  }

  private static class FragmentGenerators {
    @NotNull private final FragmentGenerator fragmentGenerator;
    @NotNull private final LinearFragmentGenerator linearFragmentGenerator;

    private FragmentGenerators(@NotNull final LinearGraph linearGraph,
                               @NotNull PermanentGraphInfo permanentGraphInfo,
                               @NotNull final UnsignedBitSet matchedNodeId) {
      fragmentGenerator = new FragmentGenerator(LinearGraphUtils.asLiteLinearGraph(linearGraph), new Condition() {
        @Override
        public boolean value(Integer nodeIndex) {
          return matchedNodeId.get(linearGraph.getNodeId(nodeIndex));
        }
      });

      Set branchNodeIndexes = LinearGraphUtils.convertIdsToNodeIndexes(linearGraph, permanentGraphInfo.getBranchNodeIds());
      linearFragmentGenerator = new LinearFragmentGenerator(LinearGraphUtils.asLiteLinearGraph(linearGraph), branchNodeIndexes);
    }
  }

  private final static ActionCase LINEAR_COLLAPSE_CASE = new ActionCase() {
    @Nullable
    @Override
    public LinearGraphAnswer performAction(@NotNull final ActionContext context) {
      if (isForDelegateGraph(context)) return null;

      GraphElement affectedGraphElement = context.getAffectedGraphElement();
      if (affectedGraphElement == null) return null;

      LinearFragmentGenerator compiledLinearFragmentGenerator = context.myCompiledFragmentGenerators.linearFragmentGenerator;
      FragmentGenerator compiledFragmentGenerator = context.myCompiledFragmentGenerators.fragmentGenerator;

      if (context.getActionType() == GraphAction.Type.MOUSE_OVER) {
        GraphFragment fragment = compiledLinearFragmentGenerator.getPartLongFragment(affectedGraphElement);
        if (fragment == null) return null;
        Set middleCompiledNodes = compiledFragmentGenerator.getMiddleNodes(fragment.upNodeIndex, fragment.downNodeIndex, false);
        return LinearGraphUtils.createSelectedAnswer(context.getCompiledGraph(), middleCompiledNodes);
      }

      GraphFragment fragment = compiledLinearFragmentGenerator.getLongFragment(affectedGraphElement);
      if (fragment == null) return null;

      Set middleCompiledNodes = compiledFragmentGenerator.getMiddleNodes(fragment.upNodeIndex, fragment.downNodeIndex, true);
      Set dottedCompiledEdges = ContainerUtil.newHashSet();
      for (Integer middleNodeIndex : middleCompiledNodes) {
        dottedCompiledEdges.addAll(ContainerUtil.filter(context.getCompiledGraph().getAdjacentEdges(middleNodeIndex, EdgeFilter.NORMAL_ALL),
                                                        new Condition() {
                                                          @Override
                                                          public boolean value(GraphEdge edge) {
                                                            return edge.getType() == GraphEdgeType.DOTTED;
                                                          }
                                                        }));
      }

      int upNodeIndex = context.convertToDelegateNodeIndex(fragment.upNodeIndex);
      int downNodeIndex = context.convertToDelegateNodeIndex(fragment.downNodeIndex);
      Set middleNodes = context.convertToDelegateNodeIndex(middleCompiledNodes);
      Set dottedEdges = ContainerUtil.map2Set(dottedCompiledEdges, new Function() {
        @Override
        public GraphEdge fun(GraphEdge edge) {
          return context.convertToDelegateEdge(edge);
        }
      });

      CollapsedGraph.Modification modification = context.myCollapsedGraph.startModification();
      for (GraphEdge edge : dottedEdges) modification.removeEdge(edge);
      for (Integer middleNode : middleNodes) modification.hideNode(middleNode);
      modification.createEdge(new GraphEdge(upNodeIndex, downNodeIndex, null, GraphEdgeType.DOTTED));

      modification.apply();
      return new LinearGraphAnswer(GraphChangesUtil.SOME_CHANGES, null, null, null);
    }

    @NotNull
    @Override
    public Set supportedActionTypes() {
      return ContainerUtil.set(GraphAction.Type.MOUSE_CLICK, GraphAction.Type.MOUSE_OVER);
    }
  };

  private final static ActionCase EXPAND_ALL = new ActionCase() {
    @Nullable
    @Override
    public LinearGraphAnswer performAction(@NotNull ActionContext context) {
      CollapsedGraph.Modification modification = context.myCollapsedGraph.startModification();
      modification.removeAdditionalEdges();
      modification.resetNodesVisibility();
      return new DeferredGraphAnswer(GraphChangesUtil.SOME_CHANGES, null, null, null, modification);
    }

    @NotNull
    @Override
    public Set supportedActionTypes() {
      return Collections.singleton(GraphAction.Type.BUTTON_EXPAND);
    }
  };

  private final static ActionCase COLLAPSE_ALL = new ActionCase() {
    @Nullable
    @Override
    public LinearGraphAnswer performAction(@NotNull ActionContext context) {
      CollapsedGraph.Modification modification = context.myCollapsedGraph.startModification();
      modification.removeAdditionalEdges();
      modification.resetNodesVisibility();

      LinearGraph delegateGraph = context.getDelegatedGraph();
      for (int nodeIndex = 0; nodeIndex < delegateGraph.nodesCount(); nodeIndex++) {
        if (modification.isNodeHidden(nodeIndex)) continue;

        GraphFragment fragment = context.myDelegatedFragmentGenerators.linearFragmentGenerator.getLongDownFragment(nodeIndex);
        if (fragment != null) {
          Set middleNodes =
            context.myDelegatedFragmentGenerators.fragmentGenerator.getMiddleNodes(fragment.upNodeIndex, fragment.downNodeIndex, true);

          for (Integer nodeIndexForHide : middleNodes) modification.hideNode(nodeIndexForHide);
          modification.createEdge(new GraphEdge(fragment.upNodeIndex, fragment.downNodeIndex, null, GraphEdgeType.DOTTED));
        }
      }

      return new DeferredGraphAnswer(GraphChangesUtil.SOME_CHANGES, null, null, null, modification);
    }

    @NotNull
    @Override
    public Set supportedActionTypes() {
      return Collections.singleton(GraphAction.Type.BUTTON_COLLAPSE);
    }
  };

  private final static ActionCase LINEAR_EXPAND_CASE = new ActionCase() {
    @Nullable
    @Override
    public LinearGraphAnswer performAction(@NotNull ActionContext context) {
      if (isForDelegateGraph(context)) return null;

      GraphEdge dottedEdge = getDottedEdge(context.getAffectedGraphElement(), context.getCompiledGraph());

      if (dottedEdge != null) {
        int upNodeIndex = context.convertToDelegateNodeIndex(assertInt(dottedEdge.getUpNodeIndex()));
        int downNodeIndex = context.convertToDelegateNodeIndex(assertInt(dottedEdge.getDownNodeIndex()));

        if (context.getActionType() == GraphAction.Type.MOUSE_OVER) {
          return LinearGraphUtils.createSelectedAnswer(context.getDelegatedGraph(), ContainerUtil.set(upNodeIndex, downNodeIndex));
        }

        Set middleNodes = context.myDelegatedFragmentGenerators.fragmentGenerator.getMiddleNodes(upNodeIndex, downNodeIndex, true);

        CollapsedGraph.Modification modification = context.myCollapsedGraph.startModification();
        for (Integer middleNode : middleNodes) {
          modification.showNode(middleNode);
        }
        modification.removeEdge(new GraphEdge(upNodeIndex, downNodeIndex, null, GraphEdgeType.DOTTED));

        modification.apply();
        return new LinearGraphAnswer(GraphChangesUtil.SOME_CHANGES, null, null, null);
      }

      return null;
    }

    @NotNull
    @Override
    public Set supportedActionTypes() {
      return ContainerUtil.set(GraphAction.Type.MOUSE_CLICK, GraphAction.Type.MOUSE_OVER);
    }
  };

  private final static List FILTER_ACTION_CASES =
    ContainerUtil.list(COLLAPSE_ALL, EXPAND_ALL, LINEAR_EXPAND_CASE, LINEAR_COLLAPSE_CASE);

  private static boolean isForDelegateGraph(@NotNull ActionContext context) {
    GraphElement affectedGraphElement = context.getAffectedGraphElement();
    if (affectedGraphElement == null) return false;

    GraphEdge dottedEdge = getDottedEdge(context.getAffectedGraphElement(), context.getCompiledGraph());
    if (dottedEdge != null) {
      int upNodeIndex = context.convertToDelegateNodeIndex(assertInt(dottedEdge.getUpNodeIndex()));
      int downNodeIndex = context.convertToDelegateNodeIndex(assertInt(dottedEdge.getDownNodeIndex()));

      if (!context.myCollapsedGraph.isMyCollapsedEdge(upNodeIndex, downNodeIndex)) return true;
    }
    return false;
  }

  private CollapsedActionManager() {
  }

  private static int assertInt(@Nullable Integer value) {
    assert value != null;
    return value;
  }

  @Nullable
  private static GraphEdge getDottedEdge(@Nullable GraphElement graphElement, @NotNull LinearGraph graph) {
    if (graphElement == null) return null;

    if (graphElement instanceof GraphEdge && ((GraphEdge)graphElement).getType() == GraphEdgeType.DOTTED) return (GraphEdge)graphElement;
    if (graphElement instanceof GraphNode) {
      GraphNode node = (GraphNode)graphElement;
      for (GraphEdge edge : graph.getAdjacentEdges(node.getNodeIndex(), EdgeFilter.NORMAL_ALL)) {
        if (edge.getType() == GraphEdgeType.DOTTED) return edge;
      }
    }

    return null;
  }

  private static class DeferredGraphAnswer extends LinearGraphAnswer {
    @NotNull private final CollapsedGraph.Modification myModification;

    public DeferredGraphAnswer(@Nullable GraphChanges graphChanges,
                               @Nullable Cursor cursorToSet,
                               @Nullable Integer commitToJump,
                               @Nullable Set selectedNodeIds,
                               @NotNull CollapsedGraph.Modification modification) {
      super(graphChanges, cursorToSet, commitToJump, selectedNodeIds);
      myModification = modification;
    }

    @Nullable
    @Override
    public Runnable getGraphUpdater() {
      return new Runnable() {
        @Override
        public void run() {
          myModification.apply();
        }
      };
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy