/******************************************************************************* * Copyright (c) 2015 Pivotal, Inc. * 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: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.boot.dash.util; import java.util.HashMap; import java.util.Map; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.progress.UIJob; /** * A ColumnViewerAnimator manages a Job that periodically updates labels for * 'animated' label icons in a ColumnViewer (actually, only TableViewer or * TreeViewer are supported with current implementation). * * @author Kris De Volder */ public class ColumnViewerAnimator { /** * Target provides abstraction needed so that we can 'setImage' easily on ViewerCell * from either Table or Tree viewer. */ private static abstract class Target { private static final Target NULL_TARGET = new Target(null) { //Using 'widget = null' means this Target behaves like a disposed widget, which // means that the animator will ignore / remove it and not keep a Job spinning // to essentially do nothing animating it. void setImage(Image image) { } }; private Widget widget; public Target(Widget widget) { this.widget = widget; } static Target from(ViewerCell cell) { final Widget item = cell.getItem(); final int col = cell.getColumnIndex(); if (item instanceof TableItem) { return new Target(item) { void setImage(Image image) { ((TableItem)item).setImage(col, image); } }; } else if (item instanceof TreeItem) { return new Target(item) { void setImage(Image image) { ((TreeItem)item).setImage(col, image); } }; } else { return NULL_TARGET; } } abstract void setImage(Image image); public boolean isDisposed() { return widget==null || widget.isDisposed(); } } public class CellAnimation { public final Image[] imgs; public final Target item; //could be TableItem or TreeItem public CellAnimation(ViewerCell cell, Image[] imgs) { this.item = Target.from(cell); this.imgs = imgs; } } protected static final long INTERVAL = 100; private int animationCounter = 0; private ColumnViewer tv; public ColumnViewerAnimator(ColumnViewer tv) { this.tv = tv; } private Map<ViewerCell, CellAnimation> animatedElements = new HashMap<ViewerCell, CellAnimation>(); private Job job; public void setAnimation(ViewerCell cell, Image[] images) { if (images==null || images.length==0) { cell.setImage(null); stopAnimation(cell); } else if (images.length==1) { stopAnimation(cell); cell.setImage(images[0]); } else { cell.setImage(currentImage(images)); startAnimation(cell, images); } } private synchronized void stopAnimation(Object e) { animatedElements.remove(e); } private synchronized void startAnimation(ViewerCell cell, Image[] imgs) { animatedElements.put(cell, new CellAnimation(cell, imgs)); ensureJob(); job.schedule(); } private synchronized CellAnimation[] getAnimations() { //Copy elements to avoid CME. return animatedElements.values().toArray(new CellAnimation[animatedElements.size()]); } private void ensureJob() { if (job==null) { job = new UIJob("Animate table icons") { @Override public IStatus runInUIThread(IProgressMonitor monitor) { if (!tv.getControl().isDisposed()) { animationCounter++; for (CellAnimation a : getAnimations()) { Image[] imgs = a.imgs; if (a.item.isDisposed()) { //See bug: https://www.pivotaltracker.com/story/show/100608788 stopAnimation(a); } else { a.item.setImage(imgs[animationCounter%imgs.length]); } } if (job!=null && animatedElements.size()>0) { job.schedule(INTERVAL); } } return Status.OK_STATUS; } }; job.setSystem(true); } } private Image currentImage(Image[] images) { return images[animationCounter%images.length]; } public void dispose() { job = null; } }