blob: a1ecb3eb76103fe708e0e9b5f3183460e90e2648 [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.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.TipManager;
import org.eclipse.tips.core.TipProvider;
import org.eclipse.tips.core.TipProviderListener;
import org.eclipse.tips.core.internal.LogUtil;
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 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 TipProviderListener fProviderListener;
private Composite fSelectedProviderButton;
private HashMap<String, Image> fProviderImageCache = new HashMap<>();
private int fIconSize = 48;
/**
* 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);
GridData gd_leftButton = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1);
gd_leftButton.widthHint = fIconSize / 2 + 8;
gd_leftButton.heightHint = fIconSize;
fLeftButton.setLayoutData(gd_leftButton);
fLeftButton.setImage(getImage("icons/" + fIconSize + "/aleft.png"));
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);
GridData gd_rightButton = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1);
gd_rightButton.widthHint = fIconSize / 2 + 8;
gd_rightButton.heightHint = fIconSize;
fRightButton.setLayoutData(gd_rightButton);
fRightButton.setImage(getImage("icons/" + fIconSize + "/aright.png"));
fRightButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
scrollRight();
}
});
setupDisposeListener();
setupProviderListener();
}
private void setupDisposeListener() {
addListener(SWT.Dispose, event -> {
fTipManager.getListenerManager().removeProviderListener(fProviderListener);
fProviderImageCache.values().forEach(img -> {
if (!img.isDisposed()) {
img.dispose();
}
});
});
}
private void setupProviderListener() {
fProviderListener = provider -> getDisplay().asyncExec(() -> load());
}
private static Image getImage(String icon) {
return ResourceManager.getPluginImage("org.eclipse.tips.ui", icon);
}
/**
* 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()) {
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()) {
fRightButton.setEnabled(false);
fLeftButton.setEnabled(false);
fLeftButton.setImage(getImage("icons/" + fIconSize + "/aright.png"));
fRightButton.setImage(getImage("icons/" + fIconSize + "/aleft.png"));
}
} else {
if (!fRightButton.isEnabled()) {
fRightButton.setEnabled(true);
fLeftButton.setEnabled(true);
fLeftButton.setImage(getImage("icons/" + fIconSize + "/aleft.png"));
fRightButton.setImage(getImage("icons/" + fIconSize + "/aright.png"));
}
}
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(true).isEmpty()) {
fSelectedProvider = provider;
notifyListeners(fSelectedProvider);
}
createProviderButton(providers.get(i + fSliderIndex), newSpacing, i);
}
}
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.getListenerManager().addProviderListener(fProviderListener);
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 (fSelectedProvider.equals(provider)) {
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(true).isEmpty()) {
return getProviderImage(provider, selectProviderImage(provider));
}
GC gc2 = new GC(providerButton);
gc2.setAdvanced(true);
gc2.setFont(SWTResourceManager.getBoldFont(gc2.getFont()));
int tipCount = provider.getTips(true).size();
Point textExtent = gc2.textExtent(tipCount + "");
gc2.dispose();
Image image = null;
if (tipCount > 9) {
image = new Image(getDisplay(), textExtent.x + 8, textExtent.y + 5);
} else {
image = new Image(getDisplay(), textExtent.x + 15, textExtent.y + 5);
}
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(210);
gc.setTextAntialias(SWT.ON);
if (tipCount > 9) {
gc.fillOval(0, 0, textExtent.x + 8, textExtent.y + 5);
gc.drawText(tipCount + "", 4, 2, true);
} else {
gc.fillOval(0, 0, textExtent.x + 15, textExtent.y + 5);
gc.drawText(tipCount + "", 8, 2, true);
}
Image result = ResourceManager.decorateImage(getProviderImage(provider, selectProviderImage(provider)), image,
SWTResourceManager.TOP_RIGHT);
image.dispose();
gc.dispose();
return result;
}
}