| /******************************************************************************* |
| * Copyright (c) 2010, 2011 Tasktop Technologies and others. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v. 2.0 which is available at |
| * https://www.eclipse.org/legal/epl-2.0 |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Tasktop Technologies - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.commons.workbench.search; |
| |
| import java.util.Collection; |
| import java.util.Collections; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.jface.dialogs.PopupDialog; |
| import org.eclipse.jface.layout.GridDataFactory; |
| import org.eclipse.jface.viewers.IOpenListener; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ITreeContentProvider; |
| import org.eclipse.jface.viewers.OpenEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.TableViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jface.viewers.ViewerSorter; |
| import org.eclipse.mylyn.commons.workbench.TableTreePatternFilter; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| 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.ModifyEvent; |
| import org.eclipse.swt.events.ModifyListener; |
| import org.eclipse.swt.events.MouseAdapter; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.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.TableColumn; |
| import org.eclipse.swt.widgets.Text; |
| import org.eclipse.ui.PlatformUI; |
| |
| /** |
| * @author Shawn Minto |
| * @since 3.7 |
| */ |
| public class SearchHistoryPopupDialog extends PopupDialog { |
| |
| private static final int MAX_HISTORY_NO_FILTER = 3; |
| |
| private static final int MAX_HISTORY_FILTER = 10; |
| |
| private final int side; |
| |
| private Rectangle trimBounds; |
| |
| private TextSearchControl textSearchControl; |
| |
| private Text textControl; |
| |
| private TableViewer historyTable; |
| |
| private TableTreePatternFilter patternFilter; |
| |
| private Composite additionalControlsComposite; |
| |
| public SearchHistoryPopupDialog(Shell parent, int side) { |
| super(parent, PopupDialog.HOVER_SHELLSTYLE, false, false, false, false, false, null, null); |
| this.side = side; |
| updateBounds(); |
| } |
| |
| @Override |
| protected void initializeBounds() { |
| Rectangle monitorBounds = PlatformUI.getWorkbench() |
| .getActiveWorkbenchWindow() |
| .getShell() |
| .getMonitor() |
| .getClientArea(); |
| Rectangle bounds = getShell().getBounds(); |
| int x = 0; |
| int y = 0; |
| |
| switch (side) { |
| case SWT.TOP: |
| x = trimBounds.x; |
| y = trimBounds.y + trimBounds.height; |
| if (x + bounds.width > monitorBounds.x + monitorBounds.width) { |
| x = (trimBounds.x + trimBounds.width) - bounds.width; |
| } |
| break; |
| case SWT.BOTTOM: |
| x = trimBounds.x; |
| y = trimBounds.y - bounds.height; |
| if (x + bounds.width > monitorBounds.x + monitorBounds.width) { |
| x = (trimBounds.x + trimBounds.width) - bounds.width; |
| } |
| break; |
| case SWT.RIGHT: |
| x = (trimBounds.x + trimBounds.width) - bounds.width; |
| y = trimBounds.y + trimBounds.height; |
| break; |
| case SWT.LEFT: |
| x = trimBounds.x; |
| y = trimBounds.y + trimBounds.height; |
| break; |
| } |
| getShell().setBounds(x, y, Math.max(trimBounds.width, bounds.width), bounds.height); |
| } |
| |
| @Override |
| protected Control createContents(Composite parent) { |
| getShell().setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); |
| Control createDialogArea = createDialogArea(parent); |
| return createDialogArea; |
| } |
| |
| @Override |
| protected Control createDialogArea(Composite parent) { |
| Composite composite = new Composite(parent, SWT.NONE); |
| GridLayout gl = new GridLayout(); |
| gl.marginHeight = 0; |
| gl.marginWidth = 0; |
| gl.horizontalSpacing = 0; |
| gl.verticalSpacing = 0; |
| gl.numColumns = 1; |
| composite.setLayout(gl); |
| |
| GridDataFactory.fillDefaults().grab(true, false).applyTo(composite); |
| |
| createTable(composite); |
| |
| additionalControlsComposite = new Composite(composite, SWT.NONE); |
| gl = new GridLayout(); |
| gl.marginHeight = 0; |
| gl.marginWidth = 0; |
| gl.horizontalSpacing = 0; |
| gl.verticalSpacing = 0; |
| gl.numColumns = 1; |
| additionalControlsComposite.setLayout(gl); |
| GridDataFactory.fillDefaults().grab(true, false).applyTo(additionalControlsComposite); |
| |
| createAdditionalSearchRegion(additionalControlsComposite); |
| if (additionalControlsComposite.getChildren().length == 0) { |
| additionalControlsComposite.dispose(); |
| additionalControlsComposite = null; |
| } |
| |
| parent.pack(); |
| |
| return parent; |
| } |
| |
| private void createTable(Composite composite) { |
| historyTable = new TableViewer(composite, SWT.SINGLE | SWT.FULL_SELECTION | SWT.NO_SCROLL); |
| TableColumn bindingNameColumn = new TableColumn(historyTable.getTable(), SWT.LEFT); |
| // XXX fix this |
| bindingNameColumn.setWidth(2000); |
| historyTable.setUseHashlookup(true); |
| |
| historyTable.setContentProvider(new ITreeContentProvider() { |
| |
| public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { |
| |
| } |
| |
| public void dispose() { |
| |
| } |
| |
| public Object[] getElements(Object inputElement) { |
| if (inputElement instanceof Collection<?>) { |
| Object[] elements = ((Collection<?>) inputElement).toArray(); |
| return reverseArray(elements); |
| } |
| return new Object[0]; |
| } |
| |
| public boolean hasChildren(Object element) { |
| return false; |
| } |
| |
| public Object getParent(Object element) { |
| return null; |
| } |
| |
| public Object[] getChildren(Object parentElement) { |
| return null; |
| } |
| |
| private Object[] reverseArray(Object[] originalArray) { |
| int i = 0; |
| int j = originalArray.length - 1; |
| |
| while (i < j) { |
| Object firstElement = originalArray[i]; |
| originalArray[i] = originalArray[j]; |
| originalArray[j] = firstElement; |
| |
| i++; |
| j--; |
| } |
| return originalArray; |
| } |
| |
| }); |
| |
| if (textSearchControl != null) { |
| historyTable.setInput(textSearchControl.getSearchHistory()); |
| } else { |
| historyTable.setInput(Collections.emptyList()); |
| } |
| historyTable.addOpenListener(new IOpenListener() { |
| |
| public void open(OpenEvent event) { |
| String text = getTextFromSelection(event.getSelection()); |
| if (text != null) { |
| textSearchControl.getTextControl().setText(text); |
| textControl.setSelection(text.length()); |
| textSearchControl.addToSearchHistory(text); |
| } |
| close(); |
| } |
| |
| }); |
| |
| // XXX CHANGE TO SELECTION LISTENER?? |
| historyTable.getTable().addMouseListener(new MouseAdapter() { |
| |
| @Override |
| public void mouseDown(MouseEvent e) { |
| String text = getTextFromSelection(historyTable.getSelection()); |
| if (text != null) { |
| textSearchControl.getTextControl().setText(text); |
| textSearchControl.addToSearchHistory(text); |
| } |
| textSearchControl.getTextControl().setFocus(); |
| close(); |
| } |
| }); |
| patternFilter = new TableTreePatternFilter(); |
| patternFilter.setIncludeLeadingWildcard(true); |
| patternFilter.setPattern(textSearchControl.getTextControl().getText()); |
| historyTable.addFilter(patternFilter); |
| if (textControl.getText().length() > 0) { |
| historyTable.setSorter(new ViewerSorter()); |
| historyTable.setItemCount(Math.min(historyTable.getTable().getItemCount(), MAX_HISTORY_FILTER)); |
| } else { |
| historyTable.setSorter(null); |
| historyTable.setItemCount(Math.min(historyTable.getTable().getItemCount(), MAX_HISTORY_NO_FILTER)); |
| } |
| GridDataFactory.fillDefaults() |
| .grab(true, false) |
| .hint(trimBounds.width, SWT.DEFAULT) |
| .applyTo(historyTable.getTable()); |
| setHistoryTableVisible(historyTable.getTable().getItemCount() > 0); |
| } |
| |
| private void setHistoryTableVisible(boolean isVisible) { |
| GridData layoutData = (GridData) historyTable.getTable().getLayoutData(); |
| historyTable.getTable().setVisible(isVisible); |
| boolean wasVisible = !layoutData.exclude; |
| layoutData.exclude = !isVisible; |
| if (wasVisible != isVisible || isVisible) { |
| getShell().pack(); |
| } |
| initializeBounds(); |
| } |
| |
| private boolean shouldOpen() { |
| return (historyTable != null && historyTable.getTable() != null && historyTable.getTable().isVisible()) |
| || additionalControlsComposite != null; |
| } |
| |
| private String getTextFromSelection(ISelection selection) { |
| if (selection instanceof StructuredSelection) { |
| Object firstElement = ((StructuredSelection) selection).getFirstElement(); |
| if (firstElement instanceof String) { |
| return (String) firstElement; |
| } |
| |
| } |
| return null; |
| } |
| |
| private boolean isOpen = false; |
| |
| @Override |
| public int open() { |
| if (!isOpen) { |
| isOpen = true; |
| int rc = super.open(); |
| if (!shouldOpen()) { |
| close(); |
| } |
| return rc; |
| } |
| return 0; |
| } |
| |
| public void asyncClose() { |
| Display.getDefault().asyncExec(new Runnable() { |
| public void run() { |
| if ((textControl != null && !textControl.isDisposed() && textControl.isFocusControl()) |
| || !hasFocus(getShell())) { |
| close(); |
| } |
| } |
| }); |
| } |
| |
| @Override |
| public boolean close() { |
| if (patternFilter != null) { |
| patternFilter.setPattern(null); |
| } |
| boolean b = super.close(); |
| isOpen = !b; |
| return b; |
| } |
| |
| private boolean updateBounds() { |
| Rectangle bounds; |
| if (textSearchControl == null || textSearchControl.getTextControl() == null |
| || textSearchControl.getTextControl().isDisposed()) { |
| bounds = new Rectangle(0, 0, 0, 0); |
| } else { |
| bounds = textSearchControl.getBounds(); |
| Point absPosition = textSearchControl.toDisplay(textSearchControl.getLocation()); |
| bounds.x = absPosition.x - bounds.x; |
| bounds.y = absPosition.y - bounds.y; |
| } |
| if (trimBounds == null || !trimBounds.equals(bounds)) { |
| this.trimBounds = bounds; |
| return true; |
| } |
| return false; |
| } |
| |
| public void attach(final TextSearchControl textSearchControl) { |
| Assert.isNotNull(textSearchControl); |
| Assert.isNotNull(textSearchControl.getTextControl()); |
| this.textSearchControl = textSearchControl; |
| this.textControl = textSearchControl.getTextControl(); |
| textSearchControl.addModifyListener(new ModifyListener() { |
| |
| public void modifyText(ModifyEvent e) { |
| if (!hasFocus(textControl)) { |
| // user shouldn't be modifying the text if it doesnt have focus |
| return; |
| } |
| if (!isOpen && textControl != null && !textControl.isDisposed() && textControl.getText().length() > 0) { |
| updateBounds(); |
| open(); |
| } |
| if (isOpen && historyTable != null && !historyTable.getTable().isDisposed() && patternFilter != null) { |
| patternFilter.setPattern(textControl.getText()); |
| historyTable.setSelection(null); |
| historyTable.refresh(); |
| if (textControl.getText().length() > 0) { |
| historyTable.setSorter(new ViewerSorter()); |
| historyTable.setItemCount(Math.min(historyTable.getTable().getItemCount(), MAX_HISTORY_FILTER)); |
| } else { |
| historyTable.setSorter(null); |
| historyTable.setItemCount(Math.min(historyTable.getTable().getItemCount(), |
| MAX_HISTORY_NO_FILTER)); |
| } |
| setHistoryTableVisible(historyTable.getTable().getItemCount() > 0); |
| if (!shouldOpen()) { |
| asyncClose(); |
| } |
| } |
| } |
| }); |
| |
| textControl.addDisposeListener(new DisposeListener() { |
| |
| public void widgetDisposed(DisposeEvent e) { |
| close(); |
| } |
| }); |
| |
| textControl.addKeyListener(new KeyAdapter() { |
| |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (e.character == SWT.ESC && isOpen) { |
| close(); |
| e.doit = false; |
| } else if (e.stateMask == 0 && e.keyCode == SWT.ARROW_DOWN && isOpen && historyTable.getTable() != null |
| && !historyTable.getTable().isDisposed() && historyTable.getTable().getItemCount() > 0) { |
| historyTable.getTable().select(0); |
| historyTable.getTable().setFocus(); |
| e.doit = false; |
| } else if ((e.stateMask & SWT.MOD1) != 0 && e.keyCode == SWT.ARROW_DOWN && !isOpen) { |
| updateBounds(); |
| open(); |
| e.doit = false; |
| } |
| } |
| }); |
| |
| Listener moveResizeListener = new Listener() { |
| |
| public void handleEvent(Event event) { |
| if (isOpen) { |
| if (updateBounds()) { |
| initializeBounds(); |
| } |
| } |
| } |
| }; |
| textSearchControl.addListener(SWT.Move, moveResizeListener); |
| textSearchControl.addListener(SWT.Resize, moveResizeListener); |
| |
| textControl.addFocusListener(new FocusListener() { |
| |
| public void focusLost(FocusEvent e) { |
| asyncClose(); |
| } |
| |
| public void focusGained(FocusEvent e) { |
| } |
| }); |
| } |
| |
| private boolean hasFocus(Control control) { |
| if (control != null && !control.isDisposed() && control.isFocusControl()) { |
| return true; |
| } |
| if (control instanceof Composite) { |
| for (Control child : ((Composite) control).getChildren()) { |
| if (hasFocus(child)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| protected void createAdditionalSearchRegion(Composite composite) { |
| } |
| |
| } |