blob: ce1ccc4cbb63cfd84d483d13ab5be52576a6295b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2018 QNX Software Systems and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Doug Schaefer
*******************************************************************************/
package org.eclipse.launchbar.ui.controls.internal;
import java.util.Comparator;
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.layout.GridLayoutFactory;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.LineAttributes;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
public abstract class CSelector extends Composite {
private IStructuredContentProvider contentProvider;
private ILabelProvider labelProvider;
private Comparator<?> sorter;
private Comparator<?> sorterTop;
private Object input;
private Composite buttonComposite;
private static final int arrowMax = 2;
private Transition arrowTransition;
private Object selection;
private boolean mouseOver;
private Label currentIcon;
private Label currentLabel;
private Shell popup;
private LaunchBarListViewer listViewer;
private Job delayJob;
private MouseListener mouseListener = new MouseAdapter() {
@Override
public void mouseUp(MouseEvent event) {
if (popup == null || popup.isDisposed()) {
setFocus();
openPopup();
} else {
closePopup();
}
}
};
protected boolean myIsFocusAncestor(Control control) {
while (control != null && control != this && !(control instanceof Shell)) {
control = control.getParent();
}
return control == this;
}
private Listener focusOutListener = new Listener() {
private Job closingJob;
@Override
public void handleEvent(Event event) {
switch (event.type) {
case SWT.FocusIn:
if (closingJob != null)
closingJob.cancel();
if (event.widget instanceof Control && myIsFocusAncestor((Control) event.widget)) {
break; // not closing
}
if (!isPopUpInFocus()) {
closePopup();
}
break;
case SWT.FocusOut:
if (isPopUpInFocus()) {
// we about to loose focus from popup children, but it may
// go to another child, lets schedule a job to wait before
// we close
if (closingJob != null)
closingJob.cancel();
closingJob = new Job(Messages.CSelector_0) {
@Override
protected IStatus run(IProgressMonitor monitor) {
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
closePopup();
closingJob = null;
return Status.OK_STATUS;
}
};
closingJob.schedule(300);
}
break;
case SWT.MouseUp:
if (popup != null && !popup.isDisposed()) {
Point loc = getDisplay().getCursorLocation();
if (!popup.getBounds().contains(loc) && !getBounds().contains(getParent().toControl(loc))) {
closePopup();
}
}
break;
default:
break;
}
}
};
public CSelector(Composite parent, int style) {
super(parent, style);
setBackground(getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));
GridLayout mainButtonLayout = new GridLayout();
setLayout(mainButtonLayout);
addPaintListener(e -> {
GC gc = e.gc;
gc.setBackground(getBackground());
gc.setForeground(getOutlineColor());
Point size = getSize();
final int arc = 3;
gc.fillRoundRectangle(0, 0, size.x - 1, size.y - 1, arc, arc);
gc.drawRoundRectangle(0, 0, size.x - 1, size.y - 1, arc, arc);
});
addMouseListener(mouseListener);
}
private boolean isPopUpInFocus() {
Control focusControl = getDisplay().getFocusControl();
if (focusControl != null && focusControl.getShell() == popup) {
return true;
}
return false;
}
@Override
public void dispose() {
super.dispose();
if (popup != null)
popup.dispose();
}
public void setDelayedSelection(final Object element, long millis) {
if (delayJob != null)
delayJob.cancel();
delayJob = new Job(Messages.CSelector_1) {
@Override
protected IStatus run(final IProgressMonitor monitor) {
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
if (isDisposed())
return Status.CANCEL_STATUS;
getDisplay().asyncExec(() -> {
if (monitor.isCanceled())
return;
setSelection(element);
});
return Status.OK_STATUS;
}
};
delayJob.schedule(millis);
}
public void setSelection(Object element) {
if (isDisposed())
return;
this.selection = element;
if (buttonComposite != null)
buttonComposite.dispose();
String toolTipText = getToolTipText();
boolean editable = false;
int columns = 2;
Image image = labelProvider.getImage(element);
if (image != null)
columns++;
editable = isEditable(element);
if (editable)
columns++;
buttonComposite = new Composite(this, SWT.NONE);
GridLayout buttonLayout = new GridLayout(columns, false);
buttonLayout.marginHeight = buttonLayout.marginWidth = 0;
buttonComposite.setLayout(buttonLayout);
buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
buttonComposite.setBackground(getBackground());
buttonComposite.addMouseListener(mouseListener);
buttonComposite.setToolTipText(toolTipText);
if (element != null) {
if (image != null) {
Label icon = createImage(buttonComposite, image);
icon.addMouseListener(mouseListener);
currentIcon = icon;
currentIcon.setToolTipText(toolTipText);
}
Label label = createLabel(buttonComposite, element);
label.addMouseListener(mouseListener);
currentLabel = label;
currentLabel.setToolTipText(toolTipText);
} else {
Composite blank = new Composite(buttonComposite, SWT.NONE);
blank.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
currentIcon = null;
currentLabel = null;
}
final Canvas arrow = new Canvas(buttonComposite, SWT.NONE) {
@Override
public Point computeSize(int wHint, int hHint, boolean changed) {
return new Point(12, 16);
}
};
arrow.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true));
arrow.setBackground(getBackground());
arrow.setForeground(getForeground());
arrowTransition = new Transition(arrow, arrowMax, 80);
arrow.addPaintListener(e -> {
final int hPadding = 2;
GC gc = e.gc;
LineAttributes attributes = new LineAttributes(2);
attributes.cap = SWT.CAP_ROUND;
gc.setLineAttributes(attributes);
gc.setAlpha(mouseOver ? 255 : 100);
Rectangle bounds = arrow.getBounds();
int arrowWidth = bounds.width - hPadding * 2;
int current = arrowTransition.getCurrent();
gc.drawPolyline(new int[] { hPadding, bounds.height / 2 - current, hPadding + (arrowWidth / 2),
bounds.height / 2 + current, hPadding + arrowWidth, bounds.height / 2 - current });
});
arrow.addMouseListener(mouseListener);
if (editable) {
final EditButton editButton = new EditButton(buttonComposite, SWT.NONE);
editButton.setData(LaunchBarWidgetIds.ID, LaunchBarWidgetIds.EDIT);
editButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true));
editButton.setBackground(getBackground());
editButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// Need to run this after the current event storm
// Or we get a disposed error.
getDisplay().asyncExec(() -> {
if (CSelector.this.selection != null)
handleEdit(selection);
});
}
});
}
layout();
}
protected abstract void fireSelectionChanged();
public Object getSelection() {
return selection;
}
public MouseListener getMouseListener() {
return mouseListener;
}
protected void openPopup() {
Object[] elements = contentProvider.getElements(input);
if (elements.length == 0 && !hasActionArea())
return;
arrowTransition.to(-arrowMax);
if (popup != null && !popup.isDisposed()) {
popup.dispose();
}
popup = new Shell(getShell(), SWT.TOOL | SWT.ON_TOP | SWT.RESIZE);
popup.setData(LaunchBarWidgetIds.ID, LaunchBarWidgetIds.POPUP);
popup.setLayout(GridLayoutFactory.fillDefaults().spacing(0, 0).create());
popup.setBackground(getBackground());
listViewer = new LaunchBarListViewer(popup);
initializeListViewer(listViewer);
listViewer.setFilterVisible(elements.length > 7);
listViewer.setInput(input);
listViewer.addSelectionChangedListener(event -> {
if (!listViewer.isFinalSelection())
return;
StructuredSelection ss = (StructuredSelection) event.getSelection();
if (!ss.isEmpty()) {
setSelection(ss.getFirstElement());
fireSelectionChanged();
}
closePopup();
});
if (hasActionArea())
createActionArea(popup);
Rectangle buttonBounds = getBounds();
Point popupLocation = popup.getDisplay().map(this, null, 0, buttonBounds.height);
popup.setLocation(popupLocation.x, popupLocation.y + 5);
restoreShellSize();
popup.setVisible(true);
popup.setFocus();
getDisplay().addFilter(SWT.FocusIn, focusOutListener);
getDisplay().addFilter(SWT.FocusOut, focusOutListener);
getDisplay().addFilter(SWT.MouseUp, focusOutListener);
popup.addDisposeListener(e -> {
getDisplay().removeFilter(SWT.FocusIn, focusOutListener);
getDisplay().removeFilter(SWT.FocusOut, focusOutListener);
getDisplay().removeFilter(SWT.MouseUp, focusOutListener);
saveShellSize();
});
}
protected String getDialogPreferencePrefix() {
return getClass().getSimpleName();
}
protected void restoreShellSize() {
Point size = popup.computeSize(SWT.DEFAULT, SWT.DEFAULT);
Point buttonSize = getSize();
size.x = Math.max(size.x, buttonSize.x);
size.y = Math.min(size.y, 300);
try {
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
String prefName = getDialogPreferencePrefix();
int w = store.getInt(prefName + ".shell.w"); //$NON-NLS-1$
int h = store.getInt(prefName + ".shell.h"); //$NON-NLS-1$
size.x = Math.max(size.x, w);
size.y = Math.max(size.y, h);
} catch (Exception e) {
Activator.log(e);
}
popup.setSize(size);
}
protected void saveShellSize() {
Point size = popup.getSize();
IPreferenceStore store = Activator.getDefault().getPreferenceStore();
String prefName = getDialogPreferencePrefix();
store.setValue(prefName + ".shell.w", size.x); //$NON-NLS-1$
store.setValue(prefName + ".shell.h", size.y); //$NON-NLS-1$
}
protected void initializeListViewer(LaunchBarListViewer listViewer) {
listViewer.setContentProvider(contentProvider);
listViewer.setLabelProvider(labelProvider);
listViewer.setComparator(sorter);
listViewer.setHistoryComparator(sorterTop);
}
private void closePopup() {
getDisplay().asyncExec(() -> {
if (popup == null || popup.isDisposed()) {
return;
}
arrowTransition.to(arrowMax);
popup.setVisible(false);
popup.dispose();
});
}
private Label createImage(Composite parent, Image image) {
Rectangle bounds = image.getBounds();
boolean disposeImage = false;
if (bounds.height > 16 || bounds.width > 16) {
Image buttonImage = new Image(getDisplay(), 16, 16);
GC gc = new GC(buttonImage);
gc.setAntialias(SWT.ON);
gc.setInterpolation(SWT.HIGH);
gc.drawImage(image, 0, 0, image.getBounds().width, image.getBounds().height, 0, 0, 16, 16);
gc.dispose();
image = buttonImage;
disposeImage = true;
}
Label icon = new Label(parent, SWT.NONE);
icon.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true));
icon.setImage(image);
if (disposeImage) {
final Image disposableImage = image;
icon.addDisposeListener(e -> disposableImage.dispose());
}
return icon;
}
private Label createLabel(Composite parent, Object element) {
Label label = new Label(parent, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
label.setText(labelProvider.getText(element));
label.setFont(getDisplay().getSystemFont());
return label;
}
public void setContentProvider(IStructuredContentProvider contentProvider) {
this.contentProvider = contentProvider;
}
public IStructuredContentProvider getContentProvider() {
return contentProvider;
}
public void setLabelProvider(ILabelProvider labelProvider) {
this.labelProvider = labelProvider;
}
public ILabelProvider getLabelProvider() {
return labelProvider;
}
@Override
public void setToolTipText(String toolTipText) {
super.setToolTipText(toolTipText);
if (buttonComposite != null) {
buttonComposite.setToolTipText(toolTipText);
}
if (currentLabel != null && !currentLabel.isDisposed()) {
currentLabel.setToolTipText(toolTipText);
}
if (currentIcon != null && !currentIcon.isDisposed()) {
currentIcon.setToolTipText(toolTipText);
}
}
/**
* Set sorter for the bottom part of the selector
*
* @param sorter
*/
public void setSorter(Comparator<?> sorter) {
this.sorter = sorter;
}
/**
* Set sorter for the "history" part of the selector
*
* @param sorter
*/
public void setHistorySortComparator(Comparator<?> sorter) {
this.sorterTop = sorter;
}
public void setInput(Object input) {
this.input = input;
}
public Object getInput() {
return input;
}
public void refresh() {
PlatformUI.getWorkbench().getDisplay().asyncExec(() -> {
if (isDisposed())
return;
update(selection); // update current selection - name or icon
// may have changed
if (popup != null && !popup.isDisposed()) {
listViewer.refresh(true); // update all labels in the popup
}
});
}
public void update(Object element) {
if (selection == element) {
if (currentIcon != null && !currentIcon.isDisposed()) {
currentIcon.setImage(labelProvider.getImage(element));
}
if (currentLabel != null && !currentLabel.isDisposed()) {
currentLabel.setText(labelProvider.getText(element));
}
}
if (popup != null && !popup.isDisposed()) {
listViewer.update(element, null);
}
}
/**
* Returns the text currently visible in the selector
*/
public String getText() {
if (currentLabel != null) {
currentLabel.getText();
}
return ""; //$NON-NLS-1$
}
protected boolean hasActionArea() {
return false;
}
protected void createActionArea(Composite parent) {
// empty
}
protected boolean isEditable(Object element) {
return false;
}
protected void handleEdit(Object element) {
// nothing to do here
}
public Color getOutlineColor() {
return getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
}
public Color getHighlightColor() {
return getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION);
}
}