487 lines
14 KiB
Java
487 lines
14 KiB
Java
package jadx.gui.ui;
|
|
|
|
import javax.swing.*;
|
|
import javax.swing.table.AbstractTableModel;
|
|
import javax.swing.table.TableCellRenderer;
|
|
import javax.swing.table.TableColumn;
|
|
import javax.swing.table.TableColumnModel;
|
|
import java.awt.*;
|
|
import java.awt.event.KeyAdapter;
|
|
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.util.*;
|
|
import java.util.List;
|
|
|
|
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
|
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
|
import org.fife.ui.rtextarea.SearchContext;
|
|
import org.fife.ui.rtextarea.SearchEngine;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import jadx.gui.jobs.BackgroundJob;
|
|
import jadx.gui.jobs.BackgroundWorker;
|
|
import jadx.gui.jobs.DecompileJob;
|
|
import jadx.gui.treemodel.JNode;
|
|
import jadx.gui.utils.CacheObject;
|
|
import jadx.gui.utils.NLS;
|
|
import jadx.gui.utils.Position;
|
|
import jadx.gui.utils.search.TextSearchIndex;
|
|
|
|
public abstract class CommonSearchDialog extends JDialog {
|
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class);
|
|
private static final long serialVersionUID = 8939332306115370276L;
|
|
|
|
public static final int RESULTS_PER_PAGE = 100;
|
|
|
|
protected final transient TabbedPane tabbedPane;
|
|
protected final transient CacheObject cache;
|
|
protected final transient MainWindow mainWindow;
|
|
protected final transient Font codeFont;
|
|
|
|
protected ResultsModel resultsModel;
|
|
protected ResultsTable resultsTable;
|
|
protected JLabel resultsInfoLabel;
|
|
protected JLabel warnLabel;
|
|
protected ProgressPanel progressPane;
|
|
|
|
protected String highlightText;
|
|
protected boolean highlightTextCaseInsensitive = false;
|
|
|
|
public CommonSearchDialog(MainWindow mainWindow) {
|
|
super(mainWindow);
|
|
this.mainWindow = mainWindow;
|
|
this.tabbedPane = mainWindow.getTabbedPane();
|
|
this.cache = mainWindow.getCacheObject();
|
|
this.codeFont = mainWindow.getSettings().getFont();
|
|
}
|
|
|
|
protected abstract void openInit();
|
|
|
|
protected abstract void loadFinished();
|
|
|
|
protected abstract void loadStart();
|
|
|
|
public void loadWindowPos() {
|
|
mainWindow.getSettings().loadWindowPos(this);
|
|
}
|
|
|
|
public void prepare() {
|
|
if (cache.getIndexJob().isComplete()) {
|
|
loadFinishedCommon();
|
|
loadFinished();
|
|
return;
|
|
}
|
|
LoadTask task = new LoadTask();
|
|
task.addPropertyChangeListener(progressPane);
|
|
task.execute();
|
|
}
|
|
|
|
protected void registerInitOnOpen() {
|
|
addWindowListener(new WindowAdapter() {
|
|
@Override
|
|
public void windowOpened(WindowEvent e) {
|
|
SwingUtilities.invokeLater(CommonSearchDialog.this::openInit);
|
|
}
|
|
});
|
|
}
|
|
|
|
protected synchronized void performSearch() {
|
|
resultsTable.updateTable();
|
|
updateProgressLabel();
|
|
}
|
|
|
|
protected void openSelectedItem() {
|
|
int selectedId = resultsTable.getSelectedRow();
|
|
if (selectedId == -1) {
|
|
return;
|
|
}
|
|
JNode node = (JNode) resultsModel.getValueAt(selectedId, 0);
|
|
tabbedPane.codeJump(new Position(node.getRootClass(), node.getLine()));
|
|
|
|
dispose();
|
|
}
|
|
|
|
@Override
|
|
public void dispose() {
|
|
mainWindow.getSettings().saveWindowPos(this);
|
|
super.dispose();
|
|
}
|
|
|
|
protected void initCommon() {
|
|
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
|
getRootPane().registerKeyboardAction(e -> dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
|
|
}
|
|
|
|
@NotNull
|
|
protected JPanel initButtonsPanel() {
|
|
progressPane = new ProgressPanel(mainWindow, false);
|
|
|
|
JButton cancelButton = new JButton(NLS.str("search_dialog.cancel"));
|
|
cancelButton.addActionListener(event -> dispose());
|
|
JButton openBtn = new JButton(NLS.str("search_dialog.open"));
|
|
openBtn.addActionListener(event -> openSelectedItem());
|
|
getRootPane().setDefaultButton(openBtn);
|
|
|
|
JPanel buttonPane = new JPanel();
|
|
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
|
|
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
|
buttonPane.add(progressPane);
|
|
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
|
|
buttonPane.add(Box.createHorizontalGlue());
|
|
buttonPane.add(openBtn);
|
|
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
|
|
buttonPane.add(cancelButton);
|
|
return buttonPane;
|
|
}
|
|
|
|
protected JPanel initResultsTable() {
|
|
ResultsTableCellRenderer renderer = new ResultsTableCellRenderer();
|
|
resultsModel = new ResultsModel(renderer);
|
|
resultsModel.addTableModelListener((e) -> updateProgressLabel());
|
|
resultsTable = new ResultsTable(resultsModel);
|
|
resultsTable.setShowHorizontalLines(false);
|
|
resultsTable.setDragEnabled(false);
|
|
resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
|
resultsTable.setBackground(CodeArea.CODE_BACKGROUND);
|
|
resultsTable.setColumnSelectionAllowed(false);
|
|
resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
|
|
resultsTable.setAutoscrolls(false);
|
|
resultsTable.setDefaultRenderer(Object.class, renderer);
|
|
|
|
resultsTable.addMouseListener(new MouseAdapter() {
|
|
@Override
|
|
public void mouseClicked(MouseEvent evt) {
|
|
if (evt.getClickCount() == 2) {
|
|
openSelectedItem();
|
|
}
|
|
}
|
|
});
|
|
resultsTable.addKeyListener(new KeyAdapter() {
|
|
@Override
|
|
public void keyPressed(KeyEvent e) {
|
|
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
|
openSelectedItem();
|
|
}
|
|
}
|
|
});
|
|
|
|
warnLabel = new JLabel();
|
|
warnLabel.setForeground(Color.RED);
|
|
warnLabel.setVisible(false);
|
|
|
|
JPanel resultsPanel = new JPanel();
|
|
resultsPanel.setLayout(new BoxLayout(resultsPanel, BoxLayout.PAGE_AXIS));
|
|
resultsPanel.add(warnLabel);
|
|
resultsPanel.add(new JScrollPane(resultsTable,
|
|
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
|
|
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED));
|
|
|
|
JPanel paginationPanel = new JPanel();
|
|
paginationPanel.setAlignmentX( Component.LEFT_ALIGNMENT );
|
|
paginationPanel.setLayout(new BoxLayout(paginationPanel, BoxLayout.X_AXIS));
|
|
resultsInfoLabel = new JLabel("");
|
|
|
|
JButton nextPageButton = new JButton("->");
|
|
nextPageButton.setToolTipText(NLS.str("search_dialog.next_page"));
|
|
nextPageButton.addActionListener((e) -> {
|
|
resultsModel.nextPage();
|
|
resultsTable.updateTable();
|
|
resultsTable.scrollRectToVisible(new Rectangle(0,0,1,1));
|
|
});
|
|
|
|
JButton prevPageButton = new JButton("<-");
|
|
prevPageButton.setToolTipText(NLS.str("search_dialog.prev_page"));
|
|
prevPageButton.addActionListener((e) -> {
|
|
resultsModel.prevPage();
|
|
resultsTable.updateTable();
|
|
resultsTable.scrollRectToVisible(new Rectangle(0,0,1,1));
|
|
});
|
|
|
|
paginationPanel.add(prevPageButton);
|
|
paginationPanel.add(nextPageButton);
|
|
paginationPanel.add(resultsInfoLabel);
|
|
|
|
resultsPanel.add(paginationPanel);
|
|
resultsPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
|
|
return resultsPanel;
|
|
}
|
|
|
|
protected void updateProgressLabel() {
|
|
String statusText = String.format(NLS.str("search_dialog.info_label"), resultsModel.getDisplayedResultsStart(),
|
|
resultsModel.getDisplayedResultsEnd(), resultsModel.getResultCount());
|
|
resultsInfoLabel.setText(statusText);
|
|
}
|
|
|
|
protected static class ResultsTable extends JTable {
|
|
private static final long serialVersionUID = 3901184054736618969L;
|
|
|
|
public ResultsTable(ResultsModel resultsModel) {
|
|
super(resultsModel);
|
|
}
|
|
|
|
public void updateTable() {
|
|
ResultsModel model = (ResultsModel) getModel();
|
|
TableColumnModel columnModel = getColumnModel();
|
|
|
|
int width = getParent().getWidth();
|
|
int firstColMaxWidth = (int) (width * 0.5);
|
|
int rowCount = getRowCount();
|
|
int columnCount = getColumnCount();
|
|
if (!model.isAddDescColumn()) {
|
|
firstColMaxWidth = width;
|
|
}
|
|
Component codeComp = null;
|
|
for (int col = 0; col < columnCount; col++) {
|
|
int colWidth = 50;
|
|
for (int row = 0; row < rowCount; row++) {
|
|
TableCellRenderer renderer = getCellRenderer(row, col);
|
|
Component comp = prepareRenderer(renderer, row, col);
|
|
if (comp == null) {
|
|
continue;
|
|
}
|
|
colWidth = Math.max(comp.getPreferredSize().width, colWidth);
|
|
if (codeComp == null && col == 1) {
|
|
codeComp = comp;
|
|
}
|
|
}
|
|
colWidth += 10;
|
|
if (col == 0) {
|
|
colWidth = Math.min(colWidth, firstColMaxWidth);
|
|
} else {
|
|
colWidth = Math.max(colWidth, width - columnModel.getColumn(0).getPreferredWidth());
|
|
}
|
|
TableColumn column = columnModel.getColumn(col);
|
|
column.setPreferredWidth(colWidth);
|
|
}
|
|
if (codeComp != null) {
|
|
setRowHeight(Math.max(20, codeComp.getPreferredSize().height + 4));
|
|
}
|
|
updateUI();
|
|
}
|
|
}
|
|
|
|
protected static class ResultsModel extends AbstractTableModel {
|
|
private static final long serialVersionUID = -7821286846923903208L;
|
|
private static final String[] COLUMN_NAMES = {"Node", "Code"};
|
|
|
|
private final transient ArrayList<JNode> rows = new ArrayList<>();
|
|
private final transient ResultsTableCellRenderer renderer;
|
|
private transient boolean addDescColumn;
|
|
private transient int start = 0;
|
|
|
|
public ResultsModel(ResultsTableCellRenderer renderer) {
|
|
this.renderer = renderer;
|
|
}
|
|
|
|
protected void addAll(Collection<? extends JNode> nodes) {
|
|
rows.ensureCapacity(rows.size() + nodes.size());
|
|
for (JNode node : nodes) {
|
|
add(node);
|
|
}
|
|
}
|
|
|
|
private void add(JNode node) {
|
|
if (node.hasDescString()) {
|
|
addDescColumn = true;
|
|
}
|
|
rows.add(node);
|
|
}
|
|
|
|
public void clear() {
|
|
start = 0;
|
|
addDescColumn = false;
|
|
rows.clear();
|
|
renderer.clear();
|
|
}
|
|
|
|
public boolean isAddDescColumn() {
|
|
return addDescColumn;
|
|
}
|
|
|
|
public int getResultCount() {
|
|
return rows.size();
|
|
}
|
|
|
|
public int getDisplayedResultsStart() {
|
|
if (rows.size() == 0)
|
|
return 0;
|
|
return start + 1;
|
|
}
|
|
|
|
public int getDisplayedResultsEnd() {
|
|
return Math.min(rows.size(), start + RESULTS_PER_PAGE);
|
|
}
|
|
|
|
public void nextPage() {
|
|
if (start + RESULTS_PER_PAGE < rows.size()) {
|
|
renderer.clear();
|
|
start += RESULTS_PER_PAGE;
|
|
fireTableStructureChanged();
|
|
}
|
|
}
|
|
|
|
public void prevPage() {
|
|
if (start - RESULTS_PER_PAGE >= 0) {
|
|
renderer.clear();
|
|
start -= RESULTS_PER_PAGE;
|
|
fireTableStructureChanged();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getRowCount() {
|
|
return rows.size() - start;
|
|
}
|
|
|
|
@Override
|
|
public int getColumnCount() {
|
|
return 2;
|
|
}
|
|
|
|
@Override
|
|
public String getColumnName(int index) {
|
|
return COLUMN_NAMES[index];
|
|
}
|
|
|
|
@Override
|
|
public Object getValueAt(int rowIndex, int columnIndex) {
|
|
return rows.get(rowIndex + start);
|
|
}
|
|
}
|
|
|
|
protected class ResultsTableCellRenderer implements TableCellRenderer {
|
|
private final Color selectedBackground;
|
|
private final Color selectedForeground;
|
|
private final Color foreground;
|
|
|
|
private final JLabel emptyLabel = new JLabel();
|
|
|
|
private Map<Integer, Component> componentCache = new HashMap<>();
|
|
|
|
public ResultsTableCellRenderer() {
|
|
UIDefaults defaults = UIManager.getDefaults();
|
|
foreground = defaults.getColor("List.foreground");
|
|
selectedBackground = defaults.getColor("List.selectionBackground");
|
|
selectedForeground = defaults.getColor("List.selectionForeground");
|
|
}
|
|
|
|
@Override
|
|
public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected,
|
|
boolean hasFocus, int row, int column) {
|
|
int id = row << 2 | column;
|
|
Component comp = componentCache.get(id);
|
|
if (comp == null) {
|
|
if (obj instanceof JNode) {
|
|
comp = makeCell((JNode) obj, column);
|
|
componentCache.put(id, comp);
|
|
} else {
|
|
comp = emptyLabel;
|
|
}
|
|
}
|
|
updateSelection(comp, isSelected);
|
|
return comp;
|
|
}
|
|
|
|
private void updateSelection(Component comp, boolean isSelected) {
|
|
if (isSelected) {
|
|
comp.setBackground(selectedBackground);
|
|
comp.setForeground(selectedForeground);
|
|
} else {
|
|
comp.setBackground(CodeArea.CODE_BACKGROUND);
|
|
comp.setForeground(foreground);
|
|
}
|
|
}
|
|
|
|
private Component makeCell(JNode node, int column) {
|
|
if (column == 0) {
|
|
JLabel label = new JLabel(node.makeLongString() + " ", node.getIcon(), SwingConstants.LEFT);
|
|
label.setOpaque(true);
|
|
label.setToolTipText(label.getText());
|
|
return label;
|
|
}
|
|
if (!node.hasDescString()) {
|
|
return emptyLabel;
|
|
}
|
|
RSyntaxTextArea textArea = new RSyntaxTextArea();
|
|
textArea.setFont(codeFont);
|
|
textArea.setEditable(false);
|
|
textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
|
|
textArea.setText(" " + node.makeDescString());
|
|
textArea.setRows(1);
|
|
textArea.setColumns(textArea.getText().length());
|
|
if (highlightText != null) {
|
|
SearchContext searchContext = new SearchContext(highlightText);
|
|
searchContext.setMatchCase(!highlightTextCaseInsensitive);
|
|
searchContext.setMarkAll(true);
|
|
SearchEngine.markAll(textArea, searchContext);
|
|
}
|
|
return textArea;
|
|
}
|
|
|
|
public void clear() {
|
|
componentCache.clear();
|
|
}
|
|
}
|
|
|
|
private class LoadTask extends SwingWorker<Void, Void> {
|
|
public LoadTask() {
|
|
loadStartCommon();
|
|
loadStart();
|
|
}
|
|
|
|
@Override
|
|
public Void doInBackground() {
|
|
try {
|
|
BackgroundWorker backgroundWorker = mainWindow.getBackgroundWorker();
|
|
if (backgroundWorker == null) {
|
|
return null;
|
|
}
|
|
backgroundWorker.exec();
|
|
|
|
DecompileJob decompileJob = cache.getDecompileJob();
|
|
progressPane.changeLabel(this, decompileJob.getInfoString());
|
|
decompileJob.processAndWait();
|
|
|
|
BackgroundJob indexJob = cache.getIndexJob();
|
|
progressPane.changeLabel(this, indexJob.getInfoString());
|
|
indexJob.processAndWait();
|
|
} catch (Exception e) {
|
|
LOG.error("Waiting background tasks failed", e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
public void done() {
|
|
loadFinishedCommon();
|
|
loadFinished();
|
|
}
|
|
}
|
|
|
|
protected void loadStartCommon() {
|
|
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
|
progressPane.setIndeterminate(true);
|
|
progressPane.setVisible(true);
|
|
resultsTable.setEnabled(false);
|
|
warnLabel.setVisible(false);
|
|
}
|
|
|
|
private void loadFinishedCommon() {
|
|
setCursor(null);
|
|
resultsTable.setEnabled(true);
|
|
progressPane.setVisible(false);
|
|
|
|
TextSearchIndex textIndex = cache.getTextIndex();
|
|
if (textIndex == null) {
|
|
warnLabel.setText("Index not initialized, search will be disabled!");
|
|
warnLabel.setVisible(true);
|
|
}
|
|
}
|
|
}
|