workbench/mps-workbench/source/jetbrains/mps/ide/ui/FindTextInModelDialog.java (602 lines of code) (raw):
/*
* Copyright 2003-2022 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.mps.ide.ui;
import com.intellij.find.SearchTextArea;
import com.intellij.ide.IdeEventQueue;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.CommonShortcuts;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.actionSystem.ShortcutSet;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogPanel;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.OnePixelDivider;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.util.PopupUtil;
import com.intellij.openapi.util.DimensionService;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.wm.IdeGlassPane;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.impl.IdeGlassPaneImpl;
import com.intellij.ui.AnimatedIcon;
import com.intellij.ui.DocumentAdapter;
import com.intellij.ui.DoubleClickListener;
import com.intellij.ui.ExperimentalUI;
import com.intellij.ui.JBColor;
import com.intellij.ui.OnePixelSplitter;
import com.intellij.ui.PopupBorder;
import com.intellij.ui.ScrollingUtil;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.WindowMoveListener;
import com.intellij.ui.WindowResizeListener;
import com.intellij.ui.WindowRoundedCornersManager;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.JBTextArea;
import com.intellij.ui.scale.JBUIScale;
import com.intellij.ui.table.JBTable;
import com.intellij.util.Alarm;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBEmptyBorder;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.JBUI.Borders;
import com.intellij.util.ui.JBUI.CurrentTheme.CustomFrameDecorations;
import com.intellij.util.ui.UIUtil;
import jetbrains.mps.ide.findusages.findalgorithm.finders.IFinder;
import jetbrains.mps.ide.findusages.findalgorithm.resultproviders.treenodes.BaseNode;
import jetbrains.mps.ide.findusages.model.IResultProvider;
import jetbrains.mps.ide.findusages.model.SearchQuery;
import jetbrains.mps.ide.findusages.model.SearchResult;
import jetbrains.mps.ide.findusages.model.holders.StringHolder;
import jetbrains.mps.ide.findusages.view.UsageToolOptions;
import jetbrains.mps.ide.findusages.view.UsagesViewTool;
import jetbrains.mps.ide.ui.FindTextInModelTask.MatchHandlerEx;
import jetbrains.mps.openapi.navigation.EditorNavigator;
import jetbrains.mps.project.MPSProject;
import jetbrains.mps.scope.EmptySearchScope;
import jetbrains.mps.smodel.SNodeUtil;
import net.miginfocom.swing.MigLayout;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.model.EditableSModel;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.util.ProgressMonitor;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.event.DocumentEvent;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableModel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.MessageFormat;
import java.util.List;
/**
* Dialog to query search string to look up property values in model.
* Quite rudimentary at the moment, supposed to improve over time to get close to IDEA's FindPopupPanel (Find in Path action)
*
* @author Artem Tikhomirov
* @since 2019.2
*/
public final class FindTextInModelDialog extends DialogWrapper {
private static final KeyStroke ENTER = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
private static final String SERVICE_KEY = "mps.find.text.popup";
private static final String SPLITTER_SERVICE_KEY = "mps.find.text.popup.splitter";
private final MPSProject myProject;
private final Alarm mySearchSchedule;
private SearchTextArea mySearchEntry;
private JBTable myResultsPreviewTable;
private String myText;
private FindTextInModelTask myCurrentSearchTask;
private FindTextResultPreview myUsagePreviewPanel;
private FindTextInModelDialogHeader myHeader;
private OnePixelSplitter myPreviewSplitter;
public FindTextInModelDialog(@NotNull MPSProject mpsProject) {
// inspired by FindPopupPanel
super(mpsProject.getProject(), null, true, DialogWrapper.IdeModalityType.MODELESS, true);
myProject = mpsProject;
mySearchSchedule = new Alarm(getDisposable());
setOKButtonText("Open in Find Usages");
setUndecorated(true);
init();
getRootPane().setDefaultButton(null);
Project project = myProject.getProject();
final Window window = WindowManager.getInstance().suggestParentWindow(project);
Component parent = UIUtil.findUltimateParent(window);
RelativePoint showPoint = null;
Point screenPoint = DimensionService.getInstance().getLocation(getDimensionServiceKey(), project);
if (screenPoint != null) {
if (parent != null) {
SwingUtilities.convertPointFromScreen(screenPoint, parent);
showPoint = new RelativePoint(parent, screenPoint);
} else {
showPoint = new RelativePoint(screenPoint);
}
}
Window dialogWindow = getPeer().getWindow();
if (showPoint != null) {
setLocation(showPoint.getScreenPoint());
} else {
dialogWindow.setLocationRelativeTo(parent);
}
// Add reaction to drag by mouse to top most component
new WindowMoveListener(getContentPanel())
.installTo(getContentPanel())
.installTo(myHeader.panel);
if (WindowRoundedCornersManager.isAvailable()) {
WindowRoundedCornersManager.setRoundedCorners(dialogWindow);
}
Dimension panelSize = getPreferredSize();
Dimension prev = DimensionService.getInstance().getSize(getDimensionServiceKey(), project);
panelSize.width += JBUIScale.scale(24); //hidden 'loading' icon
panelSize.height *= 2;
if (prev != null && prev.height < panelSize.height) {
prev.height = panelSize.height;
}
dialogWindow.setMinimumSize(panelSize);
dialogWindow.setBackground(UIUtil.getPanelBackground());
if (prev == null) {
panelSize.height *= 1.5;
panelSize.width *= 1.15;
}
dialogWindow.setSize(prev != null ? prev : panelSize);
JRootPane root = ((RootPaneContainer) dialogWindow).getRootPane();
if (SystemInfo.isMac && UIUtil.isUnderDarcula()) {
root.setBorder(PopupBorder.Factory.createColored(OnePixelDivider.BACKGROUND));
} else {
root.setBorder(PopupBorder.Factory.create(true, true));
}
IdeGlassPane glass = (IdeGlassPane) root.getGlassPane();
// Override WindowResizeListener#setCursor to show resize cursor in expected places
WindowResizeListener resizeListener = new WindowResizeListener(
root, JBUI.insets(4), null) {
private Cursor myCursor;
@Override
protected void setCursor(@NotNull Component content, Cursor cursor) {
if (myCursor != cursor || myCursor != Cursor.getDefaultCursor()) {
glass.setCursor(cursor, this);
myCursor = cursor;
if (content instanceof JComponent) {
IdeGlassPaneImpl.savePreProcessedCursor((JComponent) content, content.getCursor());
}
super.setCursor(content, cursor);
}
}
};
glass.addMousePreprocessor(resizeListener, getDisposable());
glass.addMouseMotionPreprocessor(resizeListener, getDisposable());
//
// XXX perhaps, shall move the next line to UI action?
IdeEventQueue.getInstance().getPopupManager().closeAllPopups(false);
final AnAction escape = ActionManager.getInstance().getAction("EditorEscape");
DumbAwareAction.create(e -> doCancelAction())
.registerCustomShortcutSet(escape == null ? CommonShortcuts.ESCAPE : escape.getShortcutSet(), root, getDisposable());
}
@Override
protected JButton createJButtonForAction(Action action) {
JButton button = super.createJButtonForAction(action);
button.setOpaque(false);
return button;
}
protected JComponent createSouthPanel() {
JComponent southPanel = super.createSouthPanel();
if (ExperimentalUI.isNewUI()) {
southPanel.setBorder(JBUI.Borders.empty(JBUI.CurrentTheme.FindPopup.bottomPanelInsets()));
southPanel.setBackground(JBUI.CurrentTheme.Advertiser.background());
} else {
southPanel.setBorder(JBUI.Borders.empty(5));
}
return southPanel;
}
public void setText(@Nullable String text) {
myText = text;
if (mySearchEntry != null) {
mySearchEntry.getTextArea().setText(text);
}
}
@Nullable
public String getText() {
return myText;
}
@Override
protected void dispose() {
saveSettings();
super.dispose();
}
@Nullable
@Override
protected Border createContentPaneBorder() {
return null;
}
@Override
protected JComponent createCenterPanel() {
DialogPanel dialogPanel = new DialogPanel();
dialogPanel.setLayout(new MigLayout("flowx, ins 0, gap 0, fillx, hidemode 3"));
final JBLabel title = new JBLabel("Find Text in Node Properties");
title.setFont(title.getFont().deriveFont(Font.BOLD));
JBTextArea textArea = new JBTextArea(1, 25); // values from FindPopupPanel
if (myText != null) {
textArea.setText(myText);
}
mySearchEntry = new SearchTextArea(textArea, true);
mySearchEntry.setMultilineEnabled(false);
// beware, SearchTextArea changes Document of text area, add listener *after* new SearchTextArea did its dirty job
mySearchEntry.getTextArea().getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(@NotNull DocumentEvent e) {
scheduleResultsUpdate();
}
});
myResultsPreviewTable = new JBTable() {
@Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(getWidth(), 1 + getRowHeight() * 4);
}
};
myResultsPreviewTable.getEmptyText().setShowAboveCenter(false);
myResultsPreviewTable.setShowColumns(false);
myResultsPreviewTable.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
myResultsPreviewTable.setShowGrid(false);
myResultsPreviewTable.setIntercellSpacing(JBUI.emptySize());
new DoubleClickListener() {
@Override
protected boolean onDoubleClick(@NotNull MouseEvent event) {
if (event.getSource() != myResultsPreviewTable) {
return false;
}
navigateToSelectedUsage();
return true;
}
}.installOn(myResultsPreviewTable);
// Focus on the table is disabled as we wish to both navigate on the preview table and edit results (we transfer focus)
myResultsPreviewTable.setFocusable(false);
myResultsPreviewTable.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
// In FindPopupPanel transferFocus is called, but they have a fine control over focus which isn't setup here
// When adding buttons; this can be changed to "focus text area" instead
myResultsPreviewTable.transferFocusBackward();
}
});
myResultsPreviewTable.getEmptyText().setText("Enter property value to look up");
JBScrollPane scrollPane = new JBScrollPane(myResultsPreviewTable);
scrollPane.setBorder(JBUI.Borders.empty());
// Add default find next/previous result shortcuts on dialog components to navigate results
JComponent[] tableAware = {textArea};
for (JComponent component : tableAware) {
ScrollingUtil.installActions(myResultsPreviewTable, false, component);
}
KeymapManager keymapManager = KeymapManager.getInstance();
Keymap activeKeymap = keymapManager != null ? keymapManager.getActiveKeymap() : null;
if (activeKeymap != null) {
ShortcutSet findNextShortcutSet = new CustomShortcutSet(activeKeymap.getShortcuts("FindNext"));
ShortcutSet findPreviousShortcutSet = new CustomShortcutSet(activeKeymap.getShortcuts("FindPrevious"));
DumbAwareAction findNextAction = DumbAwareAction.create(event -> {
int selectedRow = myResultsPreviewTable.getSelectedRow();
if (selectedRow >= 0 && selectedRow < myResultsPreviewTable.getRowCount() - 1) {
myResultsPreviewTable.setRowSelectionInterval(selectedRow + 1, selectedRow + 1);
ScrollingUtil.ensureIndexIsVisible(myResultsPreviewTable, selectedRow + 1, 1);
}
});
DumbAwareAction findPreviousAction = DumbAwareAction.create(event -> {
int selectedRow = myResultsPreviewTable.getSelectedRow();
if (selectedRow > 0 && selectedRow <= myResultsPreviewTable.getRowCount() - 1) {
myResultsPreviewTable.setRowSelectionInterval(selectedRow - 1, selectedRow - 1);
ScrollingUtil.ensureIndexIsVisible(myResultsPreviewTable, selectedRow - 1, 1);
}
});
for (JComponent component : tableAware) {
findNextAction.registerCustomShortcutSet(findNextShortcutSet, component);
findPreviousAction.registerCustomShortcutSet(findPreviousShortcutSet, component);
}
}
new MyEnterAction().registerCustomShortcutSet(new CustomShortcutSet(ENTER), dialogPanel);
myUsagePreviewPanel = new FindTextResultPreview(myProject) {
@Override
public Dimension getPreferredSize() {
// TODO get editor line height, replace 0 by it * 15
return new Dimension(myResultsPreviewTable.getWidth(), Math.max(getHeight(), 0));
}
};
Disposer.register(getDisposable(), myUsagePreviewPanel);
myResultsPreviewTable.getSelectionModel().addListSelectionListener(e -> {
if (myResultsPreviewTable.getSelectedRow() >= 0) {
final Object valueAt = myResultsPreviewTable.getValueAt(myResultsPreviewTable.getSelectedRow(), 0);
if (valueAt instanceof TableEntry) {
myProject.getRepository().getModelAccess().runReadAction(() -> {
SNode node = ((TableEntry) valueAt).node.resolve(myProject.getRepository());
SNode root = node.getContainingRoot();
myPreviewSplitter.getSecondComponent().setVisible(node != null);
myUsagePreviewPanel.editNode(root);
myUsagePreviewPanel.selectNode(node);
});
}
} else {
myPreviewSplitter.getSecondComponent().setVisible(false);
}
});
myPreviewSplitter = new OnePixelSplitter(true, .33f);
myPreviewSplitter.setSplitterProportionKey(SPLITTER_SERVICE_KEY);
myPreviewSplitter.getDivider().setBackground(OnePixelDivider.BACKGROUND);
myPreviewSplitter.setFirstComponent(scrollPane);
myPreviewSplitter.setSecondComponent(myUsagePreviewPanel);
// Not visible before result come in
myPreviewSplitter.getSecondComponent().setVisible(false);
myHeader = new FindTextInModelDialogHeader();
dialogPanel.add(myHeader.panel, "growx, pushx, wrap");
dialogPanel.add(mySearchEntry, "growx, pushx, wrap");
dialogPanel.add(myPreviewSplitter, "growx, pushx, growy, pushy, wrap");
dialogPanel.setPreferredFocusedComponent(textArea);
if (ExperimentalUI.isNewUI()) {
Color background = JBUI.CurrentTheme.Popup.BACKGROUND;
myHeader.panel.setBorder(JBUI.Borders.compound(JBUI.Borders.customLineBottom(JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground()),
PopupUtil.getComplexPopupHorizontalHeaderBorder()));
myHeader.panel.setBackground(JBUI.CurrentTheme.ComplexPopup.HEADER_BACKGROUND);
dialogPanel.setBackground(background);
mySearchEntry.setOpaque(false);
mySearchEntry.setBorder(PopupUtil.createComplexPopupTextFieldBorder());
myResultsPreviewTable.setBackground(background);
var textFieldBorderInsets = JBUI.CurrentTheme.ComplexPopup.textFieldBorderInsets();
// noinspection UseDPIAwareInsets
myPreviewSplitter.setBlindZone(() -> new Insets(0, textFieldBorderInsets.left, 0, textFieldBorderInsets.right));
scrollPane.setBorder(JBUI.Borders.emptyBottom(4));
} else {
myHeader.panel.setBorder(JBUI.Borders.empty(2, 5));
mySearchEntry.setBorder(JBUI.Borders.compound(
JBUI.Borders.customLine(JBUI.CurrentTheme.BigPopup.searchFieldBorderColor(), 1, 0, 1, 0),
JBUI.Borders.empty(1, 0, 2, 0)));
scrollPane.setBorder(JBUI.Borders.empty());
}
return dialogPanel;
}
@Override
public void show() {
super.show();
Window window = this.getPeer().getWindow();
window.addWindowListener(new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
window.addWindowFocusListener(new WindowAdapter() {
@Override
public void windowLostFocus(WindowEvent evt) {
Window oppositeWindow = evt.getOppositeWindow();
if (oppositeWindow == window || oppositeWindow != null && oppositeWindow.getOwner() == window) {
return;
}
if (canBeClosed() || oppositeWindow != null) {
FindTextInModelDialog.this.doCancelAction();
}
}
});
}
});
}
private boolean canBeClosed() {
if (myProject.isDisposed()) {
return true;
}
if (!ApplicationManager.getApplication().isActive()) {
return false;
}
if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow() == null) {
return false;
}
List<JBPopup> popups = ContainerUtil.filter(JBPopupFactory.getInstance().getChildPopups(getContentPanel()), popup -> !popup.isDisposed());
if (!popups.isEmpty()) {
for (JBPopup popup : popups) {
popup.cancel();
}
return false;
}
return true;
}
@Override
public void doCancelAction() {
mySearchSchedule.cancelAllRequests();
stopCurrentSearch();
super.doCancelAction();
}
@Override
protected void doOKAction() {
myText = mySearchEntry.getTextArea().getText();
mySearchSchedule.cancelAllRequests();
stopCurrentSearch();
super.doOKAction();
if (myText == null || myText.isEmpty()) {
return;
}
// FIXME transient == true just because result provider (and, perhaps, search query) could not get serialized
UsageToolOptions opt = new UsageToolOptions().navigateIfSingle(false).notFoundMessage("No matching property values found").transientView(true);
SearchQuery sq = new SearchQuery(new StringHolder(myText), new EmptySearchScope());
IResultProvider rp = new BaseNode() {
@Override
protected void doFindResults(@NotNull SearchQuery q, @NotNull final IFinder.FindCallback cb, @NotNull ProgressMonitor pm) {
final String text = ((StringHolder) q.getObjectHolder()).getObject();
final FindTextInModelTask task = new FindTextInModelTask(myProject, text, new FindReferencesSink(myProject, (n, p, value) -> cb.onUsageFound(new SearchResult<>(p, n, "usage"))));
task.performLookup(pm);
}
};
UsagesViewTool.showUsages(myProject.getProject(), rp, sq, opt);
}
@Override
protected String getDimensionServiceKey() {
return SERVICE_KEY;
}
private void saveSettings() {
DimensionService.getInstance().setSize(getDimensionServiceKey(), getSize(), myProject.getProject());
DimensionService.getInstance().setLocation(getDimensionServiceKey(), getWindow().getLocationOnScreen(), myProject.getProject());
}
@SuppressWarnings("WeakerAccess")
/*package*/ TableEntry toEntry(SNode node, SProperty p, String value) {
// collect necessary presentation data while still in model read
SNode named = node;
while (named != null && !named.isInstanceOfConcept(SNodeUtil.concept_INamedConcept)) {
named = named.getParent();
}
if (named == null) {
named = node;
}
String loc = SNodeUtil.getPresentation(named);
final String text = String.format("%s%s.%s", loc, named == node ? "" : "[...]", p.getName());
final SModel m = node.getModel();
return new TableEntry(node.getReference(), value, text, m instanceof EditableSModel && ((EditableSModel) m).isChanged());
}
@SuppressWarnings("WeakerAccess")
/*package*/ void scheduleResultsUpdate() {
if (mySearchSchedule.isDisposed()) {
return;
}
mySearchSchedule.cancelAllRequests();
mySearchSchedule.addRequest(this::textChanged, 300);
}
private void stopCurrentSearch() {
// assumes EDT
final FindTextInModelTask oldTask = myCurrentSearchTask;
if (oldTask != null) {
oldTask.cancel();
}
}
@SuppressWarnings("WeakerAccess")
/*package*/ void textChanged() {
// this method works in EDT
final DefaultTableModel model = new DefaultTableModel() {
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
};
model.addColumn("usages");
final String text = mySearchEntry.getTextArea().getText();
// Reset regardless of the text value
myPreviewSplitter.getSecondComponent().setVisible(false);
myResultsPreviewTable.setModel(model);
myResultsPreviewTable.getColumnModel().getColumn(0).setCellRenderer(new UsageTableCellRenderer());
stopCurrentSearch();
if (text == null || text.isEmpty()) {
myResultsPreviewTable.getEmptyText().setText("Enter property value to look up");
myHeader.updateStatus(null, "");
return;
}
myCurrentSearchTask = new FindTextInModelTask(myProject, text, new FindReferencesSink(myProject, new MatchHandlerEx() {
@Override
public void handleMatch(SNode one, SProperty p, String value) {
final TableEntry tableEntry = toEntry(one, p, value);
ApplicationManager.getApplication().invokeLater(() -> {
model.addRow(new Object[]{tableEntry});
if (model.getRowCount() == 1 && myResultsPreviewTable.getModel() == model) {
myResultsPreviewTable.setRowSelectionInterval(0, 0);
}
});
}
@Override
public void reset() {
// TODO might serve no purpose here (we issue new search with new callback every time)
model.setRowCount(0);
}
@Override
public void done(ProgressMonitor monitor) {
ApplicationManager.getApplication().invokeLater(() -> {
// Ensure model hasn't changed (on new search issued)
if (myResultsPreviewTable.getModel() == model) {
myHeader.updateStatus(null, MessageFormat.format("{0} {0,choice, 0# matches|1# match|2# matches}", model.getRowCount()));
if (model.getRowCount() == 0) {
myResultsPreviewTable.getEmptyText().setText("Nothing found");
}
}
});
MatchHandlerEx.super.done(monitor);
}
@Override
public void aborted() {
ApplicationManager.getApplication().invokeLater(() -> {
// Ensure model hasn't changed (on new search issued)
if (myResultsPreviewTable.getModel() != model) {
myHeader.updateStatus(null, "Search aborted");
}
});
}
}));
myResultsPreviewTable.getEmptyText().setText("Searching...");
myHeader.updateStatus(AnimatedIcon.Default.INSTANCE, "");
ProgressIndicatorUtils.scheduleWithWriteActionPriority(myCurrentSearchTask);
}
private void navigateToSelectedUsage() {
final int selectedRow = myResultsPreviewTable.getSelectedRow();
final TableModel model = myResultsPreviewTable.getModel();
if (selectedRow != -1 && model != null) {
final Object valueAt = model.getValueAt(selectedRow, 0);
if (valueAt instanceof TableEntry) {
// Just open chosen property and close dialog
new EditorNavigator(myProject).shallFocus(false).selectIfChild().open(((TableEntry) valueAt).node);
if (canBeClosed()) {
FindTextInModelDialog.this.doCancelAction();
}
}
}
}
static final class TableEntry {
public final SNodeReference node;
public final String value;
public final String nodePresentation;
public final boolean fromChangedModel;
/*package*/ TableEntry(SNodeReference ptr, String pv, String np, boolean changedModel) {
node = ptr;
value = pv;
nodePresentation = np;
this.fromChangedModel = changedModel;
}
}
static final class UsageTableCellRenderer extends JPanel implements TableCellRenderer {
private static final int MARGIN = 2;
private final JBLabel myChangedIndicator;
private final SimpleColoredComponent myValue;
private final SimpleColoredComponent myLocation;
UsageTableCellRenderer() {
setLayout(new BorderLayout());
add(myChangedIndicator = new JBLabel(), BorderLayout.WEST);
// These components are used rather than JBLabel since JBLabel accepts HTML content and we don't want it
add(myValue = new SimpleColoredComponent(), BorderLayout.CENTER);
add(myLocation = new SimpleColoredComponent(), BorderLayout.EAST);
myValue.setOpaque(false);
myLocation.setOpaque(false);
myChangedIndicator.setForeground(FileStatus.MODIFIED.getColor());
myLocation.setForeground(UIUtil.getInactiveTextColor());
/* As we don't have a line number on the right side as in IntelliJ IDEA,
* we have to set some right margin. 2x was chosen experimentally */
setBorder(JBUI.Borders.empty(MARGIN, MARGIN, MARGIN, 2 * MARGIN));
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
// Table is not focusable, but we want to make rows appear selected and focused
isSelected = table.getSelectedRow() == row;
setBackground(UIUtil.getTableBackground(isSelected, isSelected));
myValue.setForeground(UIUtil.getTableForeground(isSelected, isSelected));
if (value instanceof TableEntry) {
final TableEntry te = (TableEntry) value;
myChangedIndicator.setText(te.fromChangedModel ? "*" : " ");
myValue.clear();
myValue.append(te.value);
myLocation.clear();
myLocation.append(te.nodePresentation);
}
return this;
}
}
private class MyEnterAction extends DumbAwareAction {
@Override
public void update(@NotNull AnActionEvent e) {
e.getPresentation().setEnabled(e.getData(CommonDataKeys.EDITOR) == null);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
navigateToSelectedUsage();
}
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
// updates UI
return ActionUpdateThread.EDT;
}
}
}