| /* |
| * 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); |
| } |
| |
| } |