Build output view: progress animation fix

This commit is contained in:
Vladislav.Soroka
2017-10-22 15:06:01 +03:00
parent 070f52a2c9
commit b263d780cd
3 changed files with 76 additions and 72 deletions
@@ -17,6 +17,7 @@ package com.intellij.build;
import com.intellij.build.events.*;
import com.intellij.build.events.impl.FailureImpl;
import com.intellij.concurrency.JobScheduler;
import com.intellij.execution.filters.Filter;
import com.intellij.execution.filters.HyperlinkInfo;
import com.intellij.execution.filters.TextConsoleBuilderFactory;
@@ -46,6 +47,7 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.util.ui.ColumnInfo;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -63,6 +65,8 @@ import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -77,12 +81,14 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
private final SimpleTreeBuilder myBuilder;
private final Map<Object, ExecutionNode> nodesMap = ContainerUtil.newConcurrentMap();
private final ExecutionNodeProgressAnimator myProgressAnimator;
private Set<Update> myRequests = Collections.synchronizedSet(new HashSet<Update>());
private final Project myProject;
private final SimpleTreeStructure myTreeStructure;
private final DetailsHandler myDetailsHandler;
private final TableColumn myTimeColumn;
private volatile int myTimeColumnWidth;
private final AtomicBoolean myDisposed = new AtomicBoolean();
public BuildTreeConsoleView(Project project) {
myProject = project;
@@ -104,7 +110,7 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
}
}
};
final ExecutionNode rootNode = new ExecutionNode(myProject, null);
final ExecutionNode rootNode = new ExecutionNode(myProject);
rootNode.setAutoExpandNode(true);
final ListTreeTableModelOnColumns model = new ListTreeTableModelOnColumns(new DefaultMutableTreeNode(rootNode), COLUMNS);
@@ -180,9 +186,6 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
Disposer.register(this, myBuilder);
myBuilder.initRootNode();
myBuilder.updateFromRoot();
myBuilder.expand(rootNode, null);
myProgressAnimator = new ExecutionNodeProgressAnimator(myBuilder);
JPanel myContentPanel = new JPanel();
myContentPanel.setLayout(new CardLayout());
@@ -209,6 +212,8 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
myDetailsHandler = new DetailsHandler(myProject, tree, myThreeComponentsSplitter);
myThreeComponentsSplitter.setLastComponent(myDetailsHandler.getComponent());
myPanel.add(myThreeComponentsSplitter, BorderLayout.CENTER);
myProgressAnimator = new ExecutionNodeProgressAnimator(this);
}
private ExecutionNode getRootElement() {
@@ -297,6 +302,11 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
@Override
public void dispose() {
myDisposed.set(true);
}
public boolean isDisposed() {
return myDisposed.get();
}
@Override
@@ -306,7 +316,7 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
if (event instanceof StartEvent) {
ExecutionNode rootElement = getRootElement();
if (currentNode == null) {
currentNode = event instanceof StartBuildEvent ? rootElement : new ExecutionNode(myProject, myProgressAnimator);
currentNode = event instanceof StartBuildEvent ? rootElement : new ExecutionNode(myProject);
currentNode.setAutoExpandNode(currentNode == rootElement || parentNode == rootElement);
nodesMap.put(event.getId(), currentNode);
}
@@ -322,12 +332,13 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
String buildTitle = ((StartBuildEvent)event).getBuildTitle();
currentNode.setTitle(buildTitle);
currentNode.setAutoExpandNode(true);
myProgressAnimator.startMovie();
}
}
else {
currentNode = nodesMap.get(event.getId());
if (currentNode == null && event instanceof ProgressBuildEvent) {
currentNode = new ExecutionNode(myProject, myProgressAnimator);
currentNode = new ExecutionNode(myProject);
nodesMap.put(event.getId(), currentNode);
if (parentNode != null) {
parentNode.add(currentNode);
@@ -354,8 +365,12 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
myTimeColumnWidth = timeColumnWidth;
}
}
myBuilder.queueUpdateFrom(currentNode, false, false);
else {
scheduleUpdate(currentNode);
if (event instanceof StartEvent) {
myProgressAnimator.addNode(currentNode);
}
}
if (event instanceof FinishBuildEvent) {
String aHint = event.getHint();
@@ -371,7 +386,7 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
JTree tree = myBuilder.getTree();
if (tree != null && !tree.isRootVisible()) {
ExecutionNode rootElement = getRootElement();
ExecutionNode resultNode = new ExecutionNode(myProject, null);
ExecutionNode resultNode = new ExecutionNode(myProject);
resultNode.setName(StringUtil.toTitleCase(rootElement.getName()));
resultNode.setHint(rootElement.getHint());
resultNode.setEndTime(rootElement.getEndTime());
@@ -379,9 +394,25 @@ public class BuildTreeConsoleView implements ConsoleView, DataProvider, BuildCon
resultNode.setResult(rootElement.getResult());
resultNode.setTooltip(rootElement.getTooltip());
rootElement.add(resultNode);
myBuilder.queueUpdateFrom(resultNode, false, false);
scheduleUpdate(resultNode);
}
}
myProgressAnimator.stopMovie();
myBuilder.updateFromRoot();
}
}
void scheduleUpdate(ExecutionNode executionNode) {
final Update update = new Update(executionNode) {
@Override
public void run() {
myRequests.remove(this);
myBuilder.queueUpdateFrom(executionNode, false, true);
}
};
if (myRequests.add(update)) {
JobScheduler.getScheduler().schedule(update, 100, TimeUnit.MILLISECONDS);
}
}
@@ -38,8 +38,6 @@ import java.util.List;
*/
public class ExecutionNode extends CachingSimpleNode {
private final List<ExecutionNode> myChildrenList = ContainerUtil.newSmartList();
@Nullable
private final ExecutionNodeProgressAnimator myAnimator;
private long startTime;
private long endTime;
@Nullable
@@ -52,9 +50,8 @@ public class ExecutionNode extends CachingSimpleNode {
private EventResult myResult;
private boolean myAutoExpandNode;
public ExecutionNode(Project aProject, @Nullable ExecutionNodeProgressAnimator animator) {
public ExecutionNode(Project aProject) {
super(aProject, null);
myAnimator = animator;
}
@Override
@@ -84,15 +81,6 @@ public class ExecutionNode extends CachingSimpleNode {
if (tooltip != null) {
presentation.setTooltip(tooltip);
}
if (myAnimator != null) {
if (isRunning()) {
myAnimator.addNode(this);
}
else {
myAnimator.removeNode(this);
}
}
}
@Override
@@ -16,25 +16,22 @@
package com.intellij.build;
import com.intellij.icons.AllIcons;
import com.intellij.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Disposer;
import com.intellij.util.Alarm;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* @author Vladislav.Soroka
*/
public class ExecutionNodeProgressAnimator implements Runnable, Disposable {
private static final int FRAMES_COUNT = 8;
private static final int MOVIE_TIME = 1600;
private static final int MOVIE_TIME = 1200;
private static final int FRAME_TIME = MOVIE_TIME / FRAMES_COUNT;
public static final Icon[] FRAMES = new Icon[FRAMES_COUNT];
@@ -42,12 +39,13 @@ public class ExecutionNodeProgressAnimator implements Runnable, Disposable {
private long myLastInvocationTime = -1;
private Alarm myAlarm;
private Set<ExecutionNode> myNodes = new HashSet<>();
private AbstractTreeBuilder myTreeBuilder;
private List<ExecutionNode> myNodes = new SmartList<>();
private BuildTreeConsoleView myTreeView;
public ExecutionNodeProgressAnimator(AbstractTreeBuilder builder) {
Disposer.register(builder, this);
init(builder);
public ExecutionNodeProgressAnimator(BuildTreeConsoleView treeConsoleView) {
Disposer.register(treeConsoleView, this);
myAlarm = new Alarm();
myTreeView = treeConsoleView;
}
static {
@@ -69,67 +67,56 @@ public class ExecutionNodeProgressAnimator implements Runnable, Disposable {
return FRAMES[getCurrentFrameIndex()];
}
/**
* Initializes animator: creates alarm and sets tree builder
*
* @param treeBuilder tree builder
*/
protected void init(final AbstractTreeBuilder treeBuilder) {
myAlarm = new Alarm();
myTreeBuilder = treeBuilder;
}
public void run() {
if (!myNodes.isEmpty()) {
final long time = System.currentTimeMillis();
// optimization:
// we shouldn't repaint if this frame was painted in current interval
if (time - myLastInvocationTime >= FRAME_TIME) {
repaintSubTree();
repaintTree();
myLastInvocationTime = time;
}
}
scheduleRepaint();
}
public void addNode(@NotNull ExecutionNode node) {
if (myNodes.add(node) && myNodes.size() == 1) {
scheduleRepaint();
}
public void addNode(@Nullable final ExecutionNode currentNode) {
myNodes.add(currentNode);
}
public void removeNode(@NotNull ExecutionNode node) {
if (myNodes.remove(node) && myNodes.isEmpty()) {
repaintSubTree();
if (myAlarm != null) {
myAlarm.cancelAllRequests();
}
}
public void startMovie() {
scheduleRepaint();
}
public void stopMovie() {
repaintTree();
myNodes.clear();
cancelAlarm();
}
public void dispose() {
myTreeBuilder = null;
myTreeView = null;
myNodes.clear();
cancelAlarm();
}
private void cancelAlarm() {
if (myAlarm != null) {
myAlarm.cancelAllRequests();
myAlarm = null;
}
}
private void repaintSubTree() {
if (myTreeBuilder == null || myTreeBuilder.isDisposed()) return;
private void repaintTree() {
if (myTreeView == null || myTreeView.isDisposed()) return;
List<ExecutionNode> toRemove = ContainerUtil.newSmartList();
for (ExecutionNode node : myNodes) {
DefaultMutableTreeNode treeNode = myTreeBuilder.getUi().getNodeForElement(node, false);
if (treeNode != null) {
myTreeBuilder.queueUpdateFrom(node, false, false);
}
else {
toRemove.add(node);
for (Iterator<ExecutionNode> iterator = myNodes.iterator(); iterator.hasNext(); ) {
ExecutionNode node = iterator.next();
myTreeView.scheduleUpdate(node);
if (!node.isRunning()) {
iterator.remove();
}
}
myNodes.removeAll(toRemove);
}
private void scheduleRepaint() {
@@ -137,8 +124,6 @@ public class ExecutionNodeProgressAnimator implements Runnable, Disposable {
return;
}
myAlarm.cancelAllRequests();
if (!myNodes.isEmpty()) {
myAlarm.addRequest(this, FRAME_TIME);
}
myAlarm.addRequest(this, FRAME_TIME);
}
}