blob: 7d6aa7e5d89909df576fe7898e4c84a6ab838857 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Willian Mitsuda <wmitsuda@gmail.com>
* - Fix for bug 196553 - [Dialogs] Support IColorProvider/IFontProvider in FilteredItemsSelectionDialog
* Peter Friese <peter.friese@gentleware.com>
* - Fix for bug 208602 - [Dialogs] Open Type dialog needs accessible labels
* Simon Muschel <smuschel@gmx.de> - bug 258493
* Kris De Volder Copied and modified from org.eclipse.ui.dialogs.FilteredResourcesSelectionDialog
* to create QuickSearchDialog
*******************************************************************************/
package org.eclipse.text.quicksearch.internal.ui;
import static org.eclipse.jface.resource.JFaceResources.TEXT_FONT;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.LegacyActionTools;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.viewers.ILazyContentProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StyledCellLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.StyledString.Styler;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.osgi.util.NLS;
import org.eclipse.search.internal.ui.text.EditorOpener;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.ACC;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
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.Button;
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.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.text.quicksearch.internal.core.LineItem;
import org.eclipse.text.quicksearch.internal.core.QuickTextQuery;
import org.eclipse.text.quicksearch.internal.core.QuickTextQuery.TextRange;
import org.eclipse.text.quicksearch.internal.core.QuickTextSearchRequestor;
import org.eclipse.text.quicksearch.internal.core.QuickTextSearcher;
import org.eclipse.text.quicksearch.internal.core.pathmatch.ResourceMatchers;
import org.eclipse.text.quicksearch.internal.util.DocumentFetcher;
import org.eclipse.ui.ActiveShellExpression;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.SelectionStatusDialog;
import org.eclipse.ui.handlers.IHandlerActivation;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
import org.eclipse.ui.internal.WorkbenchImages;
import org.eclipse.ui.internal.WorkbenchMessages;
import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
import org.eclipse.ui.progress.UIJob;
import org.osgi.framework.FrameworkUtil;
/**
* Shows a list of items to the user with a text entry field for a string
* pattern used to filter the list of items.
*
* @since 3.3
*/
@SuppressWarnings({ "rawtypes", "restriction", "unchecked" })
public class QuickSearchDialog extends SelectionStatusDialog {
private static final int OPEN_BUTTON_ID = IDialogConstants.OK_ID;
private static final int REFRESH_BUTTON_ID = IDialogConstants.RETRY_ID;
public static final Styler HIGHLIGHT_STYLE = org.eclipse.search.internal.ui.text.DecoratingFileSearchLabelProvider.HIGHLIGHT_STYLE;
private UIJob refreshJob = new UIJob(Messages.QuickSearchDialog_RefreshJob) {
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
refreshWidgets();
return Status.OK_STATUS;
}
};
protected void openSelection() {
try {
LineItem item = (LineItem) this.getFirstResult();
if (item!=null) {
QuickTextQuery q = this.getQuery();
TextRange range = q.findFirst(item.getText());
EditorOpener opener = new EditorOpener();
IWorkbenchPage page = window.getActivePage();
if (page!=null) {
opener.openAndSelect(page, item.getFile(), range.getOffset()+item.getOffset(),
range.getLength(), true);
}
}
} catch (PartInitException e) {
QuickSearchActivator.log(e);
}
}
/**
* Job that shows a simple busy indicator while a search is active.
* The job must be scheduled when a search starts/resumes.
*/
private UIJob progressJob = new UIJob(Messages.QuickSearchDialog_RefreshJob) {
int animate = 0; // number of dots to display.
protected String dots(int animate) {
char[] chars = new char[animate];
for (int i = 0; i < chars.length; i++) {
chars[i] = '.';
}
return new String(chars);
}
protected String currentFileInfo(IFile currentFile, int animate) {
if (currentFile!=null) {
String path = currentFile.getFullPath().toString();
if (path.length()<=30) {
return path;
}
return "..."+path.substring(path.length()-30); //$NON-NLS-1$
}
return dots(animate);
}
@Override
public IStatus runInUIThread(IProgressMonitor mon) {
if (!mon.isCanceled() && progressLabel!=null && !progressLabel.isDisposed()) {
if (searcher==null || !searcher.isActive()) {
progressLabel.setText(EMPTY_STRING);
} else {
progressLabel.setText(NLS.bind(Messages.QuickSearchDialog_searching, currentFileInfo(searcher.getCurrentFile(), animate)));
animate = (animate+1)%4;
this.schedule(333);
}
}
return Status.OK_STATUS;
}
};
public final StyledCellLabelProvider LINE_NUMBER_LABEL_PROVIDER = new StyledCellLabelProvider() {
@Override
public void update(ViewerCell cell) {
LineItem item = (LineItem) cell.getElement();
if (item!=null) {
cell.setText(Integer.toString(item.getLineNumber()));
} else {
cell.setText("?"); //$NON-NLS-1$
}
cell.setImage(getBlankImage());
};
};
private static final Color GREY = Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY);
private final StyledCellLabelProvider LINE_TEXT_LABEL_PROVIDER = new StyledCellLabelProvider() {
@Override
public void update(ViewerCell cell) {
LineItem item = (LineItem) cell.getElement();
if (item!=null) {
StyledString text = highlightMatches(item.getText());
cell.setText(text.getString());
cell.setStyleRanges(text.getStyleRanges());
} else {
cell.setText(EMPTY_STRING);
cell.setStyleRanges(null);
}
cell.setImage(getBlankImage());
super.update(cell);
}
};
private Image blankImage;
private Image getBlankImage() {
if (blankImage==null) {
blankImage = new Image(Display.getDefault(), 1, 1);
// GC gc = new GC(blankImage);
// gc.fillRectangle(0, 0, 16, 16);
// gc.dispose();
}
return blankImage;
}
private final StyledCellLabelProvider LINE_FILE_LABEL_PROVIDER = new StyledCellLabelProvider() {
@Override
public void update(ViewerCell cell) {
LineItem item = (LineItem) cell.getElement();
if (item!=null) {
IPath path = item.getFile().getFullPath();
String name = path.lastSegment();
String dir = path.removeLastSegments(1).toString();
if (dir.startsWith("/")) { //$NON-NLS-1$
dir = dir.substring(1);
}
cell.setText(name + " - " + dir); //$NON-NLS-1$
StyleRange[] styleRanges = new StyleRange[] {
new StyleRange(name.length(), dir.length()+3, GREY, null)
};
cell.setStyleRanges(styleRanges);
} else {
cell.setText(EMPTY_STRING);
cell.setStyleRanges(null);
}
cell.setImage(getBlankImage());
super.update(cell);
}
// public String getToolTipText(Object element) {
// LineItem item = (LineItem) element;
// if (item!=null) {
// return ""+item.getFile().getFullPath();
// }
// return "";
// };
// public String getText(Object _item) {
// if (_item!=null) {
// LineItem item = (LineItem) _item;
// return item.getFile().getName().toString();
// }
// return "?";
// };
};
private static final String DIALOG_SETTINGS = QuickSearchDialog.class.getName()+".DIALOG_SETTINGS"; //$NON-NLS-1$
private static final String DIALOG_BOUNDS_SETTINGS = "DialogBoundsSettings"; //$NON-NLS-1$
private static final String DIALOG_HEIGHT = "DIALOG_HEIGHT"; //$NON-NLS-1$
private static final String DIALOG_WIDTH = "DIALOG_WIDTH"; //$NON-NLS-1$
private static final String DIALOG_COLUMNS = "COLUMN_WIDTHS"; //$NON-NLS-1$
private static final String DIALOG_SASH_WEIGHTS = "SASH_WEIGHTS"; //$NON-NLS-1$
private static final String DIALOG_LAST_QUERY = "LAST_QUERY"; //$NON-NLS-1$
private static final String DIALOG_PATH_FILTER = "PATH_FILTER"; //$NON-NLS-1$
private static final String CASE_SENSITIVE = "CASE_SENSITIVE"; //$NON-NLS-1$
private static final boolean CASE_SENSITIVE_DEFAULT = true;
private static final String KEEP_OPEN = "KEEP_OPEN"; //$NON-NLS-1$
private static final boolean KEEP_OPEN_DEFAULT = false;
/**
* Represents an empty selection in the pattern input field (used only for
* initial pattern).
*/
public static final int NONE = 0;
/**
* Pattern input field selection where caret is at the beginning (used only
* for initial pattern).
*/
public static final int CARET_BEGINNING = 1;
/**
* Represents a full selection in the pattern input field (used only for
* initial pattern).
*/
public static final int FULL_SELECTION = 2;
private Text pattern;
private TableViewer list;
private MenuManager menuManager;
private MenuManager contextMenuManager;
private boolean multi;
private ToolBar toolBar;
private ToolItem toolItem;
private Label progressLabel;
private ContentProvider contentProvider;
private String initialPatternText;
private int selectionMode;
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private final int MAX_LINE_LEN;
private IHandlerActivation showViewHandler;
private QuickTextSearcher searcher;
private StyledText details;
private DocumentFetcher documents;
private ToggleCaseSensitiveAction toggleCaseSensitiveAction;
private ToggleKeepOpenAction toggleKeepOpenAction;
private QuickSearchContext context;
private SashForm sashForm;
private Label headerLabel;
private IWorkbenchWindow window;
private Text searchIn;
private Label listLabel;
/**
* Creates a new instance of the class.
*
* @param window.getShell()
* shell to parent the dialog on
* @param multi
* indicates whether dialog allows to select more than one
* position in its list of items
*/
public QuickSearchDialog(IWorkbenchWindow window) {
super(window.getShell());
this.window = window;
setShellStyle(SWT.CLOSE | SWT.MODELESS | SWT.BORDER | SWT.TITLE | SWT.RESIZE);
setBlockOnOpen(false);
this.setTitle(Messages.QuickSearchDialog_title);
this.context = new QuickSearchContext(window);
this.multi = false;
contentProvider = new ContentProvider();
selectionMode = NONE;
MAX_LINE_LEN = QuickSearchActivator.getDefault().getPreferences().getMaxLineLen();
progressJob.setSystem(true);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.window.Window#create()
*/
@Override
public void create() {
super.create();
pattern.setFocus();
}
/**
* Restores dialog using persisted settings.
*/
protected void restoreDialog(IDialogSettings settings) {
try {
if (initialPatternText==null) {
String lastSearch = settings.get(DIALOG_LAST_QUERY);
if (lastSearch==null) {
lastSearch = EMPTY_STRING;
}
pattern.setText(lastSearch);
pattern.selectAll();
}
if (settings.get(DIALOG_PATH_FILTER)!=null) {
String filter = settings.get(DIALOG_PATH_FILTER);
searchIn.setText(filter);
}
if (settings.getArray(DIALOG_COLUMNS)!=null) {
String[] columnWidths = settings.getArray(DIALOG_COLUMNS);
Table table = list.getTable();
int cols = table.getColumnCount();
for (int i = 0; i < cols; i++) {
TableColumn col = table.getColumn(i);
try {
if (col!=null) {
col.setWidth(Integer.parseInt(columnWidths[i]));
}
} catch (Throwable e) {
QuickSearchActivator.log(e);
}
}
}
if (settings.getArray(DIALOG_SASH_WEIGHTS)!=null) {
String[] _weights = settings.getArray(DIALOG_SASH_WEIGHTS);
int[] weights = new int[_weights.length];
for (int i = 0; i < weights.length; i++) {
weights[i] = Integer.parseInt(_weights[i]);
}
sashForm.setWeights(weights);
}
} catch (Throwable e) {
//None of this stuff is critical so shouldn't stop opening dialog if it fails!
QuickSearchActivator.log(e);
}
}
private class ToggleKeepOpenAction extends Action {
public ToggleKeepOpenAction(IDialogSettings settings) {
super(
Messages.QuickSearchDialog_keepOpen_toggle,
IAction.AS_CHECK_BOX
);
if (settings.get(KEEP_OPEN)==null) {
setChecked(KEEP_OPEN_DEFAULT);
} else{
setChecked(settings.getBoolean(KEEP_OPEN));
}
}
@Override
public void run() {
//setChecked(!isChecked());
}
}
private class ToggleCaseSensitiveAction extends Action {
public ToggleCaseSensitiveAction(IDialogSettings settings) {
super(
Messages.QuickSearchDialog_caseSensitive_toggle,
IAction.AS_CHECK_BOX
);
if (settings.get(CASE_SENSITIVE)==null) {
setChecked(CASE_SENSITIVE_DEFAULT);
} else{
setChecked(settings.getBoolean(CASE_SENSITIVE));
}
}
@Override
public void run() {
//setChecked(!isChecked());
refreshHeaderLabel();
applyFilter(false);
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.window.Window#close()
*/
@Override
public boolean close() {
this.progressJob.cancel();
this.progressJob = null;
// this.refreshProgressMessageJob.cancel();
if (showViewHandler != null) {
IHandlerService service = PlatformUI
.getWorkbench().getService(IHandlerService.class);
service.deactivateHandler(showViewHandler);
showViewHandler.getHandler().dispose();
showViewHandler = null;
}
if (menuManager != null)
menuManager.dispose();
if (contextMenuManager != null)
contextMenuManager.dispose();
storeDialog(getDialogSettings());
if (searcher!=null) {
searcher.cancel();
}
return super.close();
}
/**
* Stores dialog settings.
*
* @param settings
* settings used to store dialog
*/
protected void storeDialog(IDialogSettings settings) {
String currentSearch = pattern.getText();
settings.put(DIALOG_LAST_QUERY, currentSearch);
settings.put(DIALOG_PATH_FILTER, searchIn.getText());
if (toggleCaseSensitiveAction!=null) {
settings.put(CASE_SENSITIVE, toggleCaseSensitiveAction.isChecked());
}
if (toggleKeepOpenAction!=null) {
settings.put(KEEP_OPEN, toggleKeepOpenAction.isChecked());
}
Table table = list.getTable();
if (table.getColumnCount()>0) {
String[] columnWidths = new String[table.getColumnCount()];
for (int i = 0; i < columnWidths.length; i++) {
columnWidths[i] = Integer.toString(table.getColumn(i).getWidth());
}
settings.put(DIALOG_COLUMNS, columnWidths);
}
if (sashForm.getWeights()!=null) {
int[] w = sashForm.getWeights();
String[] ws = new String[w.length];
for (int i = 0; i < ws.length; i++) {
ws[i] = Integer.toString(w[i]);
}
settings.put(DIALOG_SASH_WEIGHTS, ws);
}
}
/**
* Create a new header which is labelled by headerLabel.
*
* @param parent
* @return Label the label of the header
*/
private Label createHeader(Composite parent) {
Composite header = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
layout.marginWidth = 0;
layout.marginHeight = 0;
header.setLayout(layout);
headerLabel = new Label(header, SWT.NONE);
headerLabel.addTraverseListener(e -> {
if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {
e.detail = SWT.TRAVERSE_NONE;
pattern.setFocus();
}
});
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
headerLabel.setLayoutData(gd);
createViewMenu(header);
header.setLayoutData(gd);
refreshHeaderLabel();
return headerLabel;
}
private void refreshHeaderLabel() {
String msg = toggleCaseSensitiveAction.isChecked() ? Messages.QuickSearchDialog_caseSensitive_label : Messages.QuickSearchDialog_caseInsensitive_label;
headerLabel.setText(msg);
}
/**
* Create the labels for the list and the progress. Return the list label.
*
* @param parent
* @return Label
*/
private Label createLabels(Composite parent) {
Composite labels = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.numColumns = 2;
layout.marginWidth = 0;
layout.marginHeight = 0;
labels.setLayout(layout);
Label listLabel = new Label(labels, SWT.NONE);
listLabel
.setText(WorkbenchMessages.FilteredItemsSelectionDialog_listLabel);
listLabel.addTraverseListener(e -> {
if (e.detail == SWT.TRAVERSE_MNEMONIC && e.doit) {
e.detail = SWT.TRAVERSE_NONE;
list.getTable().setFocus();
}
});
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
listLabel.setLayoutData(gd);
progressLabel = new Label(labels, SWT.RIGHT);
gd = new GridData(GridData.FILL_HORIZONTAL);
progressLabel.setLayoutData(gd);
createButton(labels, REFRESH_BUTTON_ID, Messages.QuickSearchDialog_Refresh, false);
labels.setLayoutData(gd);
return listLabel;
}
private void createViewMenu(Composite parent) {
toolBar = new ToolBar(parent, SWT.FLAT);
toolItem = new ToolItem(toolBar, SWT.PUSH, 0);
GridData data = new GridData();
data.horizontalAlignment = GridData.END;
toolBar.setLayoutData(data);
toolBar.addMouseListener(new MouseAdapter() {
@Override
public void mouseDown(MouseEvent e) {
showViewMenu();
}
});
toolItem.setImage(WorkbenchImages
.getImage(IWorkbenchGraphicConstants.IMG_LCL_VIEW_MENU));
toolItem
.setToolTipText(WorkbenchMessages.FilteredItemsSelectionDialog_menu);
toolItem.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
showViewMenu();
}
});
menuManager = new MenuManager();
fillViewMenu(menuManager);
IHandlerService service = PlatformUI.getWorkbench()
.getService(IHandlerService.class);
IHandler handler = new AbstractHandler() {
@Override
public Object execute(ExecutionEvent event) {
showViewMenu();
return null;
}
};
showViewHandler = service.activateHandler(
IWorkbenchCommandConstants.WINDOW_SHOW_VIEW_MENU, handler,
new ActiveShellExpression(getShell()));
}
/**
* Fills the menu of the dialog.
*
* @param menuManager
* the menu manager
*/
protected void fillViewMenu(IMenuManager menuManager) {
IDialogSettings settings = getDialogSettings();
toggleCaseSensitiveAction = new ToggleCaseSensitiveAction(settings);
menuManager.add(toggleCaseSensitiveAction);
toggleKeepOpenAction = new ToggleKeepOpenAction(settings);
menuManager.add(toggleKeepOpenAction);
}
private void showViewMenu() {
Menu menu = menuManager.createContextMenu(getShell());
Rectangle bounds = toolItem.getBounds();
Point topLeft = new Point(bounds.x, bounds.y + bounds.height);
topLeft = toolBar.toDisplay(topLeft);
menu.setLocation(topLeft.x, topLeft.y);
menu.setVisible(true);
}
/**
* Hook that allows to add actions to the context menu.
* <p>
* Subclasses may extend in order to add other actions.</p>
*
* @param menuManager the context menu manager
* @since 3.5
*/
protected void fillContextMenu(IMenuManager menuManager) {
}
private void createPopupMenu() {
contextMenuManager = new MenuManager();
contextMenuManager.setRemoveAllWhenShown(true);
contextMenuManager.addMenuListener(this::fillContextMenu);
final Table table = list.getTable();
Menu menu= contextMenuManager.createContextMenu(table);
table.setMenu(menu);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite)
*/
@Override
protected Control createDialogArea(Composite parent) {
Composite dialogArea = (Composite) super.createDialogArea(parent);
dialogArea.addDisposeListener(e -> QuickSearchDialog.this.dispose());
Composite content = createNestedComposite(dialogArea, 1, false);
GridData gd = new GridData(GridData.FILL_BOTH);
content.setLayoutData(gd);
final Label headerLabel = createHeader(content);
Composite inputRow = createNestedComposite(content, 10, true);
GridDataFactory.fillDefaults().grab(true, false).applyTo(inputRow);
pattern = new Text(inputRow, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL);
pattern.getAccessible().addAccessibleListener(new AccessibleAdapter() {
@Override
public void getName(AccessibleEvent e) {
e.result = LegacyActionTools.removeMnemonics(headerLabel
.getText());
}
});
GridDataFactory.fillDefaults().span(6,1).grab(true, false).applyTo(pattern);
Composite searchInComposite = createNestedComposite(inputRow, 2, false);
GridDataFactory.fillDefaults().span(4,1).grab(true, false).applyTo(searchInComposite);
Label searchInLabel = new Label(searchInComposite, SWT.NONE);
searchInLabel.setText(Messages.QuickSearchDialog_In);
GridDataFactory.swtDefaults().indent(5, 0).applyTo(searchInLabel);
searchIn = new Text(searchInComposite, SWT.SINGLE | SWT.BORDER | SWT.ICON_CANCEL);
searchIn.setToolTipText(Messages.QuickSearchDialog_InTooltip);
GridDataFactory.fillDefaults().grab(true, false).indent(5, 0).applyTo(searchIn);
listLabel = createLabels(content);
sashForm = new SashForm(content, SWT.VERTICAL);
GridDataFactory.fillDefaults().grab(true, true).applyTo(sashForm);
list = new TableViewer(sashForm, (multi ? SWT.MULTI : SWT.SINGLE) |
SWT.FULL_SELECTION | SWT.BORDER | SWT.V_SCROLL | SWT.VIRTUAL);
// ColumnViewerToolTipSupport.enableFor(list, ToolTip.NO_RECREATE);
list.getTable().setHeaderVisible(true);
list.getTable().setLinesVisible(true);
list.getTable().getAccessible().addAccessibleListener(
new AccessibleAdapter() {
@Override
public void getName(AccessibleEvent e) {
if (e.childID == ACC.CHILDID_SELF) {
e.result = LegacyActionTools
.removeMnemonics(listLabel.getText());
}
}
});
list.setContentProvider(contentProvider);
// new ScrollListener(list.getTable().getVerticalBar());
// new SelectionChangedListener(list);
TableViewerColumn col = new TableViewerColumn(list, SWT.RIGHT);
col.setLabelProvider(LINE_NUMBER_LABEL_PROVIDER);
col.getColumn().setText(Messages.QuickSearchDialog_line);
col.getColumn().setWidth(40);
col = new TableViewerColumn(list, SWT.LEFT);
col.getColumn().setText(Messages.QuickSearchDialog_text);
col.setLabelProvider(LINE_TEXT_LABEL_PROVIDER);
col.getColumn().setWidth(400);
col = new TableViewerColumn(list, SWT.LEFT);
col.getColumn().setText(Messages.QuickSearchDialog_path);
col.setLabelProvider(LINE_FILE_LABEL_PROVIDER);
col.getColumn().setWidth(150);
new TableResizeHelper(list).enableResizing();
//list.setLabelProvider(getItemsListLabelProvider());
list.setInput(new Object[0]);
list.setItemCount(contentProvider.getNumberOfElements());
gd = new GridData(GridData.FILL_BOTH);
applyDialogFont(list.getTable());
gd.heightHint= list.getTable().getItemHeight() * 15;
list.getTable().setLayoutData(gd);
createPopupMenu();
pattern.addModifyListener(e -> {
applyFilter(false);
});
searchIn.addModifyListener(e -> {
applyPathMatcher();
});
pattern.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ARROW_DOWN) {
if (list.getTable().getItemCount() > 0) {
list.getTable().setFocus();
list.getTable().select(0);
//programatic selection may not fire selection events so...
refreshDetails();
}
}
}
});
list.addSelectionChangedListener(event -> {
StructuredSelection selection = (StructuredSelection) event
.getSelection();
handleSelected(selection);
});
list.addDoubleClickListener(event -> handleDoubleClick());
list.getTable().addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.keyCode == SWT.ARROW_UP && (e.stateMask & SWT.SHIFT) == 0
&& (e.stateMask & SWT.CTRL) == 0) {
StructuredSelection selection = (StructuredSelection) list
.getSelection();
if (selection.size() == 1) {
Object element = selection.getFirstElement();
if (element.equals(list.getElementAt(0))) {
pattern.setFocus();
}
list.getTable().notifyListeners(SWT.Selection,
new Event());
}
}
if (e.keyCode == SWT.ARROW_DOWN
&& (e.stateMask & SWT.SHIFT) != 0
&& (e.stateMask & SWT.CTRL) != 0) {
list.getTable().notifyListeners(SWT.Selection, new Event());
}
}
});
createDetailsArea(sashForm);
sashForm.setWeights(new int[] {5,2});
applyDialogFont(content);
restoreDialog(getDialogSettings());
if (initialPatternText != null) {
pattern.setText(initialPatternText);
}
switch (selectionMode) {
case CARET_BEGINNING:
pattern.setSelection(0, 0);
break;
case FULL_SELECTION:
pattern.selectAll();
break;
}
// apply filter even if pattern is empty (display history)
applyFilter(false);
return dialogArea;
}
private Composite createNestedComposite(Composite parent, int numRows, boolean equalRows) {
Composite nested = new Composite(parent, SWT.NONE);
{
GridLayout layout = new GridLayout(numRows, equalRows);
layout.marginWidth = 0;
layout.marginHeight = 0;
layout.marginLeft = 0;
layout.marginRight = 0;
layout.horizontalSpacing = 0;
nested.setLayout(layout);
}
GridDataFactory.fillDefaults().grab(true, false).applyTo(nested);
return nested;
}
protected void dispose() {
if (blankImage!=null) {
blankImage.dispose();
blankImage = null;
}
}
private void createDetailsArea(Composite parent) {
details = new StyledText(parent, SWT.MULTI+SWT.READ_ONLY+SWT.BORDER+SWT.H_SCROLL+SWT.V_SCROLL);
details.setFont(JFaceResources.getFont(TEXT_FONT));
list.addSelectionChangedListener(event -> refreshDetails());
details.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
refreshDetails();
}
});
}
// Dumber version just using the a 'raw' StyledText widget.
private void refreshDetails() {
if (details!=null && list!=null && !list.getTable().isDisposed()) {
if (documents==null) {
documents = new DocumentFetcher();
}
IStructuredSelection sel = (IStructuredSelection) list.getSelection();
if (sel==null || sel.isEmpty()) {
details.setText(EMPTY_STRING);
} else {
//Not empty selection
final int context = 100; // number of lines before and after match to include in preview
int numLines = computeLines();
if (numLines > 0) {
LineItem item = (LineItem) sel.getFirstElement();
IDocument document = documents.getDocument(item.getFile());
if (document!=null) {
try {
int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based.
int contextStartLine = Math.max(line-(numLines-1)/2 - context, 0);
int start = document.getLineOffset(contextStartLine);
int end = document.getLength();
try {
IRegion lineInfo = document.getLineInformation(line + numLines/2 + context);
end = lineInfo.getOffset() + lineInfo.getLength();
} catch (BadLocationException e) {
//Presumably line number is past the end of document.
//ignore.
}
StyledString styledString = highlightMatches(document.get(start, end-start));
details.setText(styledString.getString());
details.setStyleRanges(styledString.getStyleRanges());
details.setTopIndex(Math.max(line - contextStartLine - numLines/2, 0));
return;
} catch (BadLocationException e) {
}
}
}
}
//empty selection or some error:
details.setText(EMPTY_STRING);
}
}
/**
* Computes how many lines of text can be displayed in the details section.
*/
private int computeLines() {
if (details!=null && !details.isDisposed()) {
int lineHeight = details.getLineHeight();
int areaHeight = details.getClientArea().height;
return (areaHeight + lineHeight - 1) / lineHeight;
}
return 0;
}
/**
* Helper function to highlight all the matches for the current query in a given piece
* of text.
*
* @return StyledString instance.
*/
private StyledString highlightMatches(String visibleText) {
StyledString styledText = new StyledString(visibleText);
List<TextRange> matches = getQuery().findAll(visibleText);
for (TextRange m : matches) {
styledText.setStyle(m.getOffset(), m.getLength(), HIGHLIGHT_STYLE);
}
return styledText;
}
// Version using sourceviewer
// private void refreshDetails() {
// if (details!=null && list!=null && !list.getTable().isDisposed()) {
// if (documents==null) {
// documents = new DocumentFetcher();
// }
// IStructuredSelection sel = (IStructuredSelection) list.getSelection();
// if (sel!=null && !sel.isEmpty()) {
// //Not empty selection
// LineItem item = (LineItem) sel.getFirstElement();
// IDocument document = documents.getDocument(item.getFile());
// try {
// int line = item.getLineNumber()-1; //in document lines are 0 based. In search 1 based.
// int start = document.getLineOffset(Math.max(line-2, 0));
// int end = document.getLength();
// try {
// end = document.getLineOffset(line+3);
// } catch (BadLocationException e) {
// //Presumably line number is past the end of document.
// //ignore.
// }
// details.setDocument(document, start, end-start);
//
// String visibleText = document.get(start, end-start);
// List<TextRange> matches = getQuery().findAll(visibleText);
// Region visibleRegion = new Region(start, end-start);
// TextPresentation presentation = new TextPresentation(visibleRegion, 20);
// presentation.setDefaultStyleRange(new StyleRange(0, document.getLength(), null, null));
// for (TextRange m : matches) {
// presentation.addStyleRange(new StyleRange(m.start+start, m.len, null, YELLOW));
// }
// details.changeTextPresentation(presentation, true);
//
// return;
// } catch (BadLocationException e) {
// }
// }
// details.setDocument(null);
// }
// }
/**
* Handle selection in the items list by updating labels of selected and
* unselected items and refresh the details field using the selection.
*
* @param selection
* the new selection
*/
protected void handleSelected(StructuredSelection selection) {
IStatus status = new Status(IStatus.OK, PlatformUI.PLUGIN_ID,
IStatus.OK, EMPTY_STRING, null);
updateStatus(status);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.window.Dialog#getDialogBoundsSettings()
*/
@Override
protected IDialogSettings getDialogBoundsSettings() {
IDialogSettings settings = getDialogSettings();
IDialogSettings section = settings.getSection(DIALOG_BOUNDS_SETTINGS);
if (section == null) {
section = settings.addNewSection(DIALOG_BOUNDS_SETTINGS);
section.put(DIALOG_HEIGHT, 500);
section.put(DIALOG_WIDTH, 600);
}
return section;
}
/**
* Returns the dialog settings. Returned object can't be null.
*
* @return return dialog settings for this dialog
*/
protected IDialogSettings getDialogSettings() {
IDialogSettings dialogSettings = PlatformUI.getDialogSettingsProvider(FrameworkUtil.getBundle(IDEWorkbenchPlugin.class))
.getDialogSettings();
IDialogSettings settings = dialogSettings.getSection(DIALOG_SETTINGS);
if (settings == null) {
settings = dialogSettings
.addNewSection(DIALOG_SETTINGS);
}
return settings;
}
/**
* Has to be called in UI thread.
*/
public void refreshWidgets() {
if (list != null && !list.getTable().isDisposed()) {
int itemCount = contentProvider.getNumberOfElements();
list.setItemCount(itemCount);
listLabel.setText(NLS.bind(Messages.QuickSearchDialog_listLabel, itemCount));
listLabel.pack();
list.refresh(true, false);
Button openButton = getButton(OPEN_BUTTON_ID);
if (openButton!=null && !openButton.isDisposed()) {
//Even if no element is selected. The dialog should be have as if the first
//element in the list is selected. So the button is enabled if any
//element is available in the list.
openButton.setEnabled(itemCount>0);
}
}
}
/**
* Schedule refresh job.
*/
public void scheduleRefresh() {
refreshJob.schedule();
// refreshCacheJob.cancelAll();
// refreshCacheJob.schedule();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.dialogs.SelectionStatusDialog#computeResult()
*/
@Override
protected void computeResult() {
List objectsToReturn = ((StructuredSelection) list.getSelection())
.toList();
if (objectsToReturn.isEmpty()) {
//Pretend that the first element is selected.
Object first = list.getElementAt(0);
if (first!=null) {
objectsToReturn = Arrays.asList(first);
}
}
setResult(objectsToReturn);
}
/**
* Handles double-click of items, but *also* by pressing the 'enter' key.
*/
protected void handleDoubleClick() {
okPressed();
}
protected void refreshButtonPressed() {
applyFilter(true);
}
@Override
protected void okPressed() {
computeResult();
openSelection();
if (!toggleKeepOpenAction.isChecked()) {
setReturnCode(OK);
close();
}
}
@Override
protected void buttonPressed(int buttonId) {
if (buttonId == REFRESH_BUTTON_ID) {
refreshButtonPressed();
} else {
super.buttonPressed(buttonId);
}
}
@Override
protected void createButtonsForButtonBar(Composite parent) {
createButton(parent, OPEN_BUTTON_ID, Messages.QuickSearchDialog_Open, true);
refreshWidgets();
}
/**
* Sets the initial pattern used by the filter. This text is copied into the
* selection input on the dialog. A full selection is used in the pattern
* input field.
*
* @param text
* initial pattern for the filter
* @see QuickSearchDialog#FULL_SELECTION
*/
public void setInitialPattern(String text) {
setInitialPattern(text, FULL_SELECTION);
}
/**
* Sets the initial pattern used by the filter. This text is copied into the
* selection input on the dialog. The <code>selectionMode</code> is used
* to choose selection type for the input field.
*
* @param text
* initial pattern for the filter
* @param selectionMode
* one of: {@link QuickSearchDialog#NONE},
* {@link QuickSearchDialog#CARET_BEGINNING},
* {@link QuickSearchDialog#FULL_SELECTION}
*/
public void setInitialPattern(String text, int selectionMode) {
this.initialPatternText = text;
this.selectionMode = selectionMode;
}
/**
* Gets initial pattern.
*
* @return initial pattern, or <code>null</code> if initial pattern is not
* set
*/
protected String getInitialPattern() {
return this.initialPatternText;
}
/**
* Returns the current selection.
*
* @return the current selection
*/
protected StructuredSelection getSelectedItems() {
StructuredSelection selection = (StructuredSelection) list
.getSelection();
List selectedItems = selection.toList();
return new StructuredSelection(selectedItems);
}
/**
* Validates the item. When items on the items list are selected or
* deselected, it validates each item in the selection and the dialog status
* depends on all validations.
*
* @param item
* an item to be checked
* @return status of the dialog to be set
*/
protected IStatus validateItem(Object item) {
return Status.OK_STATUS;
}
/**
* Creates an instance of a filter.
*
* @return a filter for items on the items list. Can be <code>null</code>,
* no filtering will be applied then, causing no item to be shown in
* the list.
*/
protected QuickTextQuery createFilter() {
return new QuickTextQuery(pattern.getText(), !toggleCaseSensitiveAction.isChecked());
}
/**
* Applies the filter created by <code>createFilter()</code> method to the
* items list. When new filter is different than previous one it will cause
* refiltering.
* <p>
* The 'force' parameter forces a full refresh of the search results / filter even
* when the filter is unchanged, or when a incremenal filtering optimisation could be
* applied based on query structure. (The use case for this, is to trigger forced refresh
* because the underlying resources may have changed).
*/
protected void applyFilter(boolean force) {
QuickTextQuery newFilter = createFilter();
if (this.searcher==null) {
if (!newFilter.isTrivial()) {
//Create the QuickTextSearcher with the inital query.
this.searcher = new QuickTextSearcher(newFilter, context.createPriorityFun(), MAX_LINE_LEN, new QuickTextSearchRequestor() {
@Override
public void add(LineItem match) {
contentProvider.add(match);
contentProvider.refresh();
}
@Override
public void clear() {
contentProvider.reset();
contentProvider.refresh();
}
@Override
public void revoke(LineItem match) {
contentProvider.remove(match);
contentProvider.refresh();
}
@Override
public void update(LineItem match) {
contentProvider.refresh();
}
});
applyPathMatcher();
refreshWidgets();
}
// this.list.setInput(input)
} else {
//The QuickTextSearcher is already active update the query
this.searcher.setQuery(newFilter, force);
}
if (progressJob!=null) {
progressJob.schedule();
}
}
private void applyPathMatcher() {
if (this.searcher!=null) {
this.searcher.setPathMatcher(ResourceMatchers.commaSeparatedPaths(searchIn.getText()));
}
}
/**
* Collects filtered elements. Contains one synchronized, sorted set for
* collecting filtered elements.
* Implementation of <code>ItemsFilter</code> is used to filter elements.
* The key function of filter used in to filtering is
* <code>matchElement(Object item)</code>.
* <p>
* The <code>ContentProvider</code> class also provides item filtering
* methods. The filtering has been moved from the standard TableView
* <code>getFilteredItems()</code> method to content provider, because
* <code>ILazyContentProvider</code> and virtual tables are used. This
* class is responsible for adding a separator below history items and
* marking each items as duplicate if its name repeats more than once on the
* filtered list.
*/
private class ContentProvider implements IStructuredContentProvider, ILazyContentProvider {
private List items;
/**
* Creates new instance of <code>ContentProvider</code>.
*/
public ContentProvider() {
this.items = Collections.synchronizedList(new ArrayList(2048));
// this.duplicates = Collections.synchronizedSet(new HashSet(256));
// this.lastSortedItems = Collections.synchronizedList(new ArrayList(
// 2048));
}
public void remove(LineItem match) {
this.items.remove(match);
}
/**
* Removes all content items and resets progress message.
*/
public void reset() {
this.items.clear();
}
/**
* Adds filtered item.
*
* @param match
*/
public void add(LineItem match) {
this.items.add(match);
}
/**
* Refresh dialog.
*/
public void refresh() {
scheduleRefresh();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
*/
@Override
public Object[] getElements(Object inputElement) {
return items.toArray();
}
public int getNumberOfElements() {
return items.size();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.IContentProvider#dispose()
*/
@Override
public void dispose() {
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
* java.lang.Object, java.lang.Object)
*/
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jface.viewers.ILazyContentProvider#updateElement(int)
*/
@Override
public void updateElement(int index) {
QuickSearchDialog.this.list.replace((items
.size() > index) ? items.get(index) : null,
index);
}
}
/**
* Get the control where the search pattern is entered. Any filtering should
* be done using an {@link ItemsFilter}. This control should only be
* accessed for listeners that wish to handle events that do not affect
* filtering such as custom traversal.
*
* @return Control or <code>null</code> if the pattern control has not
* been created.
*/
public Control getPatternControl() {
return pattern;
}
public QuickTextQuery getQuery() {
return searcher.getQuery();
}
}