/*******************************************************************************
* Copyright (c) 2011, 2012 Anton Gorenkov
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Anton Gorenkov - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.testsrunner.internal.ui.view;
import java.text.MessageFormat;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.testsrunner.internal.model.ITestingSessionsManagerListener;
import org.eclipse.cdt.testsrunner.internal.model.TestingSessionsManager;
import org.eclipse.cdt.testsrunner.model.ITestItem;
import org.eclipse.cdt.testsrunner.model.ITestingSession;
import org.eclipse.cdt.testsrunner.model.ITestingSessionListener;
import org.eclipse.cdt.testsrunner.model.ITestCase;
import org.eclipse.cdt.testsrunner.model.ITestSuite;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.progress.UIJob;
/**
* Tracks and collects the changes in active testing session and updates the UI
* periodically. It allows to significantly improve the UI performance.
*/
public class UIUpdater {
/** Access to the results showing view. */
private ResultsView resultsView;
/** Access to the tests hierarchy showing widget. */
private TestsHierarchyViewer testsHierarchyViewer;
/** Access to the statistics showing widget. */
private ProgressCountPanel progressCountPanel;
/** Listener for the changes in active testing session. */
private ITestingSessionListener sessionListener;
/**
* Specifies whether tests hierarchy scrolling should be done during the
* testing process.
*/
private boolean autoScroll = true;
/** Access to the testing sessions manager. */
private TestingSessionsManager sessionsManager;
/** Listener to handle active testing session change. */
private TestingSessionsManagerListener sessionsManagerListener;
/** Reference to the active testing session. */
ITestingSession testingSession;
/** Storage for the UI changes that should be done on update. */
UIChangesCache uiChangesCache = new UIChangesCache();
/** A job that makes an UI update periodically. */
UpdateUIJob updateUIJob = null;
/** Time interval over which the UI should be updated. */
private static final int REFRESH_INTERVAL = 200;
/**
* Storage for the UI changes that should be done on update.
*/
private class UIChangesCache {
/**
* Specifies whether Progress Counter Panel should be updated during the
* next UI update.
*/
private boolean needProgressCountPanelUpdate;
/**
* Specifies whether view actions should be updated during the next UI
* update.
*/
private boolean needActionsUpdate;
/**
* A test item which path should be shown as a view caption during the
* next UI update.
*/
private ITestItem testItemForNewViewCaption;
/**
* Set of tree objects on which <code>refresh()</code> should be called
* during the next UI update.
*/
private Set<Object> treeItemsToRefresh = new HashSet<Object>();
/**
* Set of tree objects on which <code>update()</code> should be called
* during the next UI update.
*/
private Set<Object> treeItemsToUpdate = new HashSet<Object>();
/** Tree object that should be revealed during the next UI update. */
private Object treeItemToReveal;
/** Map of tree objects that should be expanded or collapsed to their new states. */
private Map<Object, Boolean> treeItemsToExpand = new LinkedHashMap<Object, Boolean>();
UIChangesCache() {
resetChanges();
}
/**
* Schedules the Progress Counter Panel update during the next UI update.
*/
public void scheduleProgressCountPanelUpdate() {
synchronized (this) {
needProgressCountPanelUpdate = true;
}
}
/**
* Schedules the view actions update during the next UI update.
*/
public void scheduleActionsUpdate() {
synchronized (this) {
needActionsUpdate = true;
}
}
/**
* Schedules the view caption update to the path to specified test item
* during the next UI update.
*
* @param testItem specified test item
*/
public void scheduleViewCaptionChange(ITestItem testItem) {
synchronized (this) {
testItemForNewViewCaption = testItem;
}
}
/**
* Schedules the <code>update()</code> call for the specified tree
* object during the next UI update.
*
* @param item tree object to update
*/
public void scheduleTreeItemUpdate(Object item) {
synchronized (this) {
treeItemsToUpdate.add(item);
}
}
/**
* Schedules the revealing of the specified tree object. Overrides
* the previously specified tree object to reveal (if any).
*
* @param item tree object to reveal
*/
public void scheduleTreeItemReveal(Object item) {
synchronized (this) {
treeItemToReveal = item;
}
}
/**
* Schedules the expanding or collapsing of the specified tree object.
* Overrides the previous state for the same tree object (if any).
*
* @param item tree object to expand or collapse
* @param expandedState true if the node is expanded, and false if
* collapsed
*/
public void scheduleTreeItemExpand(Object item, boolean expandedState) {
synchronized (this) {
treeItemsToExpand.put(item, expandedState);
}
}
/**
* Schedules the <code>refresh()</code> call for the specified tree
* object during the next UI update.
*
* @param item tree object to refresh
*/
public void scheduleTreeItemRefresh(Object item) {
synchronized (this) {
treeItemsToRefresh.add(item);
}
}
/**
* Apply any scheduled changes to UI.
*/
public void applyChanges() {
synchronized (this) {
TreeViewer treeViewer = testsHierarchyViewer.getTreeViewer();
// View statistics widgets update
if (needProgressCountPanelUpdate) {
progressCountPanel.updateInfoFromSession();
}
// View actions update
if (needActionsUpdate) {
resultsView.updateActionsFromSession();
}
// View caption update
if (testItemForNewViewCaption != null) {
resultsView.setCaption(
MessageFormat.format(
UIViewMessages.UIUpdater_view_caption_format,
testItemForNewViewCaption.getName(),
TestPathUtils.getTestItemPath(testItemForNewViewCaption.getParent())
)
);
}
// Tree view update
if (!treeItemsToRefresh.isEmpty()) {
for (Object item : treeItemsToRefresh) {
treeViewer.refresh(item, false);
}
}
if (!treeItemsToUpdate.isEmpty()) {
treeViewer.update(treeItemsToUpdate.toArray(), null);
}
if (treeItemToReveal != null) {
treeViewer.reveal(treeItemToReveal);
}
if (!treeItemsToExpand.isEmpty()) {
for (Map.Entry<Object, Boolean> entry : treeItemsToExpand.entrySet()) {
treeViewer.setExpandedState(entry.getKey(), entry.getValue());
}
}
// All changes are applied, remove them
resetChangesImpl();
}
}
/**
* Reset all the scheduled changes to UI.
*/
public void resetChanges() {
synchronized (this) {
resetChangesImpl();
}
}
/**
* Reset all the scheduled changes to UI. Note, this method is not
* synchronized so it should be used carefully
*/
private void resetChangesImpl() {
needProgressCountPanelUpdate = false;
needActionsUpdate = false;
testItemForNewViewCaption = null;
treeItemsToUpdate.clear();
treeItemToReveal = null;
treeItemsToExpand.clear();
}
}
/**
* A job that makes an UI update periodically.
*/
private class UpdateUIJob extends UIJob {
/** Controls whether the job should be scheduled again. */
private boolean isRunning = true;
public UpdateUIJob() {
super(UIViewMessages.UIUpdater_update_ui_job);
setSystem(true);
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
if (!resultsView.isDisposed()) {
uiChangesCache.applyChanges();
scheduleSelf();
}
return Status.OK_STATUS;
}
/**
* Schedule self for running after time interval.
*/
public void scheduleSelf() {
schedule(REFRESH_INTERVAL);
}
/**
* Sets the flag that prevents planning this job again.
*/
public void stop() {
isRunning = false;
}
@Override
public boolean shouldSchedule() {
return isRunning;
}
}
/**
* Listener for the changes in active testing session.
*/
private class SessionListener implements ITestingSessionListener {
/**
* Common implementation for test case and test suite entering.
*
* @param testItem test case or test suite
*/
private void enterTestItem(ITestItem testItem) {
uiChangesCache.scheduleViewCaptionChange(testItem);
uiChangesCache.scheduleTreeItemUpdate(testItem);
if (autoScroll) {
uiChangesCache.scheduleTreeItemReveal(testItem);
}
}
@Override
public void enterTestSuite(ITestSuite testSuite) {
enterTestItem(testSuite);
}
@Override
public void exitTestSuite(ITestSuite testSuite) {
uiChangesCache.scheduleTreeItemUpdate(testSuite);
if (autoScroll) {
uiChangesCache.scheduleTreeItemExpand(testSuite, false);
}
}
@Override
public void enterTestCase(ITestCase testCase) {
enterTestItem(testCase);
}
@Override
public void exitTestCase(ITestCase testCase) {
uiChangesCache.scheduleActionsUpdate();
uiChangesCache.scheduleProgressCountPanelUpdate();
uiChangesCache.scheduleTreeItemUpdate(testCase);
}
@Override
public void childrenUpdate(ITestSuite parent) {
uiChangesCache.scheduleTreeItemRefresh(parent);
}
@Override
public void testingStarted() {
resultsView.updateActionsFromSession();
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
resultsView.setCaption(testingSession.getStatusMessage());
progressCountPanel.updateInfoFromSession();
testsHierarchyViewer.getTreeViewer().refresh();
}
});
startUpdateUIJob();
}
@Override
public void testingFinished() {
stopUpdateUIJob();
resultsView.updateActionsFromSession();
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
uiChangesCache.applyChanges();
resultsView.setCaption(testingSession.getStatusMessage());
progressCountPanel.updateInfoFromSession();
testsHierarchyViewer.getTreeViewer().refresh();
testsHierarchyViewer.getTreeViewer().collapseAll();
testsHierarchyViewer.getTreeViewer().expandToLevel(2);
}
});
}
}
/**
* Listener to handle active testing session change.
*/
private class TestingSessionsManagerListener implements ITestingSessionsManagerListener {
@Override
public void sessionActivated(ITestingSession newTestingSession) {
if (testingSession != newTestingSession) {
stopUpdateUIJob();
uiChangesCache.resetChanges();
unsubscribeFromSessionEvent();
testingSession = newTestingSession;
subscribeToSessionEvent();
resultsView.updateActionsFromSession();
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
progressCountPanel.setTestingSession(testingSession);
testsHierarchyViewer.setTestingSession(testingSession);
resultsView.setCaption(testingSession != null ? testingSession.getStatusMessage() : ""); //$NON-NLS-1$
}
});
if (newTestingSession != null && !newTestingSession.isFinished()) {
startUpdateUIJob();
}
}
}
}
public UIUpdater(ResultsView resultsView, TestsHierarchyViewer testsHierarchyViewer, ProgressCountPanel progressCountPanel, TestingSessionsManager sessionsManager) {
this.resultsView = resultsView;
this.testsHierarchyViewer = testsHierarchyViewer;
this.progressCountPanel = progressCountPanel;
this.sessionsManager = sessionsManager;
sessionListener = new SessionListener();
sessionsManagerListener = new TestingSessionsManagerListener();
sessionsManager.addListener(sessionsManagerListener);
}
/**
* Returns whether tests hierarchy scrolling should be done during the
* testing process.
*
* @return auto scroll state
*/
public boolean getAutoScroll() {
return autoScroll;
}
/**
* Sets whether whether tests hierarchy scrolling should be done during the
* testing process.
*
* @param autoScroll new filter state
*/
public void setAutoScroll(boolean autoScroll) {
this.autoScroll = autoScroll;
}
/**
* Disposes of the UI Updater. Make the necessary clean up.
*/
public void dispose() {
unsubscribeFromSessionEvent();
sessionsManager.removeListener(sessionsManagerListener);
}
/**
* Subscribes to the events of currently set testing session.
*/
private void subscribeToSessionEvent() {
if (testingSession != null) {
testingSession.getModelAccessor().addChangesListener(sessionListener);
}
}
/**
* Unsubscribe from the events of currently set testing session.
*/
private void unsubscribeFromSessionEvent() {
if (testingSession != null) {
testingSession.getModelAccessor().removeChangesListener(sessionListener);
}
}
/**
* Starts the UI updating job. Stops the previously running (if any).
*/
private void startUpdateUIJob() {
stopUpdateUIJob();
uiChangesCache.resetChanges();
updateUIJob = new UpdateUIJob();
updateUIJob.scheduleSelf();
}
/**
* Stops the UI updating job (if any).
*/
private void stopUpdateUIJob() {
if (updateUIJob != null) {
updateUIJob.stop();
updateUIJob = null;
}
}
/**
* Fakes the testing session activation and makes all necessary steps to
* handle it.
*/
public void reapplyActiveSession() {
sessionsManagerListener.sessionActivated(sessionsManager.getActiveSession());
}
}