package jadx.gui.ui; import jadx.gui.jobs.BackgroundJob; import jadx.gui.jobs.BackgroundWorker; import jadx.gui.jobs.DecompileJob; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.TextNode; import jadx.gui.utils.CacheObject; import jadx.gui.utils.NLS; import jadx.gui.utils.Position; import jadx.gui.utils.search.TextSearchIndex; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.ScrollPaneConstants; import javax.swing.SwingConstants; import javax.swing.SwingWorker; import javax.swing.UIDefaults; import javax.swing.UIManager; import javax.swing.table.AbstractTableModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; 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; 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 MAX_RESULTS_COUNT = 100; protected final TabbedPane tabbedPane; protected final CacheObject cache; protected final MainWindow mainWindow; protected final Font codeFont; protected ResultsModel resultsModel; protected ResultsTable resultsTable; protected JLabel warnLabel; protected ProgressPanel progressPane; protected String highlightText; public CommonSearchDialog(MainWindow mainWindow) { super(mainWindow); this.mainWindow = mainWindow; this.tabbedPane = mainWindow.getTabbedPane(); this.cache = mainWindow.getCacheObject(); this.codeFont = mainWindow.getSettings().getFont(); } 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 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(new ActionListener() { @Override public void actionPerformed(ActionEvent 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(new ActionListener() { public void actionPerformed(ActionEvent event) { dispose(); } }); JButton openBtn = new JButton(NLS.str("search_dialog.open")); openBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent 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); resultsTable = new ResultsTable(resultsModel); resultsTable.setShowHorizontalLines(false); resultsTable.setDragEnabled(false); resultsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); resultsTable.setBackground(CodeArea.BACKGROUND); resultsTable.setColumnSelectionAllowed(false); resultsTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); resultsTable.setAutoscrolls(false); Enumeration<TableColumn> columns = resultsTable.getColumnModel().getColumns(); while (columns.hasMoreElements()) { TableColumn column = columns.nextElement(); column.setCellRenderer(renderer); } resultsTable.addMouseListener(new MouseAdapter() { 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)); resultsPanel.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); return resultsPanel; } 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 List<JNode> rows = new ArrayList<JNode>(); private final ResultsTableCellRenderer renderer; private boolean addDescColumn; public ResultsModel(ResultsTableCellRenderer renderer) { this.renderer = renderer; } protected void addAll(Iterable<? extends JNode> nodes) { for (JNode node : nodes) { int size = getRowCount(); if (size >= MAX_RESULTS_COUNT) { if (size == MAX_RESULTS_COUNT) { add(new TextNode("Search results truncated (limit: " + MAX_RESULTS_COUNT + ")")); } return; } add(node); } } private void add(JNode node) { if (node.hasDescString()) { addDescColumn = true; } rows.add(node); } public void clear() { addDescColumn = false; rows.clear(); renderer.clear(); } public boolean isAddDescColumn() { return addDescColumn; } @Override public int getRowCount() { return rows.size(); } @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); } } 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<Integer, Component>(); 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.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(true); 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); } } protected abstract void loadFinished(); protected abstract void loadStart(); }