| /******************************************************************************* |
| * 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 |
| * René Brandstetter - Bug 433778 |
| * Patrik Suzzi <psuzzi@gmail.com> - Bug 491410 |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.quickaccess; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.Map; |
| import org.eclipse.core.commands.Command; |
| import org.eclipse.core.runtime.Adapters; |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.e4.core.commands.ExpressionContext; |
| import org.eclipse.e4.ui.model.application.MApplication; |
| import org.eclipse.e4.ui.model.application.ui.basic.MWindow; |
| 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.dialogs.PopupDialog; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.util.Util; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.events.KeyAdapter; |
| import org.eclipse.swt.events.KeyEvent; |
| 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.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Table; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.ui.IWorkbenchWindow; |
| import org.eclipse.ui.internal.WorkbenchPlugin; |
| import org.eclipse.ui.internal.WorkbenchWindow; |
| import org.eclipse.ui.internal.progress.ProgressManagerUtil; |
| import org.eclipse.ui.keys.IBindingService; |
| |
| /** |
| * This is the quick access popup dialog used in 3.x. The new quick access is |
| * done through a shell in {@link SearchField}. |
| * |
| * @since 3.3 |
| * |
| */ |
| public class QuickAccessDialog extends PopupDialog { |
| private TriggerSequence[] invokingCommandKeySequences; |
| private Command invokingCommand; |
| private QuickAccessContents contents; |
| private KeyAdapter keyAdapter; |
| private Text filterText; |
| private IWorkbenchWindow window; |
| private LinkedList previousPicksList = new LinkedList(); |
| 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 Map textMap = new HashMap(); |
| protected Map elementMap = new HashMap(); |
| protected Map providerMap; |
| |
| public QuickAccessDialog(final IWorkbenchWindow window, final Command invokingCommand) { |
| super(ProgressManagerUtil.getDefaultParent(), SWT.RESIZE, true, true, // persist |
| // size |
| false, // but not location |
| true, true, null, QuickAccessMessages.QuickAccess_StartTypingToFindMatches); |
| this.window = window; |
| |
| WorkbenchWindow workbenchWindow = (WorkbenchWindow) window; |
| final MWindow model = workbenchWindow.getModel(); |
| |
| BusyIndicator.showWhile(window.getShell() == null ? null : window.getShell().getDisplay(), |
| () -> { |
| final CommandProvider commandProvider = new CommandProvider(); |
| commandProvider.setSnapshot(new ExpressionContext(model.getContext() |
| .getActiveLeaf())); |
| QuickAccessProvider[] providers = new QuickAccessProvider[] { |
| new PreviousPicksProvider(previousPicksList), |
| new EditorProvider(), |
| new ViewProvider(model.getContext().get(MApplication.class), model), |
| new PerspectiveProvider(), commandProvider, new ActionProvider(), |
| new WizardProvider(), new PreferenceProvider(), |
| new PropertiesProvider() }; |
| providerMap = new HashMap(); |
| for (QuickAccessProvider provider : providers) { |
| providerMap.put(provider.getId(), provider); |
| } |
| QuickAccessDialog.this.contents = new QuickAccessContents(providers) { |
| @Override |
| protected void updateFeedback(boolean filterTextEmpty, |
| boolean showAllMatches) { |
| if (filterTextEmpty) { |
| setInfoText(QuickAccessMessages.QuickAccess_StartTypingToFindMatches); |
| } else { |
| TriggerSequence[] sequences = getInvokingCommandKeySequences(); |
| if (showAllMatches || sequences == null |
| || sequences.length == 0) { |
| setInfoText(""); //$NON-NLS-1$ |
| } else { |
| setInfoText(NLS |
| .bind(QuickAccessMessages.QuickAccess_PressKeyToShowAllMatches, |
| sequences[0].format())); |
| } |
| } |
| } |
| |
| @Override |
| protected void doClose() { |
| QuickAccessDialog.this.close(); |
| } |
| |
| /** |
| * @param element |
| */ |
| void addPreviousPick(String text, Object element) { |
| // 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 removedList = (ArrayList) 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 textList = (ArrayList) textMap.get(element); |
| if (textList == null) { |
| textList = new ArrayList(); |
| 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 = (ArrayList) textMap.get(replacedElement); |
| if (textList != null) { |
| textList.remove(text); |
| if (textList.isEmpty()) { |
| textMap.remove(replacedElement); |
| previousPicksList.remove(replacedElement); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected QuickAccessElement getPerfectMatch(String filter) { |
| QuickAccessElement perfectMatch = (QuickAccessElement) elementMap |
| .get(filter); |
| return perfectMatch; |
| } |
| |
| @Override |
| protected void handleElementSelected(String text, Object selectedElement) { |
| if (selectedElement instanceof QuickAccessElement) { |
| addPreviousPick(text, selectedElement); |
| storeDialog(getDialogSettings()); |
| |
| /* |
| * Execute after the dialog has been fully |
| * closed/disposed and the correct |
| * EclipseContext is in place. |
| */ |
| final QuickAccessElement element = (QuickAccessElement) selectedElement; |
| window.getShell().getDisplay().asyncExec(() -> element.execute()); |
| } |
| } |
| }; |
| restoreDialog(); |
| QuickAccessDialog.this.invokingCommand = invokingCommand; |
| if (QuickAccessDialog.this.invokingCommand != null |
| && !QuickAccessDialog.this.invokingCommand.isDefined()) { |
| QuickAccessDialog.this.invokingCommand = null; |
| } else { |
| // Pre-fetch key sequence - do not change because |
| // scope will |
| // change later. |
| getInvokingCommandKeySequences(); |
| } |
| // create early |
| create(); |
| }); |
| QuickAccessDialog.this.contents.refresh(""); //$NON-NLS-1$ |
| } |
| |
| @Override |
| protected Control createTitleControl(Composite parent) { |
| filterText = new Text(parent, SWT.NONE); |
| |
| GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false) |
| .applyTo(filterText); |
| |
| contents.hookFilterText(filterText); |
| filterText.addKeyListener(getKeyAdapter()); |
| |
| return filterText; |
| } |
| |
| @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); |
| |
| Table table = contents.createTable(composite, getDefaultOrientation()); |
| table.addKeyListener(getKeyAdapter()); |
| |
| return composite; |
| } |
| |
| final protected TriggerSequence[] getInvokingCommandKeySequences() { |
| if (invokingCommandKeySequences == null) { |
| if (invokingCommand != null) { |
| IBindingService bindingService = |
| Adapters.adapt(window.getWorkbench(), IBindingService.class); |
| invokingCommandKeySequences = bindingService.getActiveBindingsFor(invokingCommand.getId()); |
| } |
| } |
| return invokingCommandKeySequences; |
| } |
| |
| 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 (TriggerSequence sequence : sequences) { |
| if (sequence.equals(keySequence)) { |
| e.doit = false; |
| contents.setShowAllMatches(!contents.getShowAllMatches()); |
| return; |
| } |
| } |
| } |
| }; |
| } |
| return keyAdapter; |
| } |
| |
| @Override |
| protected Control getFocusControl() { |
| return filterText; |
| } |
| |
| @Override |
| public boolean close() { |
| storeDialog(getDialogSettings()); |
| return super.close(); |
| } |
| |
| @Override |
| protected Point getDefaultSize() { |
| GC gc = new GC(getContents()); |
| FontMetrics fontMetrics = gc.getFontMetrics(); |
| gc.dispose(); |
| int x = Dialog.convertHorizontalDLUsToPixels(fontMetrics, 300); |
| if (x < 350) { |
| x = 350; |
| } |
| int y = Dialog.convertVerticalDLUsToPixels(fontMetrics, 270); |
| if (y < 420) { |
| y = 420; |
| } |
| return new Point(x, y); |
| } |
| |
| @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 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$ |
| } |
| |
| private void storeDialog(IDialogSettings dialogSettings) { |
| String[] orderedElements = new String[previousPicksList.size()]; |
| String[] orderedProviders = new String[previousPicksList.size()]; |
| String[] textEntries = new String[previousPicksList.size()]; |
| ArrayList arrayList = new ArrayList(); |
| for (int i = 0; i < orderedElements.length; i++) { |
| QuickAccessElement quickAccessElement = (QuickAccessElement) previousPicksList.get(i); |
| ArrayList elementText = (ArrayList) textMap.get(quickAccessElement); |
| Assert.isNotNull(elementText); |
| orderedElements[i] = quickAccessElement.getId(); |
| orderedProviders[i] = quickAccessElement.getProvider().getId(); |
| arrayList.addAll(elementText); |
| textEntries[i] = elementText.size() + ""; //$NON-NLS-1$ |
| } |
| String[] textArray = (String[]) 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); |
| } |
| |
| private void restoreDialog() { |
| 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(); |
| textMap = new HashMap(); |
| previousPicksList = new LinkedList(); |
| if (orderedElements != null && orderedProviders != null && textEntries != null |
| && textArray != null) { |
| int arrayIndex = 0; |
| for (int i = 0; i < orderedElements.length; i++) { |
| QuickAccessProvider quickAccessProvider = (QuickAccessProvider) providerMap |
| .get(orderedProviders[i]); |
| int numTexts = Integer.parseInt(textEntries[i]); |
| if (quickAccessProvider != null) { |
| QuickAccessElement quickAccessElement = quickAccessProvider |
| .getElementForId(orderedElements[i]); |
| if (quickAccessElement != null) { |
| ArrayList arrayList = new ArrayList(); |
| for (int j = arrayIndex; j < arrayIndex + numTexts; j++) { |
| String text = textArray[j]; |
| // text length can be zero for old workspaces, |
| // see bug 190006 |
| if (text.length() > 0) { |
| arrayList.add(text); |
| elementMap.put(text, quickAccessElement); |
| } |
| } |
| textMap.put(quickAccessElement, arrayList); |
| previousPicksList.add(quickAccessElement); |
| } |
| } |
| arrayIndex += numTexts; |
| } |
| } |
| } |
| } |
| |
| } |