blob: ee58bd3d68033a7ae6a39c02bd712b66fe58586b [file] [log] [blame]
/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.ui.omni;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.DartCoreDebug;
import com.google.dart.tools.ui.DartToolsPlugin;
import com.google.dart.tools.ui.DartUI;
import com.google.dart.tools.ui.omni.elements.FileProvider;
import com.google.dart.tools.ui.omni.elements.HeaderElement;
import com.google.dart.tools.ui.omni.elements.ProviderCompleteListener;
import com.google.dart.tools.ui.omni.elements.TextSearchElement;
import com.google.dart.tools.ui.omni.elements.TextSearchProvider;
import com.google.dart.tools.ui.omni.elements.TopLevelElementProvider_NEW;
import com.google.dart.tools.ui.omni.elements.TypeElement_OLD;
import com.google.dart.tools.ui.omni.elements.TypeProvider_OLD;
import org.eclipse.core.commands.Command;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.SWTKeySupport;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.util.Util;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyAdapter;
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.MouseMoveListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.TextLayout;
import org.eclipse.swt.graphics.TextStyle;
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.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.IWorkbenchPreferenceConstants;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.progress.ProgressManagerUtil;
import org.eclipse.ui.keys.IBindingService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@SuppressWarnings("restriction")
public class OmniBoxPopup extends BasePopupDialog {
private class OmniRefreshJob extends Job {
OmniRefreshJob() {
super("Refreshing searchbox results...");
setSystem(true); //suppress UI notifications on refresh
}
@Override
protected IStatus run(IProgressMonitor monitor) {
refreshInternal(searchFilter);
return Status.OK_STATUS;
}
}
private class PreviousPicksProvider extends OmniProposalProvider implements IShellProvider {
@Override
public OmniElement getElementForId(String id) {
return null;
}
@Override
public OmniElement[] getElements(String pattern) {
return previousPicksList.toArray(new OmniElement[previousPicksList.size()]);
}
@Override
public OmniElement[] getElementsSorted(String pattern) {
return getElements(pattern);
}
@Override
public String getId() {
return PreviousPicksProvider.class.getName();
}
@Override
public String getName() {
return OmniBoxMessages.OmniBox_Previous;
}
@Override
public Shell getShell() {
return OmniBoxPopup.this.getShell();
}
}
// DO NOT steal focus on open (dartbug.com/3784).
// NOTE: requires the HOVER_SHELLSTYLE to work on GTK linux.
private static final boolean FOCUS_ON_OPEN = false;
private static final int INITIAL_COUNT_PER_PROVIDER = 5;
private static final int MAX_COUNT_TOTAL = 20;
private OmniProposalProvider[] providers;
private final IWorkbenchWindow window;
protected Table table;
private LocalResourceManager resourceManager = new LocalResourceManager(
JFaceResources.getResources());
private static final String TEXT_ARRAY = "textArray"; //$NON-NLS-1$
private static final String TEXT_ENTRIES = "textEntries"; //$NON-NLS-1$
private static final String ORDERED_PROVIDERS = "orderedProviders"; //$NON-NLS-1$
private static final String ORDERED_ELEMENTS = "orderedElements"; //$NON-NLS-1$
static final int MAXIMUM_NUMBER_OF_ELEMENTS = 60;
static final int MAXIMUM_NUMBER_OF_TEXT_ENTRIES_PER_ELEMENT = 3;
protected String rememberedText;
protected Map<Object, ArrayList<String>> textMap = new HashMap<Object, ArrayList<String>>();
protected Map<String, Object> elementMap = new HashMap<String, Object>();
private LinkedList<OmniElement> previousPicksList = new LinkedList<OmniElement>();
protected Map<String, OmniProposalProvider> providerMap;
private TextLayout textLayout;
private TriggerSequence[] invokingCommandKeySequences;
private Command invokingCommand;
private KeyAdapter keyAdapter;
private boolean showAllMatches = true;
protected boolean resized = false;
private Text filterControl;
private Job refreshJob = new OmniRefreshJob();
private String searchFilter;
private int searchItemCount;
private String searchText;
//used to restore selection post table refresh
private TableItem cachedSelection;
public OmniBoxPopup(IWorkbenchWindow window, final Command invokingCommand) {
super(
ProgressManagerUtil.getDefaultParent(),
HOVER_SHELLSTYLE,
FOCUS_ON_OPEN /* take focus on opening*/,
false /* persist size */,
false /* persist location */,
false /* show dialog menu menu */,
false /* show persist actions */,
null,
"" /*null*//* OmniBoxMessages.OmniBox_StartTypingToFindMatches */);
this.window = window;
BusyIndicator.showWhile(
window.getShell() == null ? null : window.getShell().getDisplay(),
new Runnable() {
@Override
public void run() {
OmniBoxPopup.this.providers = createProviders();
providerMap = new HashMap<String, OmniProposalProvider>();
for (int i = 0; i < providers.length; i++) {
providerMap.put(providers[i].getId(), providers[i]);
}
restoreDialog();
OmniBoxPopup.this.invokingCommand = invokingCommand;
if (OmniBoxPopup.this.invokingCommand != null
&& !OmniBoxPopup.this.invokingCommand.isDefined()) {
OmniBoxPopup.this.invokingCommand = null;
} else {
// Pre-fetch key sequence - do not change because scope will
// change later.
getInvokingCommandKeySequences();
}
// create early
create();
}
});
// Ugly hack to avoid bug 184045. If this gets fixed, replace the
// following code with a call to refresh("").
getShell().getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
final Shell shell = getShell();
if (shell != null && !shell.isDisposed()) {
Point size = shell.getSize();
shell.setSize(size.x, size.y + 1);
}
}
});
}
@Override
public boolean close() {
storeDialog(getDialogSettings());
if (textLayout != null && !textLayout.isDisposed()) {
textLayout.dispose();
}
if (resourceManager != null) {
resourceManager.dispose();
resourceManager = null;
}
return super.close();
}
public String getFilterTextExactCase() {
return searchText != null ? searchText : ""; // this should be private
}
public boolean isDisposed() {
return textLayout.isDisposed();
}
public void sendKeyPress(KeyEvent e) {
int itemCount = table.getItemCount();
switch (e.keyCode) {
case SWT.CR:
case SWT.KEYPAD_CR:
handleSelection();
break;
case SWT.ARROW_DOWN: {
e.doit = false;
int index = table.getSelectionIndex();
int numCycles = 0;
while (true) {
index++;
if (index >= itemCount) {
index = 0;
numCycles++;
if (numCycles >= 2) {
return;
}
}
if (index < 0 || index >= itemCount) {
return;
}
if (!isHeader(table.getItem(index))) {
break;
}
}
table.setSelection(index);
redrawTableAfterSetSelection();
break;
}
case SWT.ARROW_UP: {
e.doit = false;
int index = table.getSelectionIndex();
int numCycles = 0;
while (true) {
index--;
if (index < 0) {
index = itemCount - 1;
numCycles++;
if (numCycles >= 2) {
return;
}
}
if (index < 0 || index >= itemCount) {
return;
}
if (!isHeader(table.getItem(index))) {
break;
}
}
table.setSelection(index);
redrawTableAfterSetSelection();
break;
}
case SWT.ESC:
close();
break;
}
if (!table.isDisposed()) {
TableItem[] items = table.getSelection();
if (items.length > 0) {
TableItem selection = items[0];
Object data = selection.getData();
String info = "";
if (data instanceof OmniEntry) {
OmniElement element = ((OmniEntry) data).getElement();
info = element.getInfoLabel();
}
setInfoText(info);
}
}
}
@Override
protected void adjustBounds() {
//calculate a new height (in case new table items have been added), but leave width alone
getShell().layout();
int maxHeight = (int) (window.getShell().getBounds().height * .66);
int width = getShell().getSize().x;
Point shellSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
int height = Math.min(maxHeight, shellSize.y);
getShell().setSize(width, height);
}
@Override
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite) super.createDialogArea(parent);
boolean isWin32 = Util.isWindows();
GridLayoutFactory.fillDefaults().extendedMargins(isWin32 ? 0 : 3, 3, 2, 2).applyTo(composite);
Composite tableComposite = new Composite(composite, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).applyTo(tableComposite);
TableColumnLayout tableColumnLayout = new TableColumnLayout();
tableComposite.setLayout(tableColumnLayout);
table = new Table(tableComposite, SWT.SINGLE | SWT.FULL_SELECTION);
textLayout = new TextLayout(table.getDisplay());
textLayout.setOrientation(getDefaultOrientation());
Font boldFont = resourceManager.createFont(FontDescriptor.createFrom(
JFaceResources.getDialogFont()).setStyle(SWT.BOLD));
textLayout.setFont(table.getFont());
textLayout.setText(OmniBoxMessages.OmniBox_Providers);
int maxProviderWidth = (int) (textLayout.getBounds().width * 1.1);
textLayout.setFont(boldFont);
for (int i = 0; i < providers.length; i++) {
OmniProposalProvider provider = providers[i];
textLayout.setText(provider.getName());
int width = (int) (textLayout.getBounds().width * 1.1);
if (width > maxProviderWidth) {
maxProviderWidth = width;
}
}
//TODO (pquitslund): just a placeholder column for now
tableColumnLayout.setColumnData(
new TableColumn(table, SWT.NONE),
new ColumnWeightData(0, 3 /* maxProviderWidth) */));
tableColumnLayout.setColumnData(
new TableColumn(table, SWT.NONE),
new ColumnWeightData(100, 100));
//TODO (pquitslund): and with this goes the ability to resize...
// table.getShell().addControlListener(new ControlAdapter() {
// @Override
// public void controlResized(ControlEvent e) {
// if (!showAllMatches) {
// if (!resized) {
// resized = true;
// e.display.timerExec(100, new Runnable() {
// @Override
// public void run() {
// if (getShell() != null && !getShell().isDisposed()) {
// refresh(getFilterText());
// }
// resized = false;
// }
//
// });
// }
// }
// }
// });
/*
* Since the control is unfocused, we need to hijack paint events and draw our own selections.
*/
table.addListener(SWT.EraseItem, new Listener() {
@Override
public void handleEvent(Event event) {
event.detail &= ~SWT.HOT;
if ((event.detail & SWT.SELECTED) == 0) {
return; /* item not selected */
}
Widget item = event.item;
if (item instanceof TableItem) {
Object data = ((TableItem) item).getData();
if (data instanceof OmniEntry) {
if (((OmniEntry) data).element instanceof HeaderElement) {
event.detail &= ~SWT.SELECTED;
return;
}
}
}
final Color selectionBackColor = getSelectionBackground();
final Color selectionForeColor = getSelectionForeground();
int clientWidth = table.getClientArea().width;
GC gc = event.gc;
Color oldBackground = gc.getBackground();
Color oldForeground = gc.getForeground();
gc.setBackground(selectionBackColor);
gc.setForeground(selectionForeColor);
gc.fillRectangle(new Rectangle(0, event.y, clientWidth, event.height));
gc.setBackground(oldBackground);
gc.setForeground(oldForeground);
event.detail &= ~SWT.SELECTED;
}
});
table.addKeyListener(getKeyAdapter());
table.addKeyListener(new KeyListener() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ARROW_UP && table.getSelectionIndex() == 0) {
setFilterFocus();
} else if (e.character == SWT.ESC) {
close();
}
}
@Override
public void keyReleased(KeyEvent e) {
// do nothing
}
});
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
if (table.getSelectionCount() < 1) {
return;
}
if (e.button != 1) {
return;
}
if (table.equals(e.getSource())) {
Object o = table.getItem(new Point(e.x, e.y));
TableItem selection = table.getSelection()[0];
if (selection.equals(o)) {
handleSelection();
}
}
}
});
table.addMouseMoveListener(new MouseMoveListener() {
TableItem lastItem = null;
@Override
public void mouseMove(MouseEvent e) {
if (table.equals(e.getSource())) {
Object o = table.getItem(new Point(e.x, e.y));
if (lastItem == null ^ o == null) {
table.setCursor(o == null ? null : table.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
}
if (o instanceof TableItem) {
if (!o.equals(lastItem)) {
lastItem = (TableItem) o;
table.setSelection(new TableItem[] {lastItem});
redrawTableAfterSetSelection();
}
} else if (o == null) {
lastItem = null;
}
}
}
});
table.addSelectionListener(new SelectionListener() {
@Override
public void widgetDefaultSelected(SelectionEvent e) {
handleSelection();
}
@Override
public void widgetSelected(SelectionEvent e) {
if (Util.isMac()) {
handleSelection();
}
}
});
final TextStyle boldStyle;
if (PlatformUI.getPreferenceStore().getBoolean(IWorkbenchPreferenceConstants.USE_COLORED_LABELS)) {
boldStyle = new TextStyle(boldFont, null, null);
// italicsFont = resourceManager.createFont(FontDescriptor.createFrom(
// table.getFont()).setStyle(SWT.ITALIC));
} else {
boldStyle = null;
}
final TextStyle grayStyle = new TextStyle(
table.getFont(),
OmniBoxColors.SEARCH_ENTRY_ITEM_TEXT,
null);
Listener listener = new Listener() {
@Override
public void handleEvent(Event event) {
OmniEntry entry = (OmniEntry) event.item.getData();
if (entry != null) {
switch (event.type) {
case SWT.MeasureItem:
entry.measure(event, textLayout, resourceManager, boldStyle);
break;
case SWT.PaintItem:
entry.paint(event, textLayout, resourceManager, boldStyle, grayStyle);
break;
case SWT.EraseItem:
entry.erase(event);
break;
}
}
}
};
table.addListener(SWT.MeasureItem, listener);
table.addListener(SWT.EraseItem, listener);
table.addListener(SWT.PaintItem, listener);
//In GTK linux, the table is hungry for focus and steals it on updates
//When the table has focus it grabs key events that are intended for the
//search entry box; to make things right, we need to punt focus back
//to the search box
if (Util.isLinux()) {
table.addFocusListener(new FocusListener() {
@Override
public void focusGained(FocusEvent e) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
//punt focus back to the text box
getFocusControl().setFocus();
}
});
}
@Override
public void focusLost(FocusEvent e) {
}
});
}
return composite;
}
@Override
protected Color getBackground() {
Color color = DartUI.getViewerBackground(
DartToolsPlugin.getDefault().getCombinedPreferenceStore(),
Display.getDefault());
return color == null ? OmniBoxColors.SEARCH_RESULT_BACKGROUND : color;
}
@Override
protected Point getDefaultLocation(Point initialSize) {
Point size = new Point(400, 400);
Rectangle parentBounds = getParentShell().getBounds();
int x = parentBounds.x + parentBounds.width / 2 - size.x / 2;
int y = parentBounds.y + parentBounds.height / 2 - size.y / 2;
return new Point(x, y);
}
@Override
protected Point getDefaultSize() {
GC gc = new GC(table);
FontMetrics fontMetrics = gc.getFontMetrics();
gc.dispose();
int x = Dialog.convertHorizontalDLUsToPixels(fontMetrics, 300);
x = Math.max(x, 350);
int y = Dialog.convertVerticalDLUsToPixels(fontMetrics, 270);
y = Math.max(y, 420);
return new Point(x, y);
}
@Override
protected IDialogSettings getDialogSettings() {
final IDialogSettings dialogSettings = DartToolsPlugin.getDefault().getDialogSettings();
IDialogSettings result = dialogSettings.getSection(getId());
if (result == null) {
result = dialogSettings.addNewSection(getId());
}
return result;
}
@Override
protected Control getFocusControl() {
if (filterControl != null) {
return filterControl;
}
return super.getFocusControl();
// return filterText;
}
@Override
protected Color getForeground() {
Color color = DartUI.getViewerForeground(
DartToolsPlugin.getDefault().getCombinedPreferenceStore(),
Display.getDefault());
return color == null ? super.getForeground() : color;
}
protected String getId() {
return getClass().getName();
}
final protected TriggerSequence[] getInvokingCommandKeySequences() {
if (invokingCommandKeySequences == null) {
if (invokingCommand != null) {
IBindingService bindingService = (IBindingService) window.getWorkbench().getAdapter(
IBindingService.class);
invokingCommandKeySequences = bindingService.getActiveBindingsFor(invokingCommand.getId());
}
}
return invokingCommandKeySequences;
}
protected Color getSelectionBackground() {
Color color = DartUI.getViewerSelectionBackground(
DartToolsPlugin.getDefault().getCombinedPreferenceStore(),
Display.getDefault());
return color == null ? Display.getDefault().getSystemColor(SWT.COLOR_LIST_SELECTION) : color;
}
protected Color getSelectionForeground() {
Color color = DartUI.getViewerSelectionForeground(
DartToolsPlugin.getDefault().getCombinedPreferenceStore(),
Display.getDefault());
return color == null ? super.getForeground() : color;
}
protected void handleElementSelected(String text, OmniElement selectedElement) {
addPreviousPick(text, selectedElement.getMemento());
storeDialog(getDialogSettings());
OmniElement element = selectedElement;
element.execute(text);
}
protected void toggleShowAllMatches() {
showAllMatches = !showAllMatches;
refresh(getFilterText());
}
void refresh(String filter) {
if (table.isDisposed()) {
return;
}
searchText = filterControl.getText();
searchItemCount = computeNumberOfItems();
searchFilter = filter;
refreshJob.setPriority(Job.INTERACTIVE);
refreshJob.schedule();
// refreshInternal(filter);
}
void setFilterControl(Text filterControl) {
this.filterControl = filterControl;
}
private void addPreviousPick(String text, OmniElement element) {
//header elements (e.g, "search in progress") should not get cached
if (element instanceof HeaderElement) {
return;
}
// previousPicksList:
// Remove element from previousPicksList so there are no duplicates
// If list is max size, remove last(oldest) element
// Remove entries for removed element from elementMap and textMap
// Add element to front of previousPicksList
previousPicksList.remove(element);
if (previousPicksList.size() == MAXIMUM_NUMBER_OF_ELEMENTS) {
Object removedElement = previousPicksList.removeLast();
ArrayList<String> removedList = textMap.remove(removedElement);
for (int i = 0; i < removedList.size(); i++) {
elementMap.remove(removedList.get(i));
}
}
previousPicksList.addFirst(element);
// textMap:
// Get list of strings for element from textMap
// Create new list for element if there isn't one and put
// element->textList in textMap
// Remove rememberedText from list
// If list is max size, remove first(oldest) string
// Remove text from elementMap
// Add rememberedText to list of strings for element in textMap
ArrayList<String> textList = textMap.get(element);
if (textList == null) {
textList = new ArrayList<String>();
textMap.put(element, textList);
}
textList.remove(text);
if (textList.size() == MAXIMUM_NUMBER_OF_TEXT_ENTRIES_PER_ELEMENT) {
Object removedText = textList.remove(0);
elementMap.remove(removedText);
}
if (text.length() > 0) {
textList.add(text);
// elementMap:
// Put rememberedText->element in elementMap
// If it replaced a different element update textMap and
// PreviousPicksList
Object replacedElement = elementMap.put(text, element);
if (replacedElement != null && !replacedElement.equals(element)) {
textList = textMap.get(replacedElement);
if (textList != null) {
textList.remove(text);
if (textList.isEmpty()) {
textMap.remove(replacedElement);
previousPicksList.remove(replacedElement);
}
}
}
}
}
private OmniElement calculateDefaultSelection(String text) {
//A simple heuristic: default to the first type proposal
for (TableItem item : table.getItems()) {
Object data = item.getData();
if (data instanceof OmniEntry) {
OmniElement element = ((OmniEntry) data).element;
if (element instanceof TypeElement_OLD) {
return element;
}
}
}
//Fall back to text search
for (TableItem item : table.getItems()) {
Object data = item.getData();
if (data instanceof OmniEntry) {
OmniElement element = ((OmniEntry) data).element;
if (element instanceof TextSearchElement) {
return element;
}
}
}
//Shouldn't get here
return null;
}
private List<OmniEntry>[] computeMatchingEntries(String filter, OmniElement perfectMatch,
int maxCount) {
// collect matches in an array of lists
@SuppressWarnings("unchecked")
List<OmniEntry>[] entries = new ArrayList[providers.length];
int[] indexPerProvider = new int[providers.length];
int countPerProvider = Math.min(maxCount / 4, INITIAL_COUNT_PER_PROVIDER);
int countTotal = 0;
boolean perfectMatchAdded = true;
if (perfectMatch != null) {
// reserve one entry for the perfect match
maxCount--;
perfectMatchAdded = false;
}
boolean done;
do {
// will be set to false if we find a provider with remaining elements
done = true;
for (int i = 0; i < providers.length && (showAllMatches || countTotal < maxCount); i++) {
if (entries[i] == null) {
entries[i] = new ArrayList<OmniEntry>();
indexPerProvider[i] = 0;
}
int count = 0;
OmniProposalProvider provider = providers[i];
if (filter.length() > 0 || provider instanceof PreviousPicksProvider || showAllMatches) {
OmniElement[] elements = provider.getElementsSorted(filter);
int j = indexPerProvider[i];
while (j < elements.length
&& (showAllMatches || (count < countPerProvider && countTotal < maxCount))) {
OmniElement element = elements[j];
OmniEntry entry;
if (filter.length() == 0) {
if (i == 0 || showAllMatches) {
entry = new OmniEntry(element, provider, new int[0][0], new int[0][0]);
} else {
entry = null;
}
} else {
entry = element.match(filter, provider);
}
if (entry != null) {
entries[i].add(entry);
count++;
countTotal++;
if (i == 0 && entry.element == perfectMatch) {
perfectMatchAdded = true;
maxCount = MAX_COUNT_TOTAL;
}
}
j++;
}
indexPerProvider[i] = j;
if (j < elements.length) {
done = false;
}
}
}
// from now on, add one element per provider
countPerProvider = 1;
} while ((showAllMatches || countTotal < maxCount) && !done);
if (!perfectMatchAdded) {
OmniEntry entry = perfectMatch.match(filter, providers[0]);
if (entry != null) {
if (entries[0] == null) {
entries[0] = new ArrayList<OmniEntry>();
indexPerProvider[0] = 0;
}
entries[0].add(entry);
}
}
return entries;
}
private int computeNumberOfItems() {
Rectangle rect = table.getClientArea();
int itemHeight = table.getItemHeight();
int headerHeight = table.getHeaderHeight();
return (rect.height - headerHeight + itemHeight - 1) / (itemHeight + table.getGridLineWidth());
}
private OmniProposalProvider[] createProviders() {
IProgressMonitor pm = getProgressMonitor();
if (DartCoreDebug.ENABLE_ANALYSIS_SERVER) {
return new OmniProposalProvider[] {
new PreviousPicksProvider(), new TextSearchProvider(this),
new TopLevelElementProvider_NEW(pm), FileProvider.projectFiles(pm),
// new EditorProvider(),
// new ActionProvider(),
// new PreferenceProvider(),
// new ViewProvider()
};
} else {
return new OmniProposalProvider[] {
new PreviousPicksProvider(), new TextSearchProvider(this), new TypeProvider_OLD(pm),
FileProvider.projectFiles(pm),
// new EditorProvider(),
// new ActionProvider(),
// new PreferenceProvider(),
// new ViewProvider()
};
}
}
private OmniEntry getCurrentSelection() {
TableItem[] selection = table.getSelection();
if (selection.length > 0) {
return (OmniEntry) selection[0].getData();
}
return null;
}
private String getFilterText() {
return getFilterTextExactCase();
}
private KeyAdapter getKeyAdapter() {
if (keyAdapter == null) {
keyAdapter = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int accelerator = SWTKeySupport.convertEventToUnmodifiedAccelerator(e);
KeySequence keySequence = KeySequence.getInstance(SWTKeySupport.convertAcceleratorToKeyStroke(accelerator));
TriggerSequence[] sequences = getInvokingCommandKeySequences();
if (sequences == null) {
return;
}
for (int i = 0; i < sequences.length; i++) {
if (sequences[i].equals(keySequence)) {
e.doit = false;
toggleShowAllMatches();
return;
}
}
}
};
}
return keyAdapter;
}
private IProgressMonitor getProgressMonitor() {
//TODO (pquitslund): get a proper progress monitor
return new NullProgressMonitor();
}
private void handleSelection() {
OmniElement selectedElement = null;
String text = getFilterText();
if (table.getSelectionCount() == 1) {
OmniEntry entry = (OmniEntry) table.getSelection()[0].getData();
if (entry != null) {
OmniElement element = entry.element;
//If a header is selected, do better and calculate a sensible default
selectedElement = element instanceof HeaderElement ? calculateDefaultSelection(text)
: element;
}
}
if (selectedElement != null) {
close();
handleElementSelected(text, selectedElement);
}
}
private boolean isHeader(TableItem item) {
Object data = item.getData();
if (data instanceof OmniEntry) {
return (((OmniEntry) data).element instanceof HeaderElement);
}
return false;
}
private void markDuplicates(List<OmniEntry>[] entries) {
final HashMap<String, OmniElement> seen = new HashMap<String, OmniElement>();
OmniElement current;
for (List<OmniEntry> entrySets : entries) {
if (entrySets != null) {
for (OmniEntry entry : entrySets) {
current = entry.element;
OmniElement previous = seen.get(current.getLabel());
if (previous != null) {
previous.setIsDuplicate(true);
current.setIsDuplicate(true);
} else {
seen.put(current.getLabel(), current);
}
}
}
}
}
/**
* By some reason on OSX {@link Table} do not always redraw itself after setting a selection. This
* looks as multiple selected items. So, we need to force redraw.
*/
private void redrawTableAfterSetSelection() {
if (DartCore.isMac()) {
table.redraw();
}
}
private void refreshInternal(final String filter) {
//an empty filter indicates a new query, meaning we need to clear caches
if (filter.length() == 0) {
for (OmniProposalProvider provider : providers) {
provider.reset();
}
}
//TODO (pquitslund): the type provider generates results asynchronously, requiring a reset
//to ensure a refresh --- if/when other provides go async, this special casing should
//get generalized
for (OmniProposalProvider provider : providers) {
if (DartCoreDebug.ENABLE_ANALYSIS_SERVER) {
if (provider instanceof TopLevelElementProvider_NEW) {
TopLevelElementProvider_NEW topProvider = (TopLevelElementProvider_NEW) provider;
provider.getElements(filter);
topProvider.onComplete(new ProviderCompleteListener() {
@Override
public void complete(OmniProposalProvider provider) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
try {
refresh(getFilterText());
} catch (SWTException e) {
//ignore dispose
}
}
});
}
});
provider.reset();
}
} else {
if (provider instanceof TypeProvider_OLD) {
provider.reset();
}
}
}
// perfect match, to be selected in the table if not null
final OmniElement perfectMatch = (OmniElement) elementMap.get(filter);
final List<OmniEntry>[] entries = computeMatchingEntries(filter, perfectMatch, searchItemCount);
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
//a blanket try/catch to contain widget disposal errors
try {
refreshInternalContent(filter, perfectMatch, entries);
} catch (SWTException e) {
//if it's not a widget disposed error, re-throw
if (e.code != SWT.ERROR_WIDGET_DISPOSED) {
throw e;
}
}
}
});
}
private void refreshInternalContent(String filter, OmniElement perfectMatch,
List<OmniEntry>[] entries) {
int selectionIndex = refreshTable(perfectMatch, entries);
if (table.isDisposed()) {
return;
}
if (table.getItemCount() > 0) {
if (cachedSelection != null) {
table.setSelection(cachedSelection);
} else {
table.setSelection(selectionIndex);
}
} else if (filter.length() == 0) {
{
TableItem item = new TableItem(table, SWT.NONE);
item.setText(0, OmniBoxMessages.OmniBox_Providers);
item.setForeground(0, OmniBoxColors.SEARCH_ENTRY_ITEM_TEXT);
}
for (int i = 0; i < providers.length; i++) {
OmniProposalProvider provider = providers[i];
TableItem item = new TableItem(table, SWT.NONE);
item.setText(1, provider.getName());
item.setForeground(1, OmniBoxColors.SEARCH_ENTRY_ITEM_TEXT);
}
}
// if (filter.length() == 0) {
// setInfoText(OmniBoxMessages.OmniBox_StartTypingToFindMatches);
// } else {
// TriggerSequence[] sequences = getInvokingCommandKeySequences();
// if (sequences != null && sequences.length != 0) {
// if (showAllMatches) {
// setInfoText(NLS.bind(
// OmniBoxMessages.OmniBox_PressKeyToShowInitialMatches,
// sequences[0].format()));
// } else {
// setInfoText(NLS.bind(
// OmniBoxMessages.OmniBox_PressKeyToShowAllMatches,
// sequences[0].format()));
// }
// } else {
// setInfoText(""); //$NON-NLS-1$
// }
// }
}
private int refreshTable(OmniElement perfectMatch, List<OmniEntry>[] entries) {
if (table.isDisposed()) {
return 0;
}
//used to restore selection post-refresh
OmniEntry previousSelection = getCurrentSelection();
//TODO (pquitslund): clearing to force a complete redraw; prime for future optimization
//if (table.getItemCount() > entries.length && table.getItemCount() - entries.length > 20) {
// table.removeAll();
//}
table.removeAll();
//elements flagged as duplicates get rendered with disambiguating details
markDuplicates(entries);
TableItem[] items = table.getItems();
int selectionIndex = -1;
int index = 0;
TableItem item = null;
for (int i = 0; i < providers.length; i++) {
if (entries[i] != null) {
Iterator<OmniEntry> iterator = entries[i].iterator();
//create headers for non-empty categories
if (iterator.hasNext()) {
item = new TableItem(table, SWT.NONE);
item.setData(new OmniEntry(
new HeaderElement(providers[i]),
providers[i],
new int[0][0],
new int[0][0]));
}
//create entries
for (Iterator<OmniEntry> it = entries[i].iterator(); it.hasNext();) {
OmniEntry entry = it.next();
if (!it.hasNext()) {
entry.lastInCategory = true;
}
if (index < items.length) {
item = items[index];
table.clear(index);
} else {
item = new TableItem(table, SWT.NONE);
}
if (perfectMatch == entry.element && selectionIndex == -1) {
selectionIndex = index;
cachedSelection = null;
} else if (previousSelection != null) {
if (entry.element.isSameAs(previousSelection.element)) {
//NOTE: with async table updates, selection index is not sufficient
cachedSelection = item;
}
}
item.setData(entry);
item.setText(0, entry.provider.getName());
item.setText(1, entry.element.getLabel());
if (Util.isWpf()) {
item.setImage(1, entry.getImage(entry.element, resourceManager));
}
index++;
}
}
}
if (index < items.length) {
table.remove(index, items.length - 1);
}
//last entry should not be flagged since that will produce a trailing separator
if (item != null) {
((OmniEntry) item.getData()).lastInCategory = false;
}
adjustBounds();
if (selectionIndex == -1) {
selectionIndex = 0;
}
return selectionIndex;
}
private void restoreDialog() {
//TODO(pquitslund): re-enable/remove pending investigation (dartbug.com/5005).
// IDialogSettings dialogSettings = getDialogSettings();
// if (dialogSettings != null) {
// String[] orderedElements = dialogSettings.getArray(ORDERED_ELEMENTS);
// String[] orderedProviders = dialogSettings.getArray(ORDERED_PROVIDERS);
// String[] textEntries = dialogSettings.getArray(TEXT_ENTRIES);
// String[] textArray = dialogSettings.getArray(TEXT_ARRAY);
// elementMap = new HashMap<String, Object>();
// textMap = new HashMap<Object, ArrayList<String>>();
// previousPicksList = new LinkedList<OmniElement>();
// if (orderedElements != null && orderedProviders != null && textEntries != null
// && textArray != null) {
// int arrayIndex = 0;
// for (int i = 0; i < orderedElements.length; i++) {
// OmniProposalProvider omniElementProvider = providerMap.get(orderedProviders[i]);
// int numTexts = Integer.parseInt(textEntries[i]);
// if (omniElementProvider != null) {
// OmniElement omniElement = omniElementProvider.getElementForId(orderedElements[i]);
// if (omniElement != null) {
// ArrayList<String> arrayList = new ArrayList<String>();
// for (int j = arrayIndex; j < arrayIndex + numTexts; j++) {
// String text = textArray[j];
// if (text.length() > 0) {
// arrayList.add(text);
// elementMap.put(text, omniElement);
// }
// }
// textMap.put(omniElement, arrayList);
// previousPicksList.add(omniElement);
// }
// }
// arrayIndex += numTexts;
// }
// }
// }
}
private void setFilterFocus() {
// filterText.setFocus();
}
private void storeDialog(IDialogSettings dialogSettings) {
String[] orderedElements = new String[previousPicksList.size()];
String[] orderedProviders = new String[previousPicksList.size()];
String[] textEntries = new String[previousPicksList.size()];
ArrayList<String> arrayList = new ArrayList<String>();
for (int i = 0; i < orderedElements.length; i++) {
OmniElement omniElement = previousPicksList.get(i);
ArrayList<String> elementText = textMap.get(omniElement);
Assert.isNotNull(elementText);
orderedElements[i] = omniElement.getId();
orderedProviders[i] = omniElement.getProvider().getId();
arrayList.addAll(elementText);
textEntries[i] = elementText.size() + ""; //$NON-NLS-1$
}
String[] textArray = arrayList.toArray(new String[arrayList.size()]);
dialogSettings.put(ORDERED_ELEMENTS, orderedElements);
dialogSettings.put(ORDERED_PROVIDERS, orderedProviders);
dialogSettings.put(TEXT_ENTRIES, textEntries);
dialogSettings.put(TEXT_ARRAY, textArray);
}
}