| /******************************************************************************* |
| * Copyright (c) 2005, 2016 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Tom Hochstein (Freescale) - Bug 393703 - NotHandledException selecting inactive command under 'Previous Choices' in Quick access |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654, 491272, 491398 |
| * Leung Wang Hei <gemaspecial@yahoo.com.hk> - Bug 483343 |
| * Patrik Suzzi <psuzzi@gmail.com> - Bug 491291, 491529, 491293, 492434 |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.quickaccess; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import org.eclipse.core.runtime.Adapters; |
| import org.eclipse.jface.bindings.TriggerSequence; |
| import org.eclipse.jface.dialogs.IDialogSettings; |
| import org.eclipse.jface.layout.GridDataFactory; |
| 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.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.ControlAdapter; |
| import org.eclipse.swt.events.ControlEvent; |
| 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.ModifyEvent; |
| import org.eclipse.swt.events.ModifyListener; |
| 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.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.graphics.TextLayout; |
| import org.eclipse.swt.graphics.TextStyle; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Listener; |
| 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.ui.IWorkbenchPreferenceConstants; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.internal.WorkbenchPlugin; |
| import org.eclipse.ui.keys.IBindingService; |
| import org.eclipse.ui.themes.ColorUtil; |
| |
| |
| /** |
| * Provides the contents for the quick access shell created by |
| * {@link SearchField}. This was also used by {@link QuickAccessDialog} prior to |
| * e4. The SearchField is responsible for handling opening and closing the shell |
| * as well as setting {@link #setShowAllMatches(boolean)}. |
| */ |
| public abstract class QuickAccessContents { |
| /** |
| * When opened in a popup we were given the command used to open it. Now |
| * that we have a shell, we are just using a hard coded command id. |
| */ |
| private static final String QUICK_ACCESS_COMMAND_ID = "org.eclipse.ui.window.quickAccess"; //$NON-NLS-1$ |
| private static final int INITIAL_COUNT_PER_PROVIDER = 5; |
| private static final int MAX_COUNT_TOTAL = 20; |
| |
| protected Text filterText; |
| |
| private QuickAccessProvider[] providers; |
| |
| protected Table table; |
| protected Label infoLabel; |
| |
| private LocalResourceManager resourceManager = new LocalResourceManager( |
| JFaceResources.getResources()); |
| |
| protected String rememberedText; |
| |
| /** |
| * A color for dulled out items created by mixing the table foreground. Will |
| * be disposed when the {@link #resourceManager} is disposed. |
| */ |
| private Color grayColor; |
| private TextLayout textLayout; |
| private boolean showAllMatches = false; |
| protected boolean resized = false; |
| private TriggerSequence keySequence; |
| |
| public QuickAccessContents(QuickAccessProvider[] providers) { |
| this.providers = providers; |
| } |
| |
| /** |
| * Returns the number of items the table can fit in its current layout |
| */ |
| private int computeNumberOfItems() { |
| Rectangle rect = table.getClientArea (); |
| int itemHeight = table.getItemHeight (); |
| int headerHeight = table.getHeaderHeight (); |
| return (rect.height - headerHeight + itemHeight - 1) / (itemHeight + table.getGridLineWidth()); |
| } |
| |
| /** |
| * Refreshes the contents of the quick access shell |
| * |
| * @param filter |
| * The filter text to apply to results |
| * |
| */ |
| public void refresh(String filter) { |
| if (table != null) { |
| boolean filterTextEmpty = filter.length() == 0; |
| |
| // perfect match, to be selected in the table if not null |
| QuickAccessElement perfectMatch = getPerfectMatch(filter); |
| List<QuickAccessEntry>[] entries = computeMatchingEntries(filter, perfectMatch); |
| int selectionIndex = refreshTable(perfectMatch, entries); |
| |
| if (table.getItemCount() > 0) { |
| table.setSelection(selectionIndex); |
| hideHintText(); |
| } else if (filterTextEmpty) { |
| showHintText(QuickAccessMessages.QuickAccess_StartTypingToFindMatches, grayColor); |
| } else { |
| showHintText(QuickAccessMessages.QuickAccessContents_NoMatchingResults, grayColor); |
| } |
| |
| // update info as-you-type |
| updateInfoLabel(); |
| |
| updateFeedback(filterTextEmpty, showAllMatches); |
| } |
| } |
| |
| /** |
| * Allows the quick access content owner to mark a quick access element as |
| * being a perfect match, putting it at the start of the table. |
| * |
| * @param filter |
| * the filter text used to find a match |
| * @return an element to be put at the top of the table or <code>null</code> |
| */ |
| protected abstract QuickAccessElement getPerfectMatch(String filter); |
| |
| /** |
| * Notifies the quick access content owner that the contents of the table |
| * have been changed. |
| * |
| * @param filterTextEmpty |
| * whether the filter text used to calculate matches was empty |
| * @param showAllMatches |
| * whether the results were constrained by the size of the dialog |
| * |
| */ |
| protected abstract void updateFeedback(boolean filterTextEmpty, boolean showAllMatches); |
| |
| /** |
| * Sets whether to display all matches to the current filter or limit the |
| * results. Will refresh the table contents and update the info label. |
| * |
| * @param showAll |
| * whether to display all matches |
| */ |
| public void setShowAllMatches(boolean showAll) { |
| if (showAllMatches != showAll) { |
| showAllMatches = showAll; |
| updateInfoLabel(); |
| refresh(filterText.getText().toLowerCase()); |
| } |
| } |
| |
| private void updateInfoLabel() { |
| if (infoLabel != null) { |
| TriggerSequence sequence = getTriggerSequence(); |
| boolean forceHide = (getNumberOfFilteredResults() == 0) |
| || (showAllMatches && (table.getItemCount() <= computeNumberOfItems())); |
| if (sequence == null || forceHide) { |
| infoLabel.setText(""); //$NON-NLS-1$ |
| } else if (showAllMatches) { |
| infoLabel.setText( |
| NLS.bind(QuickAccessMessages.QuickAccessContents_PressKeyToLimitResults, sequence.format())); |
| } else { |
| infoLabel |
| .setText(NLS.bind(QuickAccessMessages.QuickAccess_PressKeyToShowAllMatches, sequence.format())); |
| } |
| infoLabel.getParent().layout(true); |
| } |
| } |
| |
| /** |
| * Returns the trigger sequence that can be used to open the quick access |
| * dialog as well as toggle the show all results feature. Can return |
| * <code>null</code> if no trigger sequence is known. |
| * |
| * @return the trigger sequence used to open the quick access or |
| * <code>null</code> |
| */ |
| public TriggerSequence getTriggerSequence() { |
| if (keySequence == null) { |
| IBindingService bindingService = |
| Adapters.adapt(PlatformUI.getWorkbench(), IBindingService.class); |
| keySequence = bindingService.getBestActiveBindingFor(QUICK_ACCESS_COMMAND_ID); |
| } |
| return keySequence; |
| } |
| |
| /** |
| * Return whether the shell is currently set to display all matches or limit |
| * the results. |
| * |
| * @return whether all matches will be displayed |
| */ |
| public boolean getShowAllMatches() { |
| return showAllMatches; |
| } |
| |
| private int refreshTable(QuickAccessElement perfectMatch, List<QuickAccessEntry>[] entries) { |
| if (table.getItemCount() > entries.length |
| && table.getItemCount() - entries.length > 20) { |
| table.removeAll(); |
| } |
| TableItem[] items = table.getItems(); |
| int selectionIndex = -1; |
| int index = 0; |
| for (int i = 0; i < providers.length; i++) { |
| if (entries[i] != null) { |
| boolean firstEntry = true; |
| for (Iterator<QuickAccessEntry> it = entries[i].iterator(); it.hasNext();) { |
| QuickAccessEntry entry = it.next(); |
| entry.firstInCategory = firstEntry; |
| firstEntry = false; |
| if (!it.hasNext()) { |
| entry.lastInCategory = true; |
| } |
| TableItem item; |
| 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; |
| } |
| 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); |
| } |
| if (selectionIndex == -1) { |
| selectionIndex = 0; |
| } |
| return selectionIndex; |
| } |
| |
| int numberOfFilteredResults; |
| |
| /** |
| * Compute how many items are effectively filtered at a specific point in |
| * time. So doing, the quick access content can perform operations that |
| * depends on this number, i.e. hide the info label. |
| * |
| * @return number number of elements filtered |
| */ |
| protected int getNumberOfFilteredResults() { |
| return numberOfFilteredResults; |
| } |
| |
| /** |
| * Returns a list per provider containing matching {@link QuickAccessEntry} |
| * that should be displayed in the table given a text filter and a perfect |
| * match entry that should be given priority. The number of items returned |
| * is affected by {@link #getShowAllMatches()} and the size of the table's |
| * composite. |
| * |
| * @param filter |
| * the string text filter to apply, possibly empty |
| * @param perfectMatch |
| * a quick access element that should be given priority or |
| * <code>null</code> |
| * @return the array of lists (one per provider) containg the quick access |
| * entries that should be added to the table, possibly empty |
| */ |
| private List<QuickAccessEntry>[] computeMatchingEntries(String filter, |
| QuickAccessElement perfectMatch) { |
| // collect matches in an array of lists |
| @SuppressWarnings("unchecked") |
| List<QuickAccessEntry>[] entries = new List[providers.length]; |
| |
| int maxCount = computeNumberOfItems(); |
| int[] indexPerProvider = new int[providers.length]; |
| int countPerProvider = INITIAL_COUNT_PER_PROVIDER; |
| int prevPick = 0; |
| int countTotal = 0; |
| boolean perfectMatchAdded = true; |
| if (perfectMatch != null) { |
| // reserve one entry for the perfect match |
| maxCount--; |
| perfectMatchAdded = false; |
| } |
| boolean done; |
| String category = null; |
| do { |
| // will be set to false if we find a provider with remaining |
| // elements |
| done = true; |
| // check for a category filter, like "Views: " |
| Matcher categoryMatcher = getCategoryPattern().matcher(filter); |
| if (categoryMatcher.matches()) { |
| category = categoryMatcher.group(1); |
| filter = category + " " + categoryMatcher.group(2); //$NON-NLS-1$ |
| } |
| for (int i = 0; i < providers.length |
| && (showAllMatches || countTotal < maxCount); i++) { |
| if (entries[i] == null) { |
| entries[i] = new ArrayList<>(); |
| indexPerProvider[i] = 0; |
| } |
| int count = 0; |
| QuickAccessProvider provider = providers[i]; |
| // when category is specified, skip providers except the |
| // specified one and the previous pick provider |
| boolean isPreviousPickProvider = (provider instanceof PreviousPicksProvider); |
| if (category != null && !category.equalsIgnoreCase(provider.getName()) && !isPreviousPickProvider) { |
| continue; |
| } |
| if (filter.length() > 0 || provider.isAlwaysPresent() || showAllMatches) { |
| QuickAccessElement[] sortedElements = provider.getElementsSorted(); |
| List<QuickAccessEntry> poorFilterMatches = new ArrayList<>(); |
| |
| // count number or previous picks |
| if ((provider instanceof PreviousPicksProvider)) { |
| prevPick = sortedElements.length; |
| } |
| |
| int j = indexPerProvider[i]; |
| while (j < sortedElements.length |
| && (showAllMatches || (count < countPerProvider && countTotal < maxCount))) { |
| QuickAccessElement element = sortedElements[j]; |
| QuickAccessEntry entry = null; |
| if (filter.length() == 0) { |
| if (i == 0 || showAllMatches) { |
| entry = new QuickAccessEntry(element, provider, new int[0][0], |
| new int[0][0], QuickAccessEntry.MATCH_PERFECT); |
| } else { |
| entry = null; |
| } |
| } else { |
| QuickAccessEntry possibleMatch = element.match(filter, provider); |
| // We only have limited space so only display |
| // excellent filter matches (Bug 398455) |
| if (possibleMatch != null) { |
| if (possibleMatch.getMatchQuality() <= QuickAccessEntry.MATCH_EXCELLENT) { |
| entry = possibleMatch; |
| } else { |
| poorFilterMatches.add(possibleMatch); |
| } |
| } |
| |
| } |
| if (entryEnabled(provider, entry)) { |
| entries[i].add(entry); |
| count++; |
| countTotal++; |
| if (i == 0 && entry.element == perfectMatch) { |
| perfectMatchAdded = true; |
| maxCount = MAX_COUNT_TOTAL; |
| } |
| } |
| |
| j++; |
| } |
| |
| indexPerProvider[i] = j; |
| // If there were low quality matches and there is still |
| // room, add them (Bug 398455) |
| for (Iterator<QuickAccessEntry> iterator = poorFilterMatches.iterator(); iterator |
| .hasNext() |
| && (showAllMatches || (count < countPerProvider && countTotal < maxCount));) { |
| QuickAccessEntry quickAccessEntry = iterator.next(); |
| entries[i].add(quickAccessEntry); |
| count++; |
| countTotal++; |
| if (i == 0 && quickAccessEntry.element == perfectMatch) { |
| perfectMatchAdded = true; |
| maxCount = MAX_COUNT_TOTAL; |
| } |
| } |
| if (j < sortedElements.length) { |
| done = false; |
| } |
| } |
| } |
| // from now on, add one element per provider |
| countPerProvider = 1; |
| } while ((showAllMatches || countTotal < maxCount) && !done); |
| if (!perfectMatchAdded) { |
| QuickAccessEntry entry = perfectMatch.match(filter, providers[0]); |
| if (entryEnabled(providers[0], entry)) { |
| if (entries[0] == null) { |
| entries[0] = new ArrayList<>(); |
| indexPerProvider[0] = 0; |
| } |
| entries[0].add(entry); |
| } |
| } |
| // number of items matching the filtered search |
| numberOfFilteredResults = countTotal - prevPick; |
| return entries; |
| } |
| |
| Pattern categoryPattern; |
| |
| /** |
| * Return a pattern like {@code "^(:?Views|Perspective):\\s?(.*)"}, with all |
| * the provider names separated by semicolon. |
| * |
| * @return Returns the patternProvider. |
| */ |
| protected Pattern getCategoryPattern() { |
| if (categoryPattern == null) { |
| // build regex like "^(:?Views|Perspective):\\s?(.*)" |
| StringBuilder sb = new StringBuilder(); |
| sb.append("^(:?"); //$NON-NLS-1$ |
| for (int i = 0; i < providers.length; i++) { |
| if (i != 0) |
| sb.append("|"); //$NON-NLS-1$ |
| sb.append(providers[i].getName()); |
| } |
| sb.append("):\\s?(.*)"); //$NON-NLS-1$ |
| String regex = sb.toString(); |
| categoryPattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); |
| } |
| return categoryPattern; |
| } |
| |
| /** |
| * @param provider |
| * @param entry |
| * @return <code>true</code> if the entry is enabled |
| */ |
| private boolean entryEnabled(QuickAccessProvider provider, QuickAccessEntry entry) { |
| if (entry == null) { |
| return false; |
| } |
| |
| // For a previous pick provider, check that the original provider does |
| // also provide the element |
| if (provider instanceof PreviousPicksProvider) { |
| QuickAccessElement element = entry.element; |
| final QuickAccessProvider originalProvider = element.getProvider(); |
| QuickAccessElement match = originalProvider.getElementForId(element.getId()); |
| return match != null; |
| } |
| |
| return true; |
| } |
| |
| private void doDispose() { |
| if (textLayout != null && !textLayout.isDisposed()) { |
| textLayout.dispose(); |
| } |
| if (resourceManager != null) { |
| // Disposing the resource manager will dispose the color |
| resourceManager.dispose(); |
| resourceManager = null; |
| } |
| } |
| |
| protected IDialogSettings getDialogSettings() { |
| final IDialogSettings workbenchDialogSettings = WorkbenchPlugin |
| .getDefault().getDialogSettings(); |
| IDialogSettings result = workbenchDialogSettings.getSection(getId()); |
| if (result == null) { |
| result = workbenchDialogSettings.addNewSection(getId()); |
| } |
| return result; |
| } |
| |
| protected String getId() { |
| return "org.eclipse.ui.internal.QuickAccess"; //$NON-NLS-1$ |
| } |
| |
| protected abstract void handleElementSelected(String text, Object selectedElement); |
| |
| private void handleSelection() { |
| QuickAccessElement selectedElement = null; |
| String text = filterText.getText().toLowerCase(); |
| if (table.getSelectionCount() == 1) { |
| QuickAccessEntry entry = (QuickAccessEntry) table |
| .getSelection()[0].getData(); |
| selectedElement = entry == null ? null : entry.element; |
| } |
| if (selectedElement != null) { |
| doClose(); |
| handleElementSelected(text, selectedElement); |
| } |
| } |
| |
| /** |
| * Should be called by the owner of the parent composite when the shell is |
| * being activated (made visible). This allows the show all keybinding to be |
| * updated. |
| */ |
| public void preOpen() { |
| // Make sure we always start filtering |
| setShowAllMatches(false); |
| // In case the key binding has changed, update the label |
| keySequence = null; |
| updateInfoLabel(); |
| } |
| |
| /** |
| * Informs the owner of the parent composite that the quick access dialog |
| * should be closed |
| */ |
| protected abstract void doClose(); |
| |
| /** |
| * Allows the dialog contents to interact correctly with the text box used to open it |
| * @param filterText text box to hook up |
| */ |
| public void hookFilterText(Text filterText) { |
| this.filterText = filterText; |
| filterText.addKeyListener(new KeyListener() { |
| @Override |
| public void keyPressed(KeyEvent e) { |
| switch (e.keyCode) { |
| case SWT.CR: |
| case SWT.KEYPAD_CR: |
| handleSelection(); |
| break; |
| case SWT.ARROW_DOWN: |
| int index = table.getSelectionIndex(); |
| if (index != -1 && table.getItemCount() > index + 1) { |
| table.setSelection(index + 1); |
| } |
| break; |
| case SWT.ARROW_UP: |
| index = table.getSelectionIndex(); |
| if (index != -1 && index >= 1) { |
| table.setSelection(index - 1); |
| } |
| break; |
| case SWT.ESC: |
| doClose(); |
| break; |
| } |
| } |
| |
| @Override |
| public void keyReleased(KeyEvent e) { |
| // do nothing |
| } |
| }); |
| filterText.addModifyListener(new ModifyListener() { |
| @Override |
| public void modifyText(ModifyEvent e) { |
| String text = ((Text) e.widget).getText().toLowerCase(); |
| refresh(text); |
| } |
| }); |
| } |
| |
| private Text hintText; |
| private boolean displayHintText; |
| |
| /** Create HintText as child of the given parent composite */ |
| Text createHintText(Composite composite, int defaultOrientation) { |
| hintText = new Text(composite, SWT.FILL); |
| hintText.setOrientation(defaultOrientation); |
| displayHintText = true; |
| return hintText; |
| } |
| |
| /** Hide the hint text */ |
| void hideHintText() { |
| if (displayHintText) { |
| setHintTextToDisplay(false); |
| } |
| } |
| |
| /** Show the hint text with the given color */ |
| void showHintText(String text, Color color) { |
| if (hintText == null) { |
| // toolbar hidden |
| return; |
| } |
| hintText.setText(text); |
| if (color != null) { |
| hintText.setForeground(color); |
| } |
| if (!displayHintText) { |
| setHintTextToDisplay(true); |
| } |
| } |
| |
| /** |
| * Sets hint text to be displayed and requests the layout |
| * |
| * @param toDisplay |
| */ |
| private void setHintTextToDisplay(boolean toDisplay) { |
| GridData data = (GridData) hintText.getLayoutData(); |
| data.exclude = !toDisplay; |
| hintText.setVisible(toDisplay); |
| hintText.requestLayout(); |
| this.displayHintText = toDisplay; |
| } |
| |
| /** |
| * Creates the table providing the contents for the quick access dialog |
| * |
| * @param composite parent composite with {@link GridLayout} |
| * @param defaultOrientation the window orientation to use for the table {@link SWT#RIGHT_TO_LEFT} or {@link SWT#LEFT_TO_RIGHT} |
| * @return the created table |
| */ |
| public Table createTable(Composite composite, int defaultOrientation) { |
| composite.addDisposeListener(new DisposeListener() { |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| doDispose(); |
| } |
| }); |
| 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(defaultOrientation); |
| Font boldFont = resourceManager.createFont(FontDescriptor.createFrom( |
| JFaceResources.getDialogFont()).setStyle(SWT.BOLD)); |
| textLayout.setFont(table.getFont()); |
| textLayout.setText(QuickAccessMessages.QuickAccess_AvailableCategories); |
| int maxProviderWidth = (textLayout.getBounds().width); |
| textLayout.setFont(boldFont); |
| for (int i = 0; i < providers.length; i++) { |
| QuickAccessProvider provider = providers[i]; |
| textLayout.setText(provider.getName()); |
| int width = (textLayout.getBounds().width); |
| if (width > maxProviderWidth) { |
| maxProviderWidth = width; |
| } |
| } |
| tableColumnLayout.setColumnData(new TableColumn(table, SWT.NONE), new ColumnWeightData(0, maxProviderWidth)); |
| tableColumnLayout.setColumnData(new TableColumn(table, SWT.NONE), new ColumnWeightData(100, 100)); |
| 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 (table != null && !table.isDisposed() && filterText !=null && !filterText.isDisposed()) { |
| refresh(filterText.getText().toLowerCase()); |
| } |
| resized = false; |
| } |
| }); |
| } |
| } |
| } |
| }); |
| |
| table.addKeyListener(new KeyListener() { |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (e.keyCode == SWT.ARROW_UP && table.getSelectionIndex() == 0) { |
| filterText.setFocus(); |
| } else if (e.character == SWT.ESC) { |
| doClose(); |
| } |
| } |
| |
| @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 }); |
| } |
| } else if (o == null) { |
| lastItem = null; |
| } |
| } |
| } |
| }); |
| |
| table.addSelectionListener(new SelectionListener() { |
| @Override |
| public void widgetSelected(SelectionEvent e) { |
| // do nothing |
| } |
| |
| @Override |
| public void widgetDefaultSelected(SelectionEvent e) { |
| handleSelection(); |
| } |
| }); |
| |
| final TextStyle boldStyle; |
| if (PlatformUI.getPreferenceStore().getBoolean( |
| IWorkbenchPreferenceConstants.USE_COLORED_LABELS)) { |
| boldStyle = new TextStyle(boldFont, null, null); |
| grayColor = resourceManager.createColor(ColorUtil.blend(table.getBackground().getRGB(), |
| table.getForeground().getRGB())); |
| } else { |
| boldStyle = null; |
| } |
| Listener listener = new Listener() { |
| @Override |
| public void handleEvent(Event event) { |
| QuickAccessEntry entry = (QuickAccessEntry) 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, grayColor); |
| break; |
| case SWT.EraseItem: |
| entry.erase(event); |
| break; |
| } |
| } |
| } |
| }; |
| table.addListener(SWT.MeasureItem, listener); |
| table.addListener(SWT.EraseItem, listener); |
| table.addListener(SWT.PaintItem, listener); |
| |
| return table; |
| } |
| |
| /** |
| * Creates a label which will display the key binding to expand |
| * the search results. |
| * |
| * @param parent parent composite with {@link GridLayout} |
| * @return the created label |
| */ |
| public Label createInfoLabel(Composite parent) { |
| infoLabel = new Label(parent, SWT.NONE); |
| infoLabel.setFont(parent.getFont()); |
| infoLabel.setForeground(grayColor); |
| infoLabel.setBackground(table.getBackground()); |
| GridData gd = new GridData(GridData.FILL_HORIZONTAL); |
| gd.horizontalAlignment = SWT.RIGHT; |
| gd.grabExcessHorizontalSpace = false; |
| infoLabel.setLayoutData(gd); |
| updateInfoLabel(); |
| return infoLabel; |
| } |
| |
| public void resetProviders() { |
| for (QuickAccessProvider provider : providers) { |
| provider.reset(); |
| } |
| } |
| |
| } |