blob: 8f8be216d29b1fc815dbc1c6d904beb21286f18e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2018 Remain Software
* 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:
* wim.jongman@remainsoftware.com - initial API and implementation
*******************************************************************************/
package org.eclipse.tips.ui.internal;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.tips.core.TipImage;
import org.eclipse.tips.core.TipProvider;
import org.eclipse.tips.core.internal.LogUtil;
import org.eclipse.tips.core.internal.TipManager;
import org.eclipse.tips.ui.internal.util.ImageUtil;
import org.eclipse.tips.ui.internal.util.ResourceManager;
import org.eclipse.tips.ui.internal.util.SWTResourceManager;
@SuppressWarnings("restriction")
public class Slider extends Composite {
private static final int RESIZE_RELOAD_DELAY = 100;
private Composite fScroller;
private TipProvider fSelectedProvider;
private int fSpacing = 5;
private int fSliderIndex = 0;
private List<ProviderSelectionListener> fListeners = new ArrayList<>();
private TipManager fTipManager;
private Button fLeftButton;
private Button fRightButton;
private Composite fSelectedProviderButton;
private HashMap<String, Image> fProviderImageCache = new HashMap<>();
private int fIconSize = 48;
private PropertyChangeListener fPropertyChangeListener;
private int fLeftRightButtonWidth;
private long fLastResizeEventTime;
private boolean fResizeRequestPending;
/**
* Constructor for the Slider widget.
*
* @param parent the parent
* @param style the SWT style bits
*/
public Slider(Composite parent, int style) {
super(parent, style);
GridLayout layout = new GridLayout(3, false);
layout.marginHeight = 0;
setLayout(layout);
fLeftButton = new Button(this, SWT.FLAT);
fLeftRightButtonWidth = fIconSize / 2 + 8;
setLeftRightButtonGridData(fLeftButton, fLeftRightButtonWidth);
fLeftButton.setImage(getImage("icons/" + fIconSize + "/aleft.png")); //$NON-NLS-1$ //$NON-NLS-2$
fLeftButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
scrollLeft();
}
});
fScroller = new Composite(this, SWT.DOUBLE_BUFFERED);
GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1);
layoutData.heightHint = fIconSize + 4;
fScroller.setLayoutData(layoutData);
fRightButton = new Button(this, SWT.FLAT);
setLeftRightButtonGridData(fRightButton, fLeftRightButtonWidth);
fRightButton.setImage(getImage("icons/" + fIconSize + "/aright.png")); //$NON-NLS-1$ //$NON-NLS-2$
fRightButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
scrollRight();
}
});
setupDisposeListener();
setupProviderListener();
setupResizeListener();
}
// Recalculation of slider is expensive. Make sure it is done only once when
// resizing.
private void setupResizeListener() {
addListener(SWT.Resize, event -> {
fLastResizeEventTime = System.currentTimeMillis();
if (!fResizeRequestPending) {
fResizeRequestPending = true;
submitResizeExecution();
}
});
}
private void submitResizeExecution() {
getDisplay().timerExec(RESIZE_RELOAD_DELAY / 2, () -> {
long currentTime = System.currentTimeMillis();
if (currentTime - fLastResizeEventTime > RESIZE_RELOAD_DELAY) {
load();
fResizeRequestPending = false;
}
if (fResizeRequestPending) {
submitResizeExecution();
}
});
}
private void setLeftRightButtonGridData(Button pButton, int pWidth) {
GridData gridData = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1);
gridData.widthHint = pWidth;
gridData.heightHint = fIconSize;
pButton.setLayoutData(gridData);
}
private void setupDisposeListener() {
addListener(SWT.Dispose, event -> {
fTipManager.getChangeSupport().removePropertyChangeListener(fPropertyChangeListener);
fProviderImageCache.values().forEach(img -> {
if (!img.isDisposed()) {
img.dispose();
}
});
});
}
private void setupProviderListener() {
fPropertyChangeListener = provider -> {
if (!isDisposed()) {
getDisplay().asyncExec(() -> load());
}
};
}
private static Image getImage(String icon) {
return ResourceManager.getPluginImage("org.eclipse.tips.ui", icon); //$NON-NLS-1$
}
/**
* Loads or reloads the provider list. If you want to update the read count of
* the current button or all buttons then check the {@link #updateButton()} and
* {@link #updateButtons()} methods.
*
*/
public void load() {
if (isDisposed() || fScroller.isDisposed() || fTipManager == null) {
return;
}
Arrays.stream(fScroller.getChildren()).filter(control -> !control.isDisposed())
.forEach(control -> control.dispose());
List<TipProvider> providers = fTipManager.getProviders();
int spaceCount = Math.floorDiv(fScroller.getBounds().width, (fIconSize + fSpacing));
int providerCount = providers.size();
if (fSliderIndex > 0 && spaceCount >= providerCount) {
fSliderIndex = 0;
}
if (spaceCount >= providerCount) {
if (fRightButton.isEnabled()) {
enableLeftRightButtons(false);
}
} else {
if (!fRightButton.isEnabled()) {
enableLeftRightButtons(true);
}
}
int emptyPixelsLeft = Math.floorMod(fScroller.getBounds().width, (fIconSize + fSpacing));
int newSpacing = fSpacing + (emptyPixelsLeft / (spaceCount + 1));
for (int i = 0; i < Math.min(providerCount - fSliderIndex, spaceCount); i++) {
TipProvider provider = providers.get(i + fSliderIndex);
if (fSelectedProvider == null && !provider.getTips().isEmpty()) {
fSelectedProvider = provider;
notifyListeners(fSelectedProvider);
}
createProviderButton(providers.get(i + fSliderIndex), newSpacing, i);
}
setBackground(fScroller.getBackground());
}
private void enableLeftRightButtons(boolean enable) {
fRightButton.setEnabled(enable);
fRightButton.setVisible(enable);
setLeftRightButtonGridData(fRightButton, enable ? fLeftRightButtonWidth : 0);
fLeftButton.setEnabled(enable);
fLeftButton.setVisible(enable);
setLeftRightButtonGridData(fLeftButton, enable ? fLeftRightButtonWidth : 0);
fRightButton.requestLayout();
fLeftButton.requestLayout();
}
private Composite createProviderButton(TipProvider provider, int spacing, int index) {
Composite button = new Composite(fScroller, SWT.DOUBLE_BUFFERED);
button.setToolTipText(provider.getDescription());
button.setBackground(fScroller.getBackground());
button.setSize(fIconSize + 4, fIconSize + 4);
button.setLocation((index * (fIconSize + spacing) + spacing - fSpacing), 2);
button.addPaintListener(e -> {
if (fSelectedProvider == provider) {
fSelectedProviderButton = button;
}
paintButton(e.gc, button, provider);
});
button.addListener(SWT.MouseEnter, event -> button.redraw());
button.addListener(SWT.MouseExit, event -> button.redraw());
button.addListener(SWT.MouseUp, event -> {
if (fSelectedProvider == provider) {
return;
}
fSelectedProvider = provider;
if (fSelectedProviderButton != null && !fSelectedProviderButton.isDisposed()) {
fSelectedProviderButton.redraw();
}
fSelectedProviderButton = button;
fSelectedProviderButton.redraw();
notifyListeners(provider);
});
if (fSelectedProvider == provider) {
fSelectedProviderButton = button;
}
return button;
}
/**
* Updates the read count of the currently selected button.
*/
public void updateButton() {
if (fSelectedProviderButton != null && !fSelectedProviderButton.isDisposed()) {
getDisplay().asyncExec(() -> fSelectedProviderButton.redraw());
}
}
/**
* Calls redraw on all buttons to update the badge counter.
*/
public void updateButtons() {
if (!isDisposed()) {
getDisplay().asyncExec(() -> {
if (!fScroller.isDisposed()) {
for (Control control : fScroller.getChildren()) {
if (control instanceof Composite && !control.isDisposed()) {
control.redraw();
}
}
}
});
}
}
/**
* Sets the {@link TipManager}.
*
* @param tipManager the {@link TipManager}
* @return this
*/
public Slider setTipManager(TipManager tipManager) {
fTipManager = tipManager;
fTipManager.getChangeSupport().addPropertyChangeListener(TipProvider.PROP_READY, fPropertyChangeListener);
fIconSize = 48;
load();
return this;
}
private void notifyListeners(TipProvider provider) {
fListeners.forEach(listener -> {
try {
listener.selected(provider);
} catch (Exception e) {
fTipManager.log(LogUtil.error(getClass(), e));
}
});
}
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
Point point = new Point(fIconSize * 3, fIconSize + fSpacing + fSpacing);
return point;
}
protected void scrollRight() {
if (fSliderIndex < fTipManager.getProviders().size() - 1) {
fSliderIndex++;
load();
}
}
protected void scrollLeft() {
if (fSliderIndex > 0) {
fSliderIndex--;
load();
}
}
/**
* @return the current selected {@link TipProvider}.
*/
public TipProvider getTipProvider() {
return fSelectedProvider;
}
/**
* Sets the passed provider as the selected provider in the slider.
*
* @param provider the new provider for the slider.
*
* @return this
*/
public Slider setTipProvider(TipProvider provider) {
fSelectedProvider = provider;
updateButtons();
return this;
}
/**
* Adds the listener to the list of listeners to be called when an event with a
* {@link TipProvider} occurs.
*
* @param listener the {@link ProviderSelectionListener}
* @return this
*/
public Slider addTipProviderListener(ProviderSelectionListener listener) {
fListeners.add(listener);
return this;
}
/**
* Removes the listener from the list.
*
* @param listener the {@link ProviderSelectionListener}
* @return this
*/
public Slider removeTipProviderListener(ProviderSelectionListener listener) {
fListeners.remove(listener);
return this;
}
private void paintButton(GC gc, Composite providerButton, TipProvider provider) {
gc.setAdvanced(true);
if (!gc.getAdvanced()) {
throw new RuntimeException(Messages.Slider_13);
}
if (provider.equals(fSelectedProvider)) {
gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION));
gc.drawRectangle(0, 0, fIconSize + 3, fIconSize + 3);
} else {
gc.setForeground(fLeftButton.getForeground());
gc.setBackground(fLeftButton.getBackground());
boolean mouseIn = getDisplay().getCursorControl() == providerButton;
if (mouseIn) {
gc.drawRectangle(0, 0, fIconSize + 3, fIconSize + 3);
} else {
gc.setBackground(fScroller.getBackground());
}
}
gc.fillRectangle(2, 2, fIconSize, fIconSize);
Image overlay = getUnreadOverlay(providerButton, provider);
gc.drawImage(overlay, 2, 2);
if (overlay != getProviderImage(provider, selectProviderImage(provider))) {
overlay.dispose();
}
}
private TipImage selectProviderImage(TipProvider provider) {
return provider.getImage();
}
private Image getProviderImage(TipProvider provider, TipImage image) {
if (!fProviderImageCache.containsKey(provider.getID())) {
try {
fProviderImageCache.put(provider.getID(),
new Image(getDisplay(), ImageUtil.decodeToImage(image.getBase64Image())));
} catch (Exception e) {
fTipManager.log(LogUtil.error(getClass(), e));
return null;
}
}
return fProviderImageCache.get(provider.getID());
}
private Image getUnreadOverlay(Composite providerButton, TipProvider provider) {
if (provider.getTips().isEmpty()) {
return getProviderImage(provider, selectProviderImage(provider));
}
int tipCount = provider.getTips().size();
Point textExtent = getTipCountTextSize(providerButton, tipCount);
Image image = null;
int imageHeight = textExtent.y + 5;
if (tipCount > 9) {
image = new Image(getDisplay(), textExtent.x + 8, imageHeight);
} else {
image = new Image(getDisplay(), imageHeight, imageHeight);
}
ImageData data = image.getImageData();
data.transparentPixel = data.getPixel(0, 0);
image.dispose();
image = new Image(getDisplay(), data);
GC gc = new GC(image);
gc.setAdvanced(true);
if (fTipManager.mustServeReadTips()) {
gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_DARK_GREEN));
} else {
gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_RED));
}
gc.setForeground(getDisplay().getSystemColor(SWT.COLOR_WHITE));
gc.setFont(SWTResourceManager.getBoldFont(gc.getFont()));
gc.setAlpha(200);
gc.setTextAntialias(SWT.ON);
if (tipCount > 9) {
gc.fillOval(0, 0, textExtent.x + 8, imageHeight);
gc.drawText(tipCount + "", 4, 2, true); //$NON-NLS-1$
} else {
gc.fillOval(0, 0, imageHeight, imageHeight);
gc.drawText(tipCount + "", (imageHeight - textExtent.x + 1) / 2, 2, true); //$NON-NLS-1$
}
Image result = ResourceManager.decorateImage(getProviderImage(provider, selectProviderImage(provider)), image,
SWTResourceManager.TOP_RIGHT);
image.dispose();
gc.dispose();
return result;
}
private Point getTipCountTextSize(Composite providerButton, int tipCount) {
GC gc2 = new GC(providerButton);
gc2.setAdvanced(true);
gc2.setFont(SWTResourceManager.getBoldFont(gc2.getFont()));
Point textExtent = gc2.textExtent(tipCount + ""); //$NON-NLS-1$
gc2.dispose();
return textExtent;
}
}