blob: c12e2bc38c269b3e1ec8b9f6fc4ad815465feac0 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2010, 2021 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.internal.r.ui.dataeditor;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWindowListener;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.statet.ecommons.ui.dialogs.DialogUtils;
import org.eclipse.statet.ecommons.ui.dialogs.ExtStatusDialog;
import org.eclipse.statet.ecommons.ui.util.LayoutUtils;
import org.eclipse.statet.internal.r.ui.RUIPlugin;
import org.eclipse.statet.r.ui.dataeditor.RDataTableComposite;
public class FindDataDialog extends ExtStatusDialog {
private static final Map<IWorkbenchWindow, FindDataDialog> gDialogs= new HashMap<>();
public static FindDataDialog get(final IWorkbenchWindow window, final boolean create) {
FindDataDialog dialog= gDialogs.get(window);
if (dialog == null && create) {
dialog= new FindDataDialog(window);
dialog.create();
gDialogs.put(window, dialog);
}
return dialog;
}
private static final int FIND_NEXT_ID= 101;
private static final int FIND_PREVIOUS_ID= 102;
private class PartListener implements IWindowListener, IPartListener {
@Override
public void windowOpened(final IWorkbenchWindow window) {
}
@Override
public void windowClosed(final IWorkbenchWindow window) {
if (FindDataDialog.this.fWindow == window) {
close();
}
}
@Override
public void windowActivated(final IWorkbenchWindow window) {
}
@Override
public void windowDeactivated(final IWorkbenchWindow window) {
}
@Override
public void partOpened(final IWorkbenchPart part) {
}
@Override
public void partClosed(final IWorkbenchPart part) {
if (FindDataDialog.this.fPart == part) {
FindDataDialog.this.fPart= null;
}
}
@Override
public void partActivated(final IWorkbenchPart part) {
FindDataDialog.this.fPart= part;
update(part);
}
@Override
public void partDeactivated(final IWorkbenchPart part) {
}
@Override
public void partBroughtToTop(final IWorkbenchPart part) {
}
}
private final IWorkbenchWindow fWindow;
private IWorkbenchPart fPart;
private final PartListener fPartListener= new PartListener();
private RDataTableComposite fTable;
private Combo fTextControl;
private Button fRExpressionModeControl;
private Button fIsNaModeControl;
private Button fDirectionFirstInColumnControl;
private Button fDirectionFirstInRowControl;
private Button fSelectedOnlyControl;
private final Map<String, String> fHistoryModeMap= new HashMap<>();
private final IFindListener fFindListener= new IFindListener() {
@Override
public void handleFindEvent(final IFindListener.FindEvent event) {
updateStatus(event.status);
}
};
protected FindDataDialog(final IWorkbenchWindow window) {
super(window.getShell());
this.fWindow= window;
this.fWindow.getWorkbench().addWindowListener(this.fPartListener);
this.fWindow.getActivePage().addPartListener(this.fPartListener);
this.fPart= this.fWindow.getActivePage().getActivePart();
setTitle("Find");
setShellStyle((getShellStyle() & ~SWT.APPLICATION_MODAL) | SWT.MODELESS);
setBlockOnOpen(false);
setStatusLineAboveButtons(false);
setHelpAvailable(false);
}
protected IDialogSettings getDialogSettings() {
return DialogUtils.getDialogSettings(RUIPlugin.getInstance(), "RDataTable.FindDialog");
}
@Override
protected IDialogSettings getDialogBoundsSettings() {
return getDialogSettings();
}
@Override
public void create() {
super.create();
loadSettings();
}
@Override
public boolean close() {
this.fWindow.getWorkbench().removeWindowListener(this.fPartListener);
final IWorkbenchPage page= this.fWindow.getActivePage();
if (page != null) {
page.removePartListener(this.fPartListener);
}
gDialogs.remove(this.fWindow);
saveSettings();
return super.close();
}
@Override
protected Control createDialogArea(final Composite parent) {
final Composite composite= new Composite(parent, SWT.NONE);
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
composite.setLayout(LayoutUtils.newDialogGrid(2));
final Composite textInput= createTextInput(composite);
textInput.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
final Composite additionalOptions= createAdditionalOptions(composite);
additionalOptions.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
final Composite directionOptions= createDirectionOptions(composite);
directionOptions.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
// final Composite scopeOptions= createScopeOptions(composite);
// scopeOptions.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
//
LayoutUtils.addSmallFiller(composite, false);
final Composite navigateButtons= createNavigateButtons(composite);
navigateButtons.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 2, 1));
applyDialogFont(composite);
return composite;
}
protected Composite createTextInput(final Composite parent) {
final Composite composite= new Composite(parent, SWT.NONE);
composite.setLayout(LayoutUtils.newCompositeGrid(2));
final Label label= new Label(composite, SWT.NONE);
label.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false));
label.setText("Find:");
this.fTextControl= new Combo(composite, SWT.DROP_DOWN | SWT.BORDER);
this.fTextControl.setLayoutData(LayoutUtils.hintWidth(
new GridData(SWT.FILL, SWT.CENTER, true, false), this.fTextControl, 25 ));
this.fTextControl.addListener(SWT.Modify, new Listener() {
@Override
public void handleEvent(final Event event) {
updateStatus(null);
updateState();
}
});
this.fTextControl.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final int selectionIdx;
if (!FindDataDialog.this.fTextControl.getListVisible()
&& (selectionIdx= FindDataDialog.this.fTextControl.getSelectionIndex()) >= 0) {
loadQuery(FindDataDialog.this.fTextControl.getItem(selectionIdx));
}
}
});
return composite;
}
protected Composite createDirectionOptions(final Composite parent) {
final Group composite= new Group(parent, SWT.NONE);
composite.setText("Direction");
composite.setLayout(LayoutUtils.newGroupGrid(2, true));
{ this.fDirectionFirstInColumnControl= new Button(composite, SWT.RADIO);
this.fDirectionFirstInColumnControl.setText("&Column, Row");
this.fDirectionFirstInColumnControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.fDirectionFirstInColumnControl.setSelection(true);
}
{ this.fDirectionFirstInRowControl= new Button(composite, SWT.RADIO);
this.fDirectionFirstInRowControl.setText("&Row, Column");
this.fDirectionFirstInRowControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
}
{ this.fSelectedOnlyControl= new Button(composite, SWT.CHECK);
this.fSelectedOnlyControl.setText("Only Selec&ted Cells");
this.fSelectedOnlyControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
}
return composite;
}
protected Composite createScopeOptions(final Composite parent) {
final Group composite= new Group(parent, SWT.NONE);
composite.setText("Scope");
composite.setLayout(LayoutUtils.newGroupGrid(1));
{ final Button button= new Button(composite, SWT.RADIO);
button.setText("A&ll");
button.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
button.setSelection(true);
}
{ this.fSelectedOnlyControl= new Button(composite, SWT.CHECK);
this.fSelectedOnlyControl.setText("Selec&ted Cells");
this.fSelectedOnlyControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
}
return composite;
}
protected Composite createAdditionalOptions(final Composite parent) {
final Group composite= new Group(parent, SWT.NONE);
composite.setText("Options");
composite.setLayout(LayoutUtils.newGroupGrid(2, true));
{ this.fRExpressionModeControl= new Button(composite, SWT.CHECK);
this.fRExpressionModeControl.setText("R expression");
this.fRExpressionModeControl.setToolTipText("Use x to reference the object itself, e.g. 'x >= 1'");
this.fRExpressionModeControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.fRExpressionModeControl.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
if (FindDataDialog.this.fRExpressionModeControl.getSelection()) {
FindDataDialog.this.fIsNaModeControl.setSelection(false);
}
}
});
}
{ this.fIsNaModeControl= new Button(composite, SWT.CHECK);
this.fIsNaModeControl.setText("Is NA");
this.fIsNaModeControl.setToolTipText("The search expression is ignored");
this.fIsNaModeControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
this.fIsNaModeControl.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
if (FindDataDialog.this.fIsNaModeControl.getSelection()) {
FindDataDialog.this.fRExpressionModeControl.setSelection(false);
}
}
});
}
return composite;
}
protected Composite createNavigateButtons(final Composite parent) {
final Composite composite= new Composite(parent, SWT.NONE);
composite.setLayout(LayoutUtils.newCompositeGrid(0));
createButton(composite, FIND_PREVIOUS_ID, "&Previous", false);
createButton(composite, FIND_NEXT_ID, "&Next", true);
return composite;
}
@Override
protected void createButtonsForButtonBar(final Composite parent) {
createButton(parent, IDialogConstants.CLOSE_ID, IDialogConstants.CLOSE_LABEL, false);
}
@Override
protected void buttonPressed(final int buttonId) {
switch (buttonId) {
case IDialogConstants.CLOSE_ID:
close();
return;
case FIND_NEXT_ID:
doFind(true);
return;
case FIND_PREVIOUS_ID:
doFind(false);
return;
}
}
private void loadQuery(final String text) {
if (text == null || text.isEmpty()) {
return;
}
final String mode= this.fHistoryModeMap.get(text);
if (mode != null) {
if ("r_expression".equals(mode)) {
this.fRExpressionModeControl.setSelection(true);
this.fIsNaModeControl.setSelection(false);
}
else {
this.fRExpressionModeControl.setSelection(false);
}
}
}
private void doFind(final boolean forward) {
final String expression= getExpression();
if (!expression.equals("is.na(x)")) {
final String text= this.fTextControl.getText();
final int idx= this.fTextControl.indexOf(text);
if (idx != 0) {
if (idx > 0) {
this.fTextControl.remove(idx);
}
this.fTextControl.add(text, 0);
this.fTextControl.setText(text);
}
this.fHistoryModeMap.put(text,
this.fRExpressionModeControl.getSelection() ? "r_expression" : "default");
}
this.fTable.find(expression,
this.fSelectedOnlyControl.getSelection(),
this.fDirectionFirstInRowControl.getSelection(),
forward);
}
protected String getExpression() {
if (this.fRExpressionModeControl.getSelection()) {
return this.fTextControl.getText();
}
else if (this.fIsNaModeControl.getSelection()) {
return "is.na(x)";
}
else {
return "x == " + this.fTextControl.getText();
}
}
protected void loadSettings() {
final IDialogSettings settings= getDialogSettings();
final String[] history= DialogUtils.noNull(settings.getArray("SearchText.history"));
this.fTextControl.setItems(history);
final String[] historyModes= DialogUtils.noNull(settings.getArray("SearchMode.history"));
if (history.length == historyModes.length) {
for (int i= 0; i < history.length; i++) {
this.fHistoryModeMap.put(history[i], historyModes[i]);
}
}
if (history.length > 0) {
this.fTextControl.setText(history[0]);
loadQuery(history[0]);
}
if (settings.getBoolean("Direction.firstInRow")) {
this.fDirectionFirstInColumnControl.setSelection(false);
this.fDirectionFirstInRowControl.setSelection(true);
}
else {
this.fDirectionFirstInColumnControl.setSelection(true);
this.fDirectionFirstInRowControl.setSelection(false);
}
}
protected void saveSettings() {
final IDialogSettings settings= getDialogSettings();
final String[] history= DialogUtils.combineHistoryItems(this.fTextControl.getItems(), null);
settings.put("SearchText.history", history);
final String[] historyModes= new String[history.length];
for (int i= 0; i < history.length; i++) {
String mode= this.fHistoryModeMap.get(history[i]);
if (mode == null) {
mode= "default";
}
historyModes[i]= mode;
}
settings.put("SearchMode.history", historyModes);
settings.put("Direction.firstInRow", this.fDirectionFirstInRowControl.getSelection());
}
public void update(final IWorkbenchPart part) {
if (part != null && this.fPart == part) {
final RDataTableComposite table= part.getAdapter(RDataTableComposite.class);
if (this.fTable != null && this.fTable != table) {
this.fTable.removeFindListener(this.fFindListener);
updateStatus(null);
}
this.fTable= table;
if (this.fTable != null) {
table.addFindListener(this.fFindListener);
}
updateState();
}
}
private void updateState() {
final boolean enabled= (this.fTable != null && this.fTextControl.getText().length() > 0);
getButton(FIND_NEXT_ID).setEnabled(enabled);
getButton(FIND_PREVIOUS_ID).setEnabled(enabled);
}
}