diff --git a/bundles/org.eclipse.launchbar.ui/icons/edit_cold.png b/bundles/org.eclipse.launchbar.ui/icons/edit_cold.png new file mode 100644 index 00000000000..5a49a09fa00 Binary files /dev/null and b/bundles/org.eclipse.launchbar.ui/icons/edit_cold.png differ diff --git a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/Activator.java b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/Activator.java index 2782da9f952..dc0f8931df7 100644 --- a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/Activator.java +++ b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/Activator.java @@ -99,7 +99,15 @@ public class Activator extends AbstractUIPlugin { } public Image getImage(String id) { - return getImageRegistry().get(id); + Image im = getImageRegistry().get(id); + if (im == null) { + ImageDescriptor des = getImageDescriptor(id); + if (des != null) { + im = des.createImage(); + getImageRegistry().put(id, im); + } + } + return im; } public static ImageDescriptor getImageDescriptor(String path) { diff --git a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/CButton.java b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/CButton.java index cc62e1f5e7f..df1dbe4443d 100644 --- a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/CButton.java +++ b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/CButton.java @@ -10,25 +10,27 @@ *******************************************************************************/ package org.eclipse.launchbar.ui.internal.controls; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseTrackAdapter; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.TypedListener; public class CButton extends Canvas { - private boolean inButton; private Image hotImage; private Image coldImage; - + public CButton(Composite parent, int style) { super(parent, style); - addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { @@ -47,17 +49,28 @@ public class CButton extends Canvas { } } }); - addMouseTrackListener(new MouseTrackAdapter() { @Override public void mouseEnter(MouseEvent e) { - inButton = true; - redraw(); + setSelected(true); } + @Override public void mouseExit(MouseEvent e) { - inButton = false; - redraw(); + setSelected(false); + } + }); + addMouseListener(new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + setSelected(true); + handleSelection(inButton); + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + setSelected(true); + handleDefaultSelection(inButton); } }); } @@ -65,14 +78,12 @@ public class CButton extends Canvas { @Override public void dispose() { super.dispose(); - if (hotImage != null) hotImage.dispose(); - if (coldImage != null) coldImage.dispose(); } - + @Override public Point computeSize(int wHint, int hHint, boolean changed) { int width = 0; @@ -91,7 +102,7 @@ public class CButton extends Canvas { } return new Point(width, height); } - + public void setHotImage(Image image) { this.hotImage = image; } @@ -100,4 +111,29 @@ public class CButton extends Canvas { this.coldImage = image; } + protected void handleSelection(boolean selection) { + // Send event + notifyListeners(SWT.Selection, null); + } + + protected void handleDefaultSelection(boolean selection) { + // Send event + notifyListeners(SWT.DefaultSelection, null); + } + + public void addSelectionListener(SelectionListener listener) { + checkWidget(); + TypedListener typedListener = new TypedListener(listener); + addListener(SWT.Selection, typedListener); + addListener(SWT.DefaultSelection, typedListener); + } + + public void setSelected(boolean sel) { + inButton = sel; + redraw(); + } + + public boolean isSelected() { + return inButton; + } } diff --git a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/CSelector.java b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/CSelector.java index cfa3aeb8781..fb6d6879e60 100644 --- a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/CSelector.java +++ b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/CSelector.java @@ -10,52 +10,53 @@ *******************************************************************************/ package org.eclipse.launchbar.ui.internal.controls; -import java.util.Arrays; import java.util.Comparator; -import org.eclipse.jface.layout.GridDataFactory; +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.viewers.ICellModifier; import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.launchbar.ui.IHoverProvider; -import org.eclipse.launchbar.ui.internal.Activator; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.MouseTrackAdapter; import org.eclipse.swt.events.MouseTrackListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; +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.RGB; 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.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Sash; import org.eclipse.swt.widgets.Shell; public abstract class CSelector extends Composite { private IStructuredContentProvider contentProvider; private ILabelProvider labelProvider; private IHoverProvider hoverProvider; - private Comparator sorter; + private Comparator sorter; + private Comparator sorterTop; private Object input; private Composite buttonComposite; private String toolTipText; @@ -68,19 +69,10 @@ public abstract class CSelector extends Composite { protected final Color highlightColor; protected final Color white; private boolean mouseOver; - private Image editImage; - private boolean inEditButton; - private Image buttonImage; private Label currentIcon; private Label currentLabel; private Shell popup; - private ListItem listItems[]; - private int selIndex; - private ScrolledComposite listScrolled; - private final int itemH = 30; - private int scrollBucket; - private final int maxScrollBucket = 7; - private int separatorIndex = -1; + private LaunchBarListViewer listViewer; private MouseTrackListener mouseTrackListener = new MouseTrackListener() { @Override public void mouseEnter(MouseEvent e) { @@ -130,43 +122,91 @@ public abstract class CSelector extends Composite { @Override public void mouseUp(MouseEvent event) { if (popup == null || popup.isDisposed()) { + setFocus(); openPopup(); } else { closePopup(); } } }; + + protected boolean isFocusAncestor(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.FocusOut: - Control focusControl = getDisplay().getFocusControl(); - if (focusControl != null && focusControl.getShell() == popup) { - Point loc = getDisplay().getCursorLocation(); - if (!getBounds().contains(toControl(loc))) { - // Don't do it if we're in the selector, we'll deal with that later - closePopup(); - } + case SWT.FocusIn: + if (closingJob != null) + closingJob.cancel(); + if (event.widget instanceof Control && isFocusAncestor((Control) event.widget)) { + break; // not closing + } + if (!isPopUpInFocus()) { + closePopup(); } break; - case SWT.MouseUp: + 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("Closing popup") { + @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(toControl(loc))) { + if (!popup.getBounds().contains(loc) && !getBounds().contains(getParent().toControl(loc))) { closePopup(); } } break; } + } + } + + }; + private ICellModifier modifier = new ICellModifier() { + @Override + public void modify(Object element, String property, Object value) { + handleEdit(element); + } + + @Override + public Object getValue(Object element, String property) { + return null; + } + + @Override + public boolean canModify(Object element, String property) { + return isEditable(element); } }; public CSelector(Composite parent, int style) { super(parent, style); - backgroundColor = new Color(getDisplay(), new RGB(249, 249, 249)); - outlineColor = new Color(getDisplay(), new RGB(189, 195, 200)); - highlightColor = new Color(getDisplay(), new RGB(223, 239, 241)); + backgroundColor = getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND); + outlineColor = getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); + highlightColor = getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION); white = getDisplay().getSystemColor(SWT.COLOR_WHITE); GridLayout mainButtonLayout = new GridLayout(); setLayout(mainButtonLayout); @@ -186,16 +226,17 @@ public abstract class CSelector extends Composite { addMouseTrackListener(mouseTrackListener); } + private boolean isPopUpInFocus() { + Control focusControl = getDisplay().getFocusControl(); + if (focusControl != null && focusControl.getShell() == popup) { + return true; + } + return false; + } + @Override public void dispose() { super.dispose(); - backgroundColor.dispose(); - outlineColor.dispose(); - highlightColor.dispose(); - if (editImage != null) - editImage.dispose(); - if (buttonImage != null) - buttonImage.dispose(); if (popup != null) popup.dispose(); } @@ -272,11 +313,12 @@ public abstract class CSelector extends Composite { arrow.addMouseListener(mouseListener); arrow.addMouseTrackListener(mouseTrackListener); if (editable) { - Control editButton = createEditButton(buttonComposite, element); + final EditButton editButton = new EditButton(buttonComposite, SWT.NONE); + editButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true)); editButton.setBackground(backgroundColor); - editButton.addMouseListener(new MouseAdapter() { + editButton.addSelectionListener(new SelectionAdapter() { @Override - public void mouseUp(MouseEvent e) { + public void widgetSelected(SelectionEvent e) { // Need to run this after the current event storm // Or we get a disposed error. getDisplay().asyncExec(new Runnable() { @@ -316,32 +358,25 @@ public abstract class CSelector extends Composite { } popup = new Shell(getShell(), SWT.TOOL | SWT.ON_TOP); popup.setLayout(GridLayoutFactory.fillDefaults().spacing(0, 0).create()); - listScrolled = new ScrolledComposite(popup, SWT.V_SCROLL | SWT.NO_BACKGROUND); - listScrolled.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - listScrolled.setExpandHorizontal(true); - final Composite listComp = new Composite(listScrolled, SWT.NONE); - listScrolled.setContent(listComp); - listComp.setLayout(GridLayoutFactory.fillDefaults().spacing(0, 0).create()); - if (sorter != null) - Arrays.sort(elements, sorter); - listItems = new ListItem[elements.length]; - if (elements.length > 0) { - listItems[0] = new ListItem(listComp, SWT.NONE, elements[0], 0); - listItems[0].lazyInit(); - final int hHint = Math.max(listItems[0].computeSize(SWT.DEFAULT, SWT.DEFAULT).y, 16); - for (int i = 1; i < elements.length; ++i) { - ListItem item = new ListItem(listComp, SWT.NONE, elements[i], i); - if (i < maxScrollBucket) { // this is how many visible by default - item.lazyInit(); - } else { - ((GridData) item.getLayoutData()).heightHint = hHint; + + + listViewer = new LaunchBarListViewer(popup); + initializeListViewer(listViewer); + listViewer.setFilterVisible(elements.length > 7); + listViewer.setInput(input); + listViewer.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + if (!listViewer.isFinalSelection()) + return; + StructuredSelection ss = (StructuredSelection) event.getSelection(); + if (!ss.isEmpty()) { + setSelection(ss.getFirstElement()); + fireSelectionChanged(); } - listItems[i] = item; + closePopup(); } - createSash(listComp, hHint); - } - Point listCompSize = listComp.computeSize(SWT.DEFAULT, SWT.DEFAULT); - listComp.setSize(listCompSize); + }); if (hasActionArea()) createActionArea(popup); Rectangle buttonBounds = getBounds(); @@ -350,295 +385,53 @@ public abstract class CSelector extends Composite { popup.setLocation(popupLocation.x, popupLocation.y + 5); Point size = popup.computeSize(SWT.DEFAULT, SWT.DEFAULT); Point buttonSize = getSize(); - size.x = Math.min(size.x + 16, buttonSize.x * 4 / 3); - size.y = Math.min(size.y, 250); + size.x = Math.max(size.x, buttonSize.x); + size.y = Math.min(size.y, 300); popup.setSize(size); popup.setVisible(true); popup.setFocus(); + getDisplay().addFilter(SWT.FocusIn, focusOutListener); getDisplay().addFilter(SWT.FocusOut, focusOutListener); getDisplay().addFilter(SWT.MouseUp, focusOutListener); popup.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { + getDisplay().removeFilter(SWT.FocusIn, focusOutListener); getDisplay().removeFilter(SWT.FocusOut, focusOutListener); getDisplay().removeFilter(SWT.MouseUp, focusOutListener); } }); - selIndex = -1; - scrollBucket = 0; if (hoverProvider != null) { hoverProvider.dismissHover(selection != null ? selection : null, true); } } - private void createSash(final Composite listComp, final int hHint) { - if (separatorIndex<0) return; - - final Sash sash = new Sash(listComp, SWT.BORDER | SWT.HORIZONTAL); - sash.setLayoutData(GridDataFactory.fillDefaults().create()); - - if (separatorIndex < listItems.length) - sash.moveAbove(listItems[separatorIndex]); - else - sash.moveBelow(null); - - sash.addListener(SWT.Selection, new Listener() { - @Override - public void handleEvent(Event e) { - separatorIndex = (e.y + hHint/2) / hHint; - } - }); - - sash.addMouseListener(new MouseListener() { - @Override - public void mouseUp(MouseEvent e) { - setSeparatorIndex(separatorIndex); // call setter if it was overriden - if (separatorIndex >= 0) { - if (separatorIndex < listItems.length) - sash.moveAbove(listItems[separatorIndex]); - else - sash.moveBelow(null); - listComp.layout(); - } - } - - @Override - public void mouseDown(MouseEvent e) { - sash.moveAbove(null); // keep on top so user see it when moving - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - // ignore - } - }); - - sash.setToolTipText("Increase/Decrease size of recently used elements pane"); + protected void initializeListViewer(LaunchBarListViewer listViewer) { + listViewer.setContentProvider(contentProvider); + listViewer.setLabelProvider(labelProvider); + listViewer.setCellModifier(modifier); + listViewer.setComparator(sorter); + listViewer.setHistoryComparator(sorterTop); } private void closePopup() { - arrowTransition.to(arrowMax); - popup.setVisible(false); getDisplay().asyncExec(new Runnable() { @Override public void run() { + if (popup == null || popup.isDisposed()) + return; + arrowTransition.to(arrowMax); + popup.setVisible(false); popup.dispose(); } }); } - TraverseListener listItemTraverseListener = new TraverseListener() { - @Override - public void keyTraversed(TraverseEvent e) { - final ListItem currItem = selIndex >= 0 ? listItems[selIndex] : null; - if (currItem == null && e.keyCode != SWT.ARROW_DOWN) { - return; - } - if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_TAB_NEXT) { - if (inEditButton || e.keyCode == SWT.ARROW_DOWN) { - int maxIdx = listItems.length - 1; - if (selIndex < maxIdx) { - inEditButton = false; - if (currItem != null) - currItem.setBackground(white); - // move to next item - selIndex++; - if (scrollBucket < maxScrollBucket) { - scrollBucket++; - } else { - // need to scroll the list up 1 item - int sY = listScrolled.getOrigin().y; - listScrolled.setOrigin(0, sY + itemH); - } - listItems[selIndex].setBackground(highlightColor); - } else if (selIndex == maxIdx && maxIdx > maxScrollBucket) { - // level the scroll for any offset at the bottom of the list - listScrolled.setOrigin(0, itemH * (maxIdx - maxScrollBucket + 1)); - } - } else if (currItem.editButton != null) { - // move focus on edit button - inEditButton = true; - currItem.editButton.redraw(); - } - } else if (e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) { - if (!inEditButton || e.keyCode == SWT.ARROW_UP) { - if (selIndex > 0) { - inEditButton = false; - currItem.setBackground(white); - // move to previous item - selIndex--; - if (scrollBucket > 0) { - scrollBucket--; - } else { - // need to scroll the list down 1 item - int sY = listScrolled.getOrigin().y; - listScrolled.setOrigin(0, sY - itemH); - } - listItems[selIndex].setBackground(highlightColor); - } else if (selIndex == 0) { - // level any offset @ beginning - listScrolled.setOrigin(0, 0); - } - } else if (currItem.editButton != null) { - // remove focus from edit button - inEditButton = false; - currItem.editButton.redraw(); - } - } else if (e.detail == SWT.TRAVERSE_RETURN) { - if (inEditButton) { - inEditButton = false; - // edit button in list item was pressed - getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - if (CSelector.this.selection != null) - handleEdit(currItem.element); - } - }); - } else { - // list item was pressed - popup.dispose(); - setSelection(currItem.element); - fireSelectionChanged(); - } - } else if (e.detail == SWT.TRAVERSE_ESCAPE) { - popup.dispose(); - } - } - }; - - private class ListItem extends Composite { - protected final Object element; - private Label icon; - private Label label; - protected Control editButton; - private int index; - - public ListItem(Composite parent, int style, Object _element, int index) { - super(parent, style); - this.element = _element; - this.index = index; - setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - addPaintListener(new PaintListener() { - @Override - public void paintControl(PaintEvent e) { - Point size = getSize(); - GC gc = e.gc; - gc.setForeground(outlineColor); - gc.drawLine(0, size.y - 1, size.x, size.y - 1); - if (label == null) - lazyInit(); - } - }); - // lazyInit(); - } // end ListItem(..) - - protected void lazyInit() { - Image image = labelProvider.getImage(element); - boolean editable = isEditable(element); - int columns = 1; - if (image != null) - columns++; - if (editable) - columns++; - GridLayout layout = new GridLayout(columns, false); - layout.marginWidth = layout.marginHeight = 7; - setLayout(layout); - MouseListener listItemMouseListener = new MouseAdapter() { - @Override - public void mouseUp(MouseEvent e) { - popup.dispose(); - setSelection(element); - fireSelectionChanged(); - } - }; - MouseTrackListener listItemMouseTrackListener = new MouseTrackAdapter() { - @Override - public void mouseEnter(MouseEvent e) { - setBackground(highlightColor); - int idx = getIndex(); - if (idx != selIndex) { - if (selIndex >= 0) { - listItems[selIndex].setBackground(white); - scrollBucket = Math.max(Math.min(scrollBucket + idx - selIndex, maxScrollBucket), 0); - } else { // initially - scrollBucket = Math.min(idx, maxScrollBucket); - } - } - selIndex = idx; - } - - @Override - public void mouseExit(MouseEvent e) { - setBackground(white); - } - }; - addMouseListener(listItemMouseListener); - // addMouseTrackListener(listItemMouseTrackListener); - if (image != null) { - icon = createImage(this, image); - icon.addMouseListener(listItemMouseListener); - icon.addMouseTrackListener(listItemMouseTrackListener); - } - label = createLabel(this, element); - label.addMouseListener(listItemMouseListener); - label.addMouseTrackListener(listItemMouseTrackListener); - if (editable) { - editButton = createEditButton(this, element); - editButton.setBackground(white); - editButton.addMouseTrackListener(listItemMouseTrackListener); - editButton.addMouseListener(new MouseAdapter() { - @Override - public void mouseUp(MouseEvent e) { - // Need to run this after the current event storm - // Or we get a disposed error. - getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - if (CSelector.this.selection != null) - handleEdit(element); - } - }); - } - }); - editButton.addTraverseListener(listItemTraverseListener); - } else { - addTraverseListener(listItemTraverseListener); - } - setBackground(white); - layout(true); - } - - @Override - public void setBackground(Color color) { - super.setBackground(color); - if (icon != null && !icon.isDisposed()) - icon.setBackground(color); - if (label != null && !label.isDisposed()) - label.setBackground(color); - if (editButton != null && !editButton.isDisposed()) - editButton.setBackground(color); - } - - public void setImage(Image image) { - if (icon != null && !icon.isDisposed()) - icon.setImage(image); - } - - public void setText(String text) { - if (!label.isDisposed()) - label.setText(text); - } - - protected int getIndex() { - return index; - } - } // end ListItem class private Label createImage(Composite parent, Image image) { Rectangle bounds = image.getBounds(); boolean disposeImage = false; if (bounds.height > 16 || bounds.width > 16) { - buttonImage = new Image(getDisplay(), 16, 16); + Image buttonImage = new Image(getDisplay(), 16, 16); GC gc = new GC(buttonImage); gc.setAntialias(SWT.ON); gc.setInterpolation(SWT.HIGH); @@ -646,6 +439,7 @@ public abstract class CSelector extends Composite { 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)); @@ -670,43 +464,6 @@ public abstract class CSelector extends Composite { return label; } - private Control createEditButton(Composite parent, Object element) { - if (editImage == null) { - editImage = Activator.getImageDescriptor("icons/config_config.png").createImage(); - } - final Canvas editButton = new Canvas(parent, SWT.NONE) { - @Override - public Point computeSize(int wHint, int hHint, boolean changed) { - Rectangle bounds = editImage.getBounds(); - return new Point(bounds.width, bounds.height); - }; - }; - editButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, true)); - editButton.setToolTipText("Edit"); - editButton.addPaintListener(new PaintListener() { - @Override - public void paintControl(PaintEvent e) { - GC gc = e.gc; - gc.setAlpha(inEditButton ? 255 : 64); - gc.drawImage(editImage, 0, 0); - } - }); - editButton.addMouseTrackListener(new MouseTrackAdapter() { - @Override - public void mouseEnter(MouseEvent e) { - inEditButton = true; - editButton.redraw(); - } - - @Override - public void mouseExit(MouseEvent e) { - inEditButton = false; - editButton.redraw(); - } - }); - return editButton; - } - public void setContentProvider(IStructuredContentProvider contentProvider) { this.contentProvider = contentProvider; } @@ -731,10 +488,24 @@ public abstract class CSelector extends Composite { return hoverProvider; } - public void setSorter(Comparator sorter) { + /** + * 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; } @@ -757,15 +528,7 @@ public abstract class CSelector extends Composite { } } if (popup != null && !popup.isDisposed()) { - Object[] elements = contentProvider.getElements(input); - int i; - for (i = 0; i < elements.length; ++i) - if (element == elements[i]) - break; - if (i != elements.length) { - listItems[i].setImage(labelProvider.getImage(element)); - listItems[i].setText(labelProvider.getText(element)); - } + listViewer.update(element, null); } } @@ -785,11 +548,4 @@ public abstract class CSelector extends Composite { // nothing to do here } - public int getSeparatorIndex() { - return separatorIndex; - } - - public void setSeparatorIndex(int separatorIndex) { - this.separatorIndex = separatorIndex; - } } diff --git a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/ConfigSelector.java b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/ConfigSelector.java index 9437285e1a6..05705bb8302 100644 --- a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/ConfigSelector.java +++ b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/ConfigSelector.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.eclipse.launchbar.ui.internal.controls; -import java.util.Arrays; import java.util.Comparator; import org.eclipse.core.runtime.CoreException; @@ -26,7 +25,6 @@ import org.eclipse.debug.internal.ui.DebugUIPlugin; import org.eclipse.debug.internal.ui.launchConfigurations.LaunchGroupExtension; import org.eclipse.debug.ui.ILaunchGroup; import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.LabelProvider; @@ -79,27 +77,13 @@ public class ConfigSelector extends CSelector { @Override public void dispose() { } + @Override public Object[] getElements(Object inputElement) { ILaunchDescriptor[] descs = uiManager.getManager().getLaunchDescriptors(); - if (descs.length > 0) { - int separatorIndex = getSeparatorIndex(); - if (descs.length > separatorIndex + 1) { - ILaunchDescriptor[] descsCopy = new ILaunchDescriptor[separatorIndex + descs.length]; - System.arraycopy(descs, 0, descsCopy, 0, separatorIndex); // copy first 3 elements - System.arraycopy(descs, 0, descsCopy, separatorIndex, descs.length); // copy all into rest - // sort rest - Arrays.sort(descsCopy, separatorIndex, descsCopy.length, new Comparator() { - @Override - public int compare(ILaunchDescriptor o1, ILaunchDescriptor o2) { - return o1.getName().compareTo(o2.getName()); - } - }); - return descsCopy; - } else - return descs; - } - return noConfigs; + if (descs.length == 0) + return noConfigs; + return descs; } }); @@ -141,13 +125,23 @@ public class ConfigSelector extends CSelector { return defaultProvider.getText(element); } }); - // no sorter on view, data is sorted by provider - setSorter(null); - IPreferenceStore store = Activator.getDefault().getPreferenceStore(); - int separator = store.getInt(Activator.PREF_LAUNCH_HISTORY_SIZE); - if (separator <= 0) - separator = 1; - setSeparatorIndex(separator); + // no sorter for top, data is sorted by provider in historical order + setHistorySortComparator(null); + // alphabetic sorter + setSorter(new Comparator() { + @Override + public int compare(ILaunchDescriptor o1, ILaunchDescriptor o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + + } + + @Override + protected void initializeListViewer(LaunchBarListViewer listViewer) { + listViewer.setHistorySupported(true); + listViewer.setHistoryPreferenceName(Activator.PREF_LAUNCH_HISTORY_SIZE); + super.initializeListViewer(listViewer); } @Override @@ -162,17 +156,7 @@ public class ConfigSelector extends CSelector { } } } - - @Override - public void setSeparatorIndex(int separatorIndex) { - super.setSeparatorIndex(separatorIndex); - IPreferenceStore store = Activator.getDefault().getPreferenceStore(); - int separator = store.getInt(Activator.PREF_LAUNCH_HISTORY_SIZE); - if (separator != getSeparatorIndex()) { - store.setValue(Activator.PREF_LAUNCH_HISTORY_SIZE, getSeparatorIndex()); - } - } - + @Override public boolean isEditable(Object element) { return element instanceof ILaunchDescriptor; @@ -233,7 +217,7 @@ public class ConfigSelector extends CSelector { GridLayout buttonLayout = new GridLayout(); buttonLayout.marginWidth = buttonLayout.marginHeight = 7; createButton.setLayout(buttonLayout); - createButton.setBackground(white); + createButton.setBackground(backgroundColor); createButton.addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { @@ -247,7 +231,7 @@ public class ConfigSelector extends CSelector { final Label createLabel = new Label(createButton, SWT.None); createLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); createLabel.setText("Create New Configuration..."); - createLabel.setBackground(white); + createLabel.setBackground(backgroundColor); MouseListener mouseListener = new MouseAdapter() { public void mouseUp(org.eclipse.swt.events.MouseEvent e) { @@ -281,8 +265,8 @@ public class ConfigSelector extends CSelector { } @Override public void mouseExit(MouseEvent e) { - createButton.setBackground(white); - createLabel.setBackground(white); + createButton.setBackground(backgroundColor); + createLabel.setBackground(backgroundColor); } }; createButton.addMouseTrackListener(mouseTrackListener); diff --git a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/EditButton.java b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/EditButton.java new file mode 100644 index 00000000000..1c95f414533 --- /dev/null +++ b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/EditButton.java @@ -0,0 +1,13 @@ +package org.eclipse.launchbar.ui.internal.controls; + +import org.eclipse.launchbar.ui.internal.Activator; +import org.eclipse.swt.widgets.Composite; + +public class EditButton extends CButton { + public EditButton(Composite parent, int style) { + super(parent, style); + setHotImage(Activator.getDefault().getImage("icons/config_config.png")); + setColdImage(Activator.getDefault().getImage("icons/edit_cold.png")); + setToolTipText("Edit"); + } +} diff --git a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/FilterControl.java b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/FilterControl.java new file mode 100644 index 00000000000..2ef11dfb141 --- /dev/null +++ b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/FilterControl.java @@ -0,0 +1,472 @@ +package org.eclipse.launchbar.ui.internal.controls; + +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.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.accessibility.AccessibleAdapter; +import org.eclipse.swt.accessibility.AccessibleEvent; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Text; +import org.eclipse.ui.internal.WorkbenchMessages; +import org.eclipse.ui.progress.WorkbenchJob; + +/** + * A simple control that provides a text widget and controls a list viewer + */ +public class FilterControl extends Composite { + /** + * The filter text widget to be used by this tree. This value may be null if there is no filter widget, or if the + * controls have not yet been created. + */ + protected Text filterText; + /** + * The viewer for the filtered tree. This value should never be null after the widget creation methods are + * complete. + */ + protected LaunchBarListViewer listViewer; + + protected ViewerFilter patternFilter; + /** + * The text to initially show in the filter text control. + */ + protected String initialText = ""; //$NON-NLS-1$ + protected String patternText = null; + /** + * The job used to refresh the tree. + */ + private Job refreshJob; + /** + * The parent composite this control. + */ + protected Composite parent; + + /** + * Creates a filter control, to be fully function attachListViewer must be called shortly after + * + * @param parent + */ + public FilterControl(Composite parent) { + super(parent, SWT.NONE); + this.parent = parent; + patternFilter = new ViewerFilter() { + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + String text = ((ILabelProvider) listViewer.getLabelProvider()).getText(element); + if (patternText == null) + return true; + String trim = patternText.trim(); + if (trim.isEmpty()) + return true; + if (text == null) + return false; + if (text.contains(trim)) + return true; + if (text.toLowerCase().contains(trim.toLowerCase())) + return true; + return false; + } + }; + init(); + } + + /** + * Create the filtered list. + */ + protected void init() { + createControl(this, SWT.NONE); + createRefreshJob(); + setInitialText(WorkbenchMessages.FilteredTree_FilterMessage); + setFont(parent.getFont()); + } + + /** + * Create the filtered tree's controls. Subclasses should override. + * + * @param parent + * @param treeStyle + */ + protected void createControl(Composite parent, int treeStyle) { + setLayout(GridLayoutFactory.fillDefaults().spacing(0, 0).create()); + if (parent.getLayout() instanceof GridLayout) { + setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create()); + } + Composite fc = createFilterControls(parent); + fc.setLayoutData(new GridData(SWT.FILL, SWT.BEGINNING, true, false)); + } + + /** + * Create the filter controls. By default, a text and corresponding tool bar button that clears the contents of the text is + * created. Subclasses may override. + * + * @param parent + * parent Composite of the filter controls + * @return the Composite that contains the filter controls + */ + protected Composite createFilterControls(Composite parent) { + createFilterText(parent); + return parent; + } + + public Control attachListViewer(LaunchBarListViewer listViewer) { + this.listViewer = listViewer; + // listViewer.getControl().setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create()); + listViewer.getControl().addDisposeListener(new DisposeListener() { + @Override + public void widgetDisposed(DisposeEvent e) { + refreshJob.cancel(); + } + }); + listViewer.addFilter(patternFilter); + return listViewer.getControl(); + } + + /** + * Create the refresh job for the receiver. + * + */ + private void createRefreshJob() { + refreshJob = doCreateRefreshJob(); + refreshJob.setSystem(true); + } + + @Override + public void setVisible(boolean visible) { + boolean oldVisible = getVisible(); + if (oldVisible == true && visible == false && listViewer != null && filterText.isFocusControl()) { + listViewer.setFocus(); + } + if (getLayoutData() instanceof GridData) { + ((GridData) getLayoutData()).heightHint = visible ? SWT.DEFAULT : 0; + } + super.setVisible(visible); + } + + /** + * Creates a workbench job that will refresh the tree based on the current filter text. Subclasses may override. + * + * @return a workbench job that can be scheduled to refresh the tree + * + * @since 3.4 + */ + protected WorkbenchJob doCreateRefreshJob() { + return new WorkbenchJob("Refresh Filter") {//$NON-NLS-1$ + @Override + public IStatus runInUIThread(IProgressMonitor monitor) { + if (listViewer == null) + return Status.CANCEL_STATUS; + if (listViewer.getControl().isDisposed()) { + return Status.CANCEL_STATUS; + } + updatePatternText(); + if (patternText == null) { + return Status.OK_STATUS; + } + Control redrawControl = listViewer.getControl(); + try { + // don't want the user to see updates that will be made to + // the tree + // we are setting redraw(false) on the composite to avoid + // dancing scrollbar + redrawControl.setRedraw(false); + listViewer.setHistorySupported(patternText == null || patternText.isEmpty()); + listViewer.refresh(true); + updateListSelection(false); + } finally { + redrawControl.setRedraw(true); + } + return Status.OK_STATUS; + } + }; + } + + /** + * Creates the filter text and adds listeners. This method calls {@link #doCreateFilterText(Composite)} to create the text + * control. Subclasses should override {@link #doCreateFilterText(Composite)} instead of overriding this method. + * + * @param parent + * Composite of the filter text + */ + protected void createFilterText(Composite parent) { + filterText = doCreateFilterText(parent); + filterText.getAccessible().addAccessibleListener( + new AccessibleAdapter() { + @Override + public void getName(AccessibleEvent e) { + String filterTextString = filterText.getText(); + if (filterTextString.length() == 0 + || filterTextString.equals(initialText)) { + e.result = initialText; + } else { + e.result = NLS.bind( + WorkbenchMessages.FilteredTree_AccessibleListenerFiltered, + new String[] { + filterTextString, + String.valueOf(getFilteredItemsCount()) }); + } + } + + /** + * Return the number of filtered items + * + * @return int + */ + private int getFilteredItemsCount() { + return listViewer.getItemCount(); + } + }); + filterText.addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + } + + @Override + public void focusLost(FocusEvent e) { + if (filterText.getText().equals(initialText)) { + setFilterText(""); //$NON-NLS-1$ + } + } + }); + filterText.addMouseListener(new MouseAdapter() { + @Override + public void mouseDown(MouseEvent e) { + if (filterText.getText().equals(initialText)) { + clearText(); + } + } + }); + // enter key set focus to tree + filterText.addTraverseListener(new TraverseListener() { + @Override + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_RETURN) { + e.doit = false; + listViewer.setFocus(); + updateListSelection(true); + } else if (e.detail == SWT.TRAVERSE_ARROW_NEXT) { + listViewer.setFocus(); + updateListSelection(false); + } + } + }); + filterText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + textChanged(); + } + }); + // if we're using a field with built in cancel we need to listen for + // default selection changes (which tell us the cancel button has been + // pressed) + if ((filterText.getStyle() & SWT.ICON_CANCEL) != 0) { + filterText.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetDefaultSelected(SelectionEvent e) { + if (e.detail == SWT.ICON_CANCEL) + clearText(); + } + }); + } + filterText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + } + + protected void updateListSelection(boolean enter) { + if (listViewer.getItemCount() == 0) { + if (enter) + Display.getCurrent().beep(); + } else { + StructuredSelection sel; + // if the initial filter text hasn't changed, do not try to match + if (patternText != null && !patternText.trim().isEmpty()) { + // select item with triggering event, it may close the popup if list used as combo + sel = new StructuredSelection(listViewer.getTopFilteredElement()); + } else { + sel = new StructuredSelection(listViewer.getTopElement()); + } + if (enter) + listViewer.setDefaultSelection(sel); + else + listViewer.setSelection(sel); + } + } + + protected Text doCreateFilterText(Composite parent) { + return new Text(parent, SWT.SINGLE | SWT.BORDER | SWT.SEARCH + | SWT.ICON_CANCEL); + } + + /** + * Update the receiver after the text has changed. + */ + protected void textChanged() { + String old = patternText; + updatePatternText(); + if (patternText != null && old == null && patternText.isEmpty()) + return;// we changing from initial selection to empty string + if (patternText == old) + return; + // cancel currently running job first, to prevent unnecessary redraw + refreshJob.cancel(); + refreshJob.schedule(getRefreshJobDelay()); + } + + /** + * Return the time delay that should be used when scheduling the filter refresh job. Subclasses may override. + * + * @return a time delay in milliseconds before the job should run + * + */ + protected long getRefreshJobDelay() { + return 200; + } + + /** + * Set the background for the widgets that support the filter text area. + * + * @param background + * background Color to set + */ + @Override + public void setBackground(Color background) { + super.setBackground(background); + // listComposite.setBackground(background); + // filterText.setBackground(background); + } + + /** + * Clears the text in the filter text widget. + */ + protected void clearText() { + setFilterText(""); //$NON-NLS-1$ + // textChanged(); + } + + /** + * Set the text in the filter control. + * + * @param string + */ + protected void setFilterText(String string) { + if (filterText != null) { + filterText.setText(string); + selectAll(); + } + } + + public Text getFilterText() { + return filterText; + } + + /** + * Get the tree viewer of the receiver. + * + * @return the tree viewer + */ + public LaunchBarListViewer getViewer() { + return listViewer; + } + + /** + * Get the filter text for the receiver, if it was created. Otherwise return null. + * + * @return the filter Text, or null if it was not created + */ + public Text getFilterControl() { + return filterText; + } + + /** + * Convenience method to return the text of the filter control. If the text widget is not created, then null is returned. + * + * @return String in the text, or null if the text does not exist + */ + protected String getFilterString() { + return filterText != null ? filterText.getText() : null; + } + + /** + * Set the text that will be shown until the first focus. A default value is provided, so this method only need be called if + * overriding the default initial text is desired. + * + * @param text + * initial text to appear in text field + */ + public void setInitialText(String text) { + initialText = text; + if (filterText != null) { + filterText.setMessage(text); + if (filterText.isFocusControl()) { + setFilterText(initialText); + textChanged(); + } else { + getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + if (!filterText.isDisposed() && filterText.isFocusControl()) { + setFilterText(initialText); + textChanged(); + } + } + }); + } + } else { + setFilterText(initialText); + textChanged(); + } + } + + /** + * Select all text in the filter text field. + * + */ + protected void selectAll() { + if (filterText != null) { + filterText.selectAll(); + } + } + + /** + * Get the initial text for the receiver. + * + * @return String + */ + protected String getInitialText() { + return initialText; + } + + private void updatePatternText() { + String text = getFilterString(); + boolean initial = initialText != null + && initialText.equals(text); + if (initial) { + patternText = null; + } else if (text != null) { + patternText = text; + } + } +} diff --git a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/LaunchBarListViewer.java b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/LaunchBarListViewer.java new file mode 100644 index 00000000000..63157a915b5 --- /dev/null +++ b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/LaunchBarListViewer.java @@ -0,0 +1,711 @@ +package org.eclipse.launchbar.ui.internal.controls; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.jface.preference.IPreferenceStore; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ICellModifier; +import org.eclipse.jface.viewers.IFontProvider; +import org.eclipse.jface.viewers.ILabelProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.StructuredViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.launchbar.core.ILaunchDescriptor; +import org.eclipse.launchbar.ui.internal.Activator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseTrackAdapter; +import org.eclipse.swt.events.MouseTrackListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +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.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Sash; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Widget; + +public class LaunchBarListViewer extends StructuredViewer { + private ScrolledComposite listScrolled; + private Composite listComposite; + private ListItem[] listItems; + private int selIndex; + private int itemH = 30; + private int scrollBucket; + private final int maxScrollBucket = 6; + private int separatorIndex = -1; + private boolean historySupported = true; + private ICellModifier modifier; + private ViewerComparator historyComparator; + private boolean finalSelection = false; + private FilterControl filterControl; + private Sash sash; + private String historyPref; + + private static class LaunchBarListViewerComparator extends ViewerComparator { + public LaunchBarListViewerComparator(Comparator comp) { + super(comp); + } + + // have to override it because standard ViewerComparator compares by labels only + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + return getComparator().compare(e1, e2); + } + } + private TraverseListener listItemTraverseListener = new TraverseListener() { + @Override + public void keyTraversed(TraverseEvent e) { + final ListItem currItem = selIndex >= 0 ? listItems[selIndex] : null; + if (currItem == null && e.keyCode != SWT.ARROW_DOWN) { + return; + } + if (e.detail == SWT.TRAVERSE_ARROW_NEXT || e.detail == SWT.TRAVERSE_TAB_NEXT) { + if (e.keyCode == SWT.ARROW_DOWN) { + int maxIdx = listItems.length - 1; + if (selIndex < maxIdx) { + // move to next item + listItems[selIndex + 1].setSelected(true); + if (scrollBucket < maxScrollBucket) { + scrollBucket++; + } else { + // need to scroll the list up 1 item + int sY = listScrolled.getOrigin().y; + listScrolled.setOrigin(0, sY + itemH); + } + } else if (selIndex == maxIdx && maxIdx > maxScrollBucket) { + // level the scroll for any offset at the bottom of the list + listScrolled.setOrigin(0, itemH * (maxIdx - maxScrollBucket + 1)); + } + } + } else if (e.detail == SWT.TRAVERSE_ARROW_PREVIOUS || e.detail == SWT.TRAVERSE_TAB_PREVIOUS) { + if (e.keyCode == SWT.ARROW_UP) { + if (selIndex > 0) { + // move to previous item + if (scrollBucket > 0) { + scrollBucket--; + } else { + // need to scroll the list down 1 item + int sY = listScrolled.getOrigin().y; + listScrolled.setOrigin(0, sY - itemH); + } + listItems[selIndex - 1].setSelected(true); + } else if (selIndex == 0) { + // level any offset @ beginning + listScrolled.setOrigin(0, 0); + } + } else if (currItem.editButton != null) { + // remove focus from edit button + currItem.editButton.setSelected(false); + currItem.editButton.redraw(); + } + } else if (e.detail == SWT.TRAVERSE_RETURN) { + setDefaultSelection(new StructuredSelection(currItem.element)); + } else if (e.detail == SWT.TRAVERSE_ESCAPE) { + setDefaultSelection(new StructuredSelection()); + } + } + }; + + + + + private class ListItem extends Composite { + protected final Object element; + private Label icon; + private Label label; + protected EditButton editButton; + private int index; + private Color backgroundColor = getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND); + private Color outlineColor = getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW); + private Color highlightColor = getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION); + private ILabelProvider labelProvider; + private ICellModifier modifer; + + @Override + public String toString() { + return "[" + index + "] " + labelProvider.getText(element); + } + public ListItem(Composite parent, int style, Object element, int index, ILabelProvider labelProvider, + ICellModifier modifier) { + super(parent, style); + this.element = element; + this.index = index; + this.labelProvider = labelProvider; + this.modifer = modifier; + setData(element); + addPaintListener(new PaintListener() { + @Override + public void paintControl(PaintEvent e) { + Point size = getSize(); + GC gc = e.gc; + gc.setForeground(outlineColor); + gc.drawLine(0, size.y - 1, size.x, size.y - 1); + if (label == null) + lazyInit(); + } + }); + // lazyInit(); + } // end ListItem(..) + + protected void lazyInit() { + Image image = labelProvider.getImage(element); + boolean editable = isEditable(element); + int columns = 1; + if (image != null) + columns++; + if (editable) + columns++; + GridLayout layout = new GridLayout(columns, false); + layout.marginWidth = layout.marginHeight = 7; + setLayout(layout); + MouseListener listItemMouseListener = new MouseAdapter() { + @Override + public void mouseUp(MouseEvent e) { + setDefaultSelection(new StructuredSelection(element)); + } + }; + MouseTrackListener listItemMouseTrackListener = new MouseTrackAdapter() { + @Override + public void mouseEnter(MouseEvent e) { + setSelected(true); + } + + @Override + public void mouseExit(MouseEvent e) { + setSelected(false); + } + }; + addMouseListener(listItemMouseListener); + addMouseTrackListener(listItemMouseTrackListener); + if (image != null) { + icon = createImage(this, image); + icon.addMouseListener(listItemMouseListener); + icon.addMouseTrackListener(listItemMouseTrackListener); + } + label = createLabel(this, element); + label.addMouseListener(listItemMouseListener); + label.addMouseTrackListener(listItemMouseTrackListener); + if (editable) { + editButton = new EditButton(this, SWT.NONE); + 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(new Runnable() { + @Override + public void run() { + if (editButton.isSelected()) + handleEdit(element); + } + }); + } + }); + editButton.setBackground(backgroundColor); + editButton.addMouseTrackListener(listItemMouseTrackListener); + editButton.addTraverseListener(listItemTraverseListener); + } else { + // add traverse listnener to control which will have keyboard focus + addTraverseListener(listItemTraverseListener); + } + addKeyListener(new KeyListener() { + @Override + public void keyReleased(KeyEvent e) { + // ignore + } + + @Override + public void keyPressed(KeyEvent e) { + if (e.character != 0 && !filterControl.isVisible()) { + // enable filter control and send the character there + filterControl.setVisible(true); + filterControl.setFocus(); + filterControl.getParent().layout(true); + filterControl.getFilterText().setText(e.character + ""); + filterControl.getFilterText().setSelection(1); + } + } + }); + setBackground(backgroundColor); + layout(true); + } + + public void setSelected(boolean selected) { + if (selected) { + setBackground(highlightColor); + int idx = getIndex(); + if (idx != selIndex) { + if (selIndex >= 0) { + listItems[selIndex].setBackground(backgroundColor); + scrollBucket = Math.max(Math.min(scrollBucket + idx - selIndex, maxScrollBucket), 0); + } else { // initially + scrollBucket = Math.min(idx, maxScrollBucket); + } + } + selIndex = idx; + } else { + setBackground(backgroundColor); + } + if (editButton != null) { + editButton.setSelected(selected); + } + } + + protected boolean isEditable(Object element) { + if (modifer != null) { + return modifer.canModify(element, null); + } + return false; + } + + protected void handleEdit(Object element) { + if (modifer != null) { + modifer.modify(element, null, null); + } + } + + @Override + public void setBackground(Color color) { + super.setBackground(color); + if (icon != null && !icon.isDisposed()) + icon.setBackground(color); + if (label != null && !label.isDisposed()) + label.setBackground(color); + if (editButton != null && !editButton.isDisposed()) + editButton.setBackground(color); + } + + public void setImage(Image image) { + if (icon != null && !icon.isDisposed()) + icon.setImage(image); + } + + public void setText(String text) { + if (!label.isDisposed()) + label.setText(text); + } + + @Override + public boolean setFocus() { + super.setFocus(); + return true; + } + + protected int getIndex() { + return index; + } + + 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); + // resize to 16 pixels + 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(new DisposeListener() { + @Override + public void widgetDisposed(DisposeEvent 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)); + ILabelProvider labelProvider = (ILabelProvider) getLabelProvider(); + label.setText(labelProvider.getText(element)); + if (labelProvider instanceof IFontProvider) { + label.setFont(((IFontProvider) labelProvider).getFont(element)); + } + return label; + } + } // end ListItem class + + public LaunchBarListViewer(Composite parent) { + filterControl = new FilterControl(parent); + listScrolled = new ScrolledComposite(parent, SWT.V_SCROLL | SWT.NO_BACKGROUND); + listScrolled.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + listScrolled.setExpandHorizontal(true); + listComposite = new Composite(listScrolled, SWT.NONE); + listScrolled.setContent(listComposite); + listComposite.setLayout(GridLayoutFactory.fillDefaults().spacing(0, 0).create()); + selIndex = -1; + scrollBucket = 0; + filterControl.attachListViewer(this); + historySupported = false; + setHistoryPreferenceName(getHistoryPreferenceName()); + } + + private void createSash(final Composite listComp) { + if (separatorIndex < 0 || !historySupported) + return; + sash = new Sash(listComp, SWT.BORDER | SWT.HORIZONTAL); + sash.setLayoutData(GridDataFactory.fillDefaults().create()); + if (separatorIndex < listItems.length) + sash.moveAbove(listItems[separatorIndex]); + else + sash.moveBelow(null); + sash.addListener(SWT.Selection, new Listener() { + @Override + public void handleEvent(Event e) { + separatorIndex = (e.y + itemH / 2) / itemH; + } + }); + sash.addMouseListener(new MouseListener() { + @Override + public void mouseUp(MouseEvent e) { + setSeparatorIndex(separatorIndex); // call setter if it was overriden + if (separatorIndex >= 0) { + if (separatorIndex < listItems.length) + sash.moveAbove(listItems[separatorIndex]); + else + sash.moveBelow(null); + listComp.layout(); + } + } + + @Override + public void mouseDown(MouseEvent e) { + sash.moveAbove(null); // keep on top so user see it when moving + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + // ignore + } + }); + sash.setToolTipText("Increase/Decrease size of recently used elements pane"); + } + + @Override + public Control getControl() { + return listScrolled; + } + + @Override + protected void inputChanged(Object input, Object oldInput) { + super.inputChanged(input, oldInput); + refreshAll(); + } + + protected void refreshAll() { + selIndex = -1; + Control[] children = listComposite.getChildren(); + for (Control control : children) { + control.dispose(); + } + Object[] origElements = getElements(); + Object[] elements = filterElements(origElements); + listItems = new ListItem[elements.length]; + if (elements.length > 0) { + listItems[0] = createListItem(elements, 0); + itemH = Math.max(listItems[0].computeSize(SWT.DEFAULT, SWT.DEFAULT).y, 16); + for (int i = 1; i < elements.length; ++i) { + listItems[i] = createListItem(elements, i); + } + createSash(listComposite); + } + listComposite.pack(true); + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true); + if (elements.length > maxScrollBucket) { + Rectangle bounds = listItems[maxScrollBucket].getBounds(); + gd.heightHint = Math.max(bounds.y + bounds.height, itemH * (maxScrollBucket + 1)); + } + listScrolled.setLayoutData(gd); + listScrolled.layout(true); + } + + private ListItem createListItem(Object[] elements, int i) { + ListItem item = new ListItem(listComposite, SWT.NONE, elements[i], i, (ILabelProvider) getLabelProvider(), modifier); + GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false); + item.setLayoutData(gd); + if (i <= maxScrollBucket) { // this is how many visible by default + item.lazyInit(); + } else { + gd.heightHint = itemH; + } + return item; + } + + @Override + protected Widget doFindInputItem(Object element) { + return doFindItem(element); + } + + @Override + protected Widget doFindItem(Object element) { + if (listItems == null) + return null; + for (ListItem listItem : listItems) { + if (listItem.element.equals(element)) + return listItem; + } + return null; + } + + @Override + protected void doUpdateItem(Widget item, Object element, boolean fullMap) { + if (item instanceof ListItem) { + ((ListItem) item).lazyInit(); + } + } + + @Override + protected List getSelectionFromWidget() { + ArrayList arrayList = new ArrayList<>(); + if (selIndex >= 0) + arrayList.add(listItems[selIndex].element); + return arrayList; + } + + @Override + protected void internalRefresh(Object element) { + if (element == null || element == getRoot()) { + refreshAll(); + return; + } + ListItem item = (ListItem) doFindItem(element); + ILabelProvider lp = (ILabelProvider) getLabelProvider(); + if (lp == null || item == null) + return; + item.setImage(lp.getImage(element)); + item.setText(lp.getText(element)); + } + + private Object[] filterElements(Object[] elements) { + Object[] topElements = elements.clone(); + if (getComparator() != null) + getComparator().sort(this, elements); + if (getTopComparator() != null) + getTopComparator().sort(this, topElements); + // only bottom part will be filtered + Object[] result = elements; + if (getFilters() != null) { + for (ViewerFilter f : getFilters()) { + result = f.filter(this, (Object) null, result); + } + } + if (separatorIndex <= 0 || !historySupported) + return result; + if (separatorIndex >= topElements.length) { + return topElements; // all elements will fit in top elements + } + ILaunchDescriptor[] descsCopy = new ILaunchDescriptor[separatorIndex + result.length]; + System.arraycopy(topElements, 0, descsCopy, 0, separatorIndex); // copy first N elements + System.arraycopy(result, 0, descsCopy, separatorIndex, result.length); // copy all into rest + return descsCopy; + } + + private Object[] getElements() { + IStructuredContentProvider cp = (IStructuredContentProvider) getContentProvider(); + if (cp == null) + return new Object[0]; + Object[] elements = cp.getElements(getInput()); + return elements; + } + + @Override + public void reveal(Object element) { + // TODO Auto-generated method stub + } + + public void setDefaultSelection(StructuredSelection selection) { + finalSelection = true; + setSelection(selection, true); + } + + @Override + protected void setSelectionToWidget(List l, boolean reveal) { + if (l.size() == 0) { + return; + } + Object sel = l.get(0); + Widget wid = doFindItem(sel); + if (wid instanceof ListItem) { + ListItem listItem = (ListItem) wid; + listItem.setSelected(true); + } + } + + public int getSeparatorIndex() { + return separatorIndex; + } + + public void setSeparatorIndex(int separatorIndex) { + this.separatorIndex = separatorIndex; + if (separatorIndex <= 0) + return; + IPreferenceStore store = Activator.getDefault().getPreferenceStore(); + String prefName = getHistoryPreferenceName(); + if (prefName != null && store.getInt(prefName) != getSeparatorIndex()) { + store.setValue(prefName, getSeparatorIndex()); + } + } + + protected String getHistoryPreferenceName() { + return historyPref; + } + + public void setCellModifier(ICellModifier modifier) { + this.modifier = modifier; + } + + public int getItemCount() { + return listItems.length; + } + + /** + * Returns top element (provider element) in the begging on non-history list + * + * @return + */ + public Object getTopFilteredElement() { + if (listItems.length > 0) { + if (separatorIndex <= 0 || separatorIndex >= listItems.length || !historySupported) + return listItems[0].element; + else + return listItems[separatorIndex].element; + } + return null; + } + + public Object getTopElement() { + if (listItems.length > 0) { + return listItems[0].element; + } + return null; + } + + public ViewerComparator getTopComparator() { + return historyComparator; + } + + /** + * ViewerComparator comparator labels of elements by default + * + * @param comp + */ + public void setHistoryComparator(ViewerComparator comp) { + historyComparator = comp; + } + + public void setHistoryComparator(Comparator comp) { + historyComparator = comp == null ? null : new LaunchBarListViewerComparator(comp); + } + + public void setComparator(Comparator comp) { + setComparator(comp == null ? null : new LaunchBarListViewerComparator(comp)); + } + + public boolean isHistorySupported() { + return historySupported; + } + + public void setHistorySupported(boolean historySupported) { + this.historySupported = historySupported; + } + + public void setHistoryPreferenceName(String historyPreferenceName) { + this.historyPref = historyPreferenceName; + if (historyPreferenceName != null) { + IPreferenceStore store = Activator.getDefault().getPreferenceStore(); + int separator = store.getInt(historyPreferenceName); + if (separator <= 0) + separator = 1; + setSeparatorIndex(separator); + } + } + + /** + * final selection will be set to true when user made a final selection in a list for example when double click on entry or + * pressed enter key + */ + public boolean isFinalSelection() { + return finalSelection; + } + + public void setFinalSelection(boolean finalSelection) { + this.finalSelection = finalSelection; + } + + public static void main(String[] args) { + Display display = Display.getDefault(); + Shell shell = new Shell(); + RowLayout rowLayout = new RowLayout(); + shell.setLayout(rowLayout); + (new Label(shell, SWT.NULL)).setText("Hello"); + LaunchBarListViewer v = new LaunchBarListViewer(shell); + v.setContentProvider(new ArrayContentProvider()); + v.setLabelProvider(new LabelProvider()); + v.setInput(new String[] { "aaa", "bbb", "ccc" }); + shell.pack(); + shell.open(); + // textUser.forceFocus(); + // Set up the event loop. + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + // If no more entries in event queue + display.sleep(); + } + } + display.dispose(); + } + + public void setFocus() { + if (selIndex >= 0 && listItems != null && listItems.length < selIndex) + listItems[selIndex].setFocus(); + else + getControl().setFocus(); + } + + public void setFilterVisible(boolean vis) { + filterControl.setVisible(vis); + } +} diff --git a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/TargetSelector.java b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/TargetSelector.java index 38b468b6b87..1524209925a 100644 --- a/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/TargetSelector.java +++ b/bundles/org.eclipse.launchbar.ui/src/org/eclipse/launchbar/ui/internal/controls/TargetSelector.java @@ -184,7 +184,7 @@ public class TargetSelector extends CSelector { GridLayout buttonLayout = new GridLayout(); buttonLayout.marginWidth = buttonLayout.marginHeight = 7; createButton.setLayout(buttonLayout); - createButton.setBackground(white); + createButton.setBackground(backgroundColor); createButton.addPaintListener(new PaintListener() { @Override public void paintControl(PaintEvent e) { @@ -198,7 +198,7 @@ public class TargetSelector extends CSelector { final Label createLabel = new Label(createButton, SWT.None); createLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); createLabel.setText("Create New Target..."); - createLabel.setBackground(white); + createLabel.setBackground(backgroundColor); MouseListener mouseListener = new MouseAdapter() { public void mouseUp(org.eclipse.swt.events.MouseEvent e) {