Skip to content

Commit

Permalink
feat(gui): add manual search, stop and sort actions to search dialog (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Aug 5, 2022
1 parent e641b77 commit 11d0450
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 51 deletions.
10 changes: 10 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class JadxSettings extends JadxCLIArgs {
private boolean codeAreaLineWrap = false;
private int srhResourceSkipSize = 1000;
private String srhResourceFileExt = ".xml|.html|.js|.json|.txt";
private boolean useAutoSearch = true;
private boolean keepCommonDialogOpen = false;
private boolean smaliAreaShowBytecode = false;
private LineNumbersMode lineNumbersMode = LineNumbersMode.AUTO;
Expand Down Expand Up @@ -540,6 +541,15 @@ public void setSrhResourceFileExt(String all) {
srhResourceFileExt = all.trim();
}

public boolean isUseAutoSearch() {
return useAutoSearch;
}

public void setUseAutoSearch(boolean useAutoSearch) {
this.useAutoSearch = useAutoSearch;
partialSync(settings -> settings.useAutoSearch = useAutoSearch);
}

public void setKeepCommonDialogOpen(boolean yes) {
keepCommonDialogOpen = yes;
}
Expand Down
20 changes: 13 additions & 7 deletions jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

Expand Down Expand Up @@ -250,15 +251,13 @@ public void actionPerformed(ActionEvent e) {

JScrollPane scroll = new JScrollPane(resultsTable, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_AS_NEEDED);

resultsInfoLabel = new JLabel("");
resultsInfoLabel.setFont(mainWindow.getSettings().getFont());

JPanel resultsActionsPanel = new JPanel();
resultsActionsPanel.setLayout(new BoxLayout(resultsActionsPanel, BoxLayout.LINE_AXIS));
resultsActionsPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0));
addCustomResultsActions(resultsActionsPanel);
resultsInfoLabel = new JLabel("");
resultsInfoLabel.setFont(mainWindow.getSettings().getFont());
resultsActionsPanel.add(Box.createRigidArea(new Dimension(20, 0)));
resultsActionsPanel.add(resultsInfoLabel);
resultsActionsPanel.add(Box.createHorizontalGlue());
addResultsActions(resultsActionsPanel);

JPanel resultsPanel = new JPanel();
resultsPanel.setLayout(new BoxLayout(resultsPanel, BoxLayout.PAGE_AXIS));
Expand All @@ -268,7 +267,10 @@ public void actionPerformed(ActionEvent e) {
return resultsPanel;
}

protected void addCustomResultsActions(JPanel actionsPanel) {
protected void addResultsActions(JPanel resultsActionsPanel) {
resultsActionsPanel.add(Box.createRigidArea(new Dimension(20, 0)));
resultsActionsPanel.add(resultsInfoLabel);
resultsActionsPanel.add(Box.createHorizontalGlue());
}

protected void updateProgressLabel(boolean complete) {
Expand Down Expand Up @@ -365,6 +367,10 @@ public void clear() {
rows.clear();
}

public void sort() {
Collections.sort(rows);
}

public boolean isAddDescColumn() {
return addDescColumn;
}
Expand Down
115 changes: 72 additions & 43 deletions jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
Expand Down Expand Up @@ -58,7 +56,7 @@
import jadx.gui.utils.TextStandardActions;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.layout.WrapLayout;
import jadx.gui.utils.ui.DocumentUpdateListener;
import jadx.gui.utils.rx.RxUtils;

import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.ACTIVE_TAB;
import static jadx.gui.ui.dialog.SearchDialog.SearchOptions.CLASS;
Expand Down Expand Up @@ -119,6 +117,8 @@ public enum SearchOptions {
private transient @Nullable SearchTask searchTask;
private transient JButton loadAllButton;
private transient JButton loadMoreButton;
private transient JButton stopBtn;
private transient JButton sortBtn;

private transient Disposable searchDisposable;
private transient SearchEventEmitter searchEmitter;
Expand All @@ -142,7 +142,7 @@ private SearchDialog(MainWindow mainWindow, SearchPreset preset, Set<SearchOptio

loadWindowPos();
initUI();
searchFieldSubscribe();
initSearchEvents();
registerInitOnOpen();
registerActiveTabListener();
}
Expand Down Expand Up @@ -208,9 +208,30 @@ private void initUI() {
searchField.setAlignmentX(LEFT_ALIGNMENT);
TextStandardActions.attach(searchField);

boolean autoSearch = mainWindow.getSettings().isUseAutoSearch();
JButton searchBtn = new JButton(NLS.str("search_dialog.search_button"));
searchBtn.setVisible(!autoSearch);
searchBtn.addActionListener(ev -> searchEmitter.emitSearch());

JCheckBox autoSearchCB = new JCheckBox(NLS.str("search_dialog.auto_search"));
autoSearchCB.setSelected(autoSearch);
autoSearchCB.addActionListener(ev -> {
boolean newValue = autoSearchCB.isSelected();
mainWindow.getSettings().setUseAutoSearch(newValue);
searchBtn.setVisible(!newValue);
initSearchEvents();
if (newValue) {
searchEmitter.emitSearch();
}
});

JPanel searchLinePanel = new JPanel();
searchLinePanel.setLayout(new BoxLayout(searchLinePanel, BoxLayout.LINE_AXIS));
searchLinePanel.add(searchField);
searchLinePanel.add(Box.createRigidArea(new Dimension(5, 0)));
searchLinePanel.add(searchBtn);
searchLinePanel.add(Box.createRigidArea(new Dimension(5, 0)));
searchLinePanel.add(autoSearchCB);
searchLinePanel.setAlignmentX(LEFT_ALIGNMENT);

JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
Expand Down Expand Up @@ -261,22 +282,11 @@ private void initUI() {
contentPanel.add(buttonPane, BorderLayout.PAGE_END);
getContentPane().add(contentPanel);

searchField.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
if (resultsModel.getRowCount() != 0) {
resultsTable.setRowSelectionInterval(0, 0);
}
resultsTable.requestFocus();
}
}
});
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}

protected void addCustomResultsActions(JPanel resultsActionsPanel) {
protected void addResultsActions(JPanel resultsActionsPanel) {
loadAllButton = new JButton(NLS.str("search_dialog.load_all"));
loadAllButton.addActionListener(e -> loadMoreResults(true));
loadAllButton.setEnabled(false);
Expand All @@ -285,9 +295,29 @@ protected void addCustomResultsActions(JPanel resultsActionsPanel) {
loadMoreButton.addActionListener(e -> loadMoreResults(false));
loadMoreButton.setEnabled(false);

stopBtn = new JButton(NLS.str("search_dialog.stop"));
stopBtn.addActionListener(e -> pauseSearch());
stopBtn.setEnabled(false);

sortBtn = new JButton(NLS.str("search_dialog.sort_results"));
sortBtn.addActionListener(e -> {
synchronized (pendingResults) {
resultsModel.sort();
resultsTable.updateTable();
}
});
sortBtn.setEnabled(false);

resultsActionsPanel.add(loadAllButton);
resultsActionsPanel.add(Box.createRigidArea(new Dimension(10, 0)));
resultsActionsPanel.add(loadMoreButton);
resultsActionsPanel.add(Box.createRigidArea(new Dimension(10, 0)));
resultsActionsPanel.add(stopBtn);
resultsActionsPanel.add(Box.createRigidArea(new Dimension(10, 0)));
resultsActionsPanel.add(stopBtn);
super.addResultsActions(resultsActionsPanel);
resultsActionsPanel.add(Box.createRigidArea(new Dimension(10, 0)));
resultsActionsPanel.add(sortBtn);
}

private class SearchEventEmitter {
Expand All @@ -311,10 +341,19 @@ public synchronized void emitSearch() {
}
}

private void searchFieldSubscribe() {
private void initSearchEvents() {
if (searchDisposable != null) {
searchDisposable.dispose();
searchDisposable = null;
}
searchEmitter = new SearchEventEmitter();
Flowable<String> textChanges = onTextFieldChanges(searchField);
Flowable<String> searchEvents = Flowable.merge(textChanges, searchEmitter.getFlowable());
Flowable<String> searchEvents;
if (mainWindow.getSettings().isUseAutoSearch()) {
searchEvents = Flowable.merge(RxUtils.textFieldChanges(searchField),
RxUtils.textFieldEnterPress(searchField), searchEmitter.getFlowable());
} else {
searchEvents = Flowable.merge(RxUtils.textFieldEnterPress(searchField), searchEmitter.getFlowable());
}
searchDisposable = searchEvents
.debounce(50, TimeUnit.MILLISECONDS)
.observeOn(Schedulers.from(searchBackgroundExecutor))
Expand Down Expand Up @@ -424,6 +463,15 @@ private boolean buildSearch(SearchTask newSearchTask, String text, SearchSetting
return true;
}

private void pauseSearch() {
stopBtn.setEnabled(false);
searchBackgroundExecutor.execute(() -> {
if (searchTask != null) {
searchTask.cancel();
}
});
}

private void stopSearchTask() {
UiUtils.notUiThreadGuard();
if (searchTask != null) {
Expand Down Expand Up @@ -462,6 +510,9 @@ private void resetSearch() {
}

private void prepareForSearch() {
UiUtils.uiThreadGuard();
stopBtn.setEnabled(true);
sortBtn.setEnabled(false);
showSearchState();
progressStartCommon();
}
Expand Down Expand Up @@ -502,40 +553,18 @@ private void searchFinished(ITaskInfo status, Boolean complete) {
LOG.debug("Search complete: {}, complete: {}", status, complete);
loadAllButton.setEnabled(!complete);
loadMoreButton.setEnabled(!complete);
stopBtn.setEnabled(false);
progressFinishedCommon();
updateTable();
updateProgressLabel(complete);
sortBtn.setEnabled(resultsModel.getRowCount() != 0);
}

private void unloadTempData() {
mainWindow.getWrapper().unloadClasses();
System.gc();
}

private static Flowable<String> onTextFieldChanges(final JTextField textField) {
return Flowable.<String>create(emitter -> {
DocumentUpdateListener listener = new DocumentUpdateListener(
ev -> emitter.onNext(textField.getText()));

textField.getDocument().addDocumentListener(listener);
emitter.setDisposable(new Disposable() {
private boolean disposed = false;

@Override
public void dispose() {
textField.getDocument().removeDocumentListener(listener);
disposed = true;
}

@Override
public boolean isDisposed() {
return disposed;
}
});
}, BackpressureStrategy.LATEST)
.distinctUntilChanged();
}

private JCheckBox makeOptionsCheckBox(String name, final SearchOptions opt) {
final JCheckBox chBox = new JCheckBox(name);
chBox.setAlignmentX(LEFT_ALIGNMENT);
Expand Down
29 changes: 29 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/utils/rx/CustomDisposable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package jadx.gui.utils.rx;

import java.util.concurrent.atomic.AtomicBoolean;

import io.reactivex.disposables.Disposable;

public class CustomDisposable implements Disposable {

private final AtomicBoolean disposed = new AtomicBoolean(false);
private final Runnable disposeTask;

public CustomDisposable(Runnable disposeTask) {
this.disposeTask = disposeTask;
}

@Override
public void dispose() {
try {
disposeTask.run();
} finally {
disposed.set(true);
}
}

@Override
public boolean isDisposed() {
return disposed.get();
}
}
43 changes: 43 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/utils/rx/RxUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package jadx.gui.utils.rx;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JTextField;
import javax.swing.event.DocumentListener;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.FlowableOnSubscribe;

import jadx.gui.utils.ui.DocumentUpdateListener;

public class RxUtils {

public static Flowable<String> textFieldChanges(final JTextField textField) {
FlowableOnSubscribe<String> source = emitter -> {
DocumentListener listener = new DocumentUpdateListener(ev -> emitter.onNext(textField.getText()));
textField.getDocument().addDocumentListener(listener);
emitter.setDisposable(new CustomDisposable(() -> textField.getDocument().removeDocumentListener(listener)));
};
return Flowable.create(source, BackpressureStrategy.LATEST).distinctUntilChanged();
}

public static Flowable<String> textFieldEnterPress(final JTextField textField) {
FlowableOnSubscribe<String> source = emitter -> {
KeyListener keyListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent ev) {
if (ev.getKeyCode() == KeyEvent.VK_ENTER) {
emitter.onNext(textField.getText());
}
}
};
textField.addKeyListener(keyListener);
emitter.setDisposable(new CustomDisposable(() -> textField.removeKeyListener(keyListener)));
};
return Flowable.create(source, BackpressureStrategy.LATEST).distinctUntilChanged();
}

}
4 changes: 4 additions & 0 deletions jadx-gui/src/main/resources/i18n/Messages_de_DE.properties
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ file_dialog.load_dir_confirm=Alle Dateien aus dem Verzeichnis laden?
search_dialog.open=Öffnen
search_dialog.cancel=Beenden
search_dialog.open_by_name=Nach Text suchen:
#search_dialog.search_button=Search
#search_dialog.auto_search=Auto search
search_dialog.search_in=Suche in Definitionen von:
search_dialog.class=Klassen
search_dialog.method=Methoden
Expand All @@ -105,10 +107,12 @@ search_dialog.options=Suchoptionen:
search_dialog.ignorecase=Groß/Kleinschreibung ignorieren
search_dialog.load_more=Mehr laden
search_dialog.load_all=Alle laden
#search_dialog.stop=Stop
search_dialog.results_incomplete=%d+ gefunden
search_dialog.results_complete=%d gefunden (komplett)
search_dialog.col_node=Knoten
search_dialog.col_code=Code
#search_dialog.sort_results=Sort results
search_dialog.regex=Regex
search_dialog.active_tab=Nur aktiver Tab
search_dialog.comments=Kommentare
Expand Down
Loading

0 comments on commit 11d0450

Please sign in to comment.