未验证 提交 11d04508 编写于 作者: S Skylot

feat(gui): add manual search, stop and sort actions to search dialog (#1600)

上级 e641b773
......@@ -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;
......@@ -540,6 +541,15 @@ public class JadxSettings extends JadxCLIArgs {
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;
}
......
......@@ -14,6 +14,7 @@ import java.awt.event.WindowAdapter;
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;
......@@ -250,15 +251,13 @@ public abstract class CommonSearchDialog extends JFrame {
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));
......@@ -268,7 +267,10 @@ public abstract class CommonSearchDialog extends JFrame {
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) {
......@@ -365,6 +367,10 @@ public abstract class CommonSearchDialog extends JFrame {
rows.clear();
}
public void sort() {
Collections.sort(rows);
}
public boolean isAddDescColumn() {
return addDescColumn;
}
......
......@@ -4,8 +4,6 @@ import java.awt.BorderLayout;
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;
......@@ -58,7 +56,7 @@ import jadx.gui.utils.NLS;
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;
......@@ -119,6 +117,8 @@ public class SearchDialog extends CommonSearchDialog {
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;
......@@ -142,7 +142,7 @@ public class SearchDialog extends CommonSearchDialog {
loadWindowPos();
initUI();
searchFieldSubscribe();
initSearchEvents();
registerInitOnOpen();
registerActiveTabListener();
}
......@@ -208,9 +208,30 @@ public class SearchDialog extends CommonSearchDialog {
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"));
......@@ -261,22 +282,11 @@ public class SearchDialog extends CommonSearchDialog {
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);
......@@ -285,9 +295,29 @@ public class SearchDialog extends CommonSearchDialog {
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 {
......@@ -311,10 +341,19 @@ public class SearchDialog extends CommonSearchDialog {
}
}
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))
......@@ -424,6 +463,15 @@ public class SearchDialog extends CommonSearchDialog {
return true;
}
private void pauseSearch() {
stopBtn.setEnabled(false);
searchBackgroundExecutor.execute(() -> {
if (searchTask != null) {
searchTask.cancel();
}
});
}
private void stopSearchTask() {
UiUtils.notUiThreadGuard();
if (searchTask != null) {
......@@ -462,6 +510,9 @@ public class SearchDialog extends CommonSearchDialog {
}
private void prepareForSearch() {
UiUtils.uiThreadGuard();
stopBtn.setEnabled(true);
sortBtn.setEnabled(false);
showSearchState();
progressStartCommon();
}
......@@ -502,9 +553,11 @@ public class SearchDialog extends CommonSearchDialog {
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() {
......@@ -512,30 +565,6 @@ public class SearchDialog extends CommonSearchDialog {
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);
......
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();
}
}
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();
}
}
......@@ -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
......@@ -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
......
......@@ -96,19 +96,23 @@ file_dialog.load_dir_confirm=Load all files from directory?
search_dialog.open=Open
search_dialog.cancel=Cancel
search_dialog.open_by_name=Search for text:
search_dialog.search_button=Search
search_dialog.auto_search=Auto search
search_dialog.search_in=Search definitions of:
search_dialog.class=Class
search_dialog.method=Method
search_dialog.field=Field
search_dialog.code=Code
search_dialog.options=Search options:
search_dialog.ignorecase=Case insensitive
search_dialog.ignorecase=Case-insensitive
search_dialog.load_more=Load more
search_dialog.load_all=Load all
search_dialog.stop=Stop
search_dialog.results_incomplete=Found %d+
search_dialog.results_complete=Found %d (complete)
search_dialog.col_node=Node
search_dialog.col_code=Code
search_dialog.sort_results=Sort results
search_dialog.regex=Regex
search_dialog.active_tab=Active tab only
search_dialog.comments=Comments
......
......@@ -96,6 +96,8 @@ nav.forward=Adelante
search_dialog.open=Abrir
search_dialog.cancel=Cancelar
search_dialog.open_by_name=Buscar texto:
#search_dialog.search_button=Search
#search_dialog.auto_search=Auto search
search_dialog.search_in=Buscar definiciones de:
search_dialog.class=Clase
search_dialog.method=Método
......@@ -105,10 +107,12 @@ search_dialog.options=Opciones de búsqueda:
search_dialog.ignorecase=Ignorar minúsculas/mayúsculas
#search_dialog.load_more=Load more
#search_dialog.load_all=Load all
#search_dialog.stop=Stop
#search_dialog.results_incomplete=Found %d+
#search_dialog.results_complete=Found %d (complete)
search_dialog.col_node=Nodo
search_dialog.col_code=Código
#search_dialog.sort_results=Sort results
search_dialog.regex=Regex
#search_dialog.active_tab=Active tab only
#search_dialog.comments=Comments
......
......@@ -96,6 +96,8 @@ common_dialog.remove=삭제
search_dialog.open=열기
search_dialog.cancel=취소
search_dialog.open_by_name=텍스트 검색 :
#search_dialog.search_button=Search
#search_dialog.auto_search=Auto search
search_dialog.search_in=정의 검색 :
search_dialog.class=클래스
search_dialog.method=메소드
......@@ -105,10 +107,12 @@ search_dialog.options=옵션 검색:
search_dialog.ignorecase=대소문자 구분 안함
#search_dialog.load_more=Load more
#search_dialog.load_all=Load all
#search_dialog.stop=Stop
#search_dialog.results_incomplete=Found %d+
#search_dialog.results_complete=Found %d (complete)
search_dialog.col_node=노드
search_dialog.col_code=코드
#search_dialog.sort_results=Sort results
search_dialog.regex=정규식
search_dialog.active_tab=열려 있는 탭에서만 검색
search_dialog.comments=주석
......
......@@ -96,6 +96,8 @@ file_dialog.load_dir_confirm=Carregar todos arquivos do diretório?
search_dialog.open=Abrir
search_dialog.cancel=Cancelar
search_dialog.open_by_name=Buscar por text:
#search_dialog.search_button=Search
#search_dialog.auto_search=Auto search
search_dialog.search_in=Buscar definições de:
search_dialog.class=Classe
search_dialog.method=Método
......@@ -105,10 +107,12 @@ search_dialog.options=Opções de busca:
search_dialog.ignorecase=Não diferencia maiúsculas de minúsculas
search_dialog.load_more=Carregar mais
search_dialog.load_all=Carregar todas
#search_dialog.stop=Stop
search_dialog.results_incomplete=Encontradas %d+
search_dialog.results_complete=Encontradas %d (complete)
search_dialog.col_node=
search_dialog.col_code=Código
#search_dialog.sort_results=Sort results
search_dialog.regex=Regex
search_dialog.active_tab=Apenas abas ativas
search_dialog.comments=Comentários
......
......@@ -96,6 +96,8 @@ file_dialog.load_dir_confirm=从目录中加载所有文件?
search_dialog.open=转到
search_dialog.cancel=取消
search_dialog.open_by_name=搜索文本:
#search_dialog.search_button=Search
#search_dialog.auto_search=Auto search
search_dialog.search_in=在以下位置搜索:
search_dialog.class=类名
search_dialog.method=方法名
......@@ -105,10 +107,12 @@ search_dialog.options=搜索选项:
search_dialog.ignorecase=忽略大小写
search_dialog.load_more=加载更多
search_dialog.load_all=加载所有
#search_dialog.stop=Stop
search_dialog.results_incomplete=已找到 %d+
search_dialog.results_complete=全部找到 %d
search_dialog.col_node=节点
search_dialog.col_code=代码
#search_dialog.sort_results=Sort results
search_dialog.regex=正则表达式
search_dialog.active_tab=只在当前页搜索
search_dialog.comments=注释
......
......@@ -96,6 +96,8 @@ file_dialog.load_dir_confirm=要載入目錄中的所有檔案嗎?
search_dialog.open=開啟
search_dialog.cancel=取消
search_dialog.open_by_name=搜尋文字:
#search_dialog.search_button=Search
#search_dialog.auto_search=Auto search
search_dialog.search_in=搜尋定義:
search_dialog.class=類別
search_dialog.method=方法
......@@ -105,10 +107,12 @@ search_dialog.options=搜尋選項:
search_dialog.ignorecase=不區分大小寫
search_dialog.load_more=載入更多
search_dialog.load_all=載入全部
#search_dialog.stop=Stop
search_dialog.results_incomplete=找到 %d+
search_dialog.results_complete=找到 %d (完整)
search_dialog.col_node=
search_dialog.col_code=程式碼
#search_dialog.sort_results=Sort results
search_dialog.regex=Regex
search_dialog.active_tab=僅使用中分頁
search_dialog.comments=註解
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册