From 11d04508f7942941b28d0ec4b029acd3ce39543d Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 5 Aug 2022 20:09:33 +0100 Subject: [PATCH] feat(gui): add manual search, stop and sort actions to search dialog (#1600) --- .../java/jadx/gui/settings/JadxSettings.java | 10 ++ .../gui/ui/dialog/CommonSearchDialog.java | 20 +-- .../java/jadx/gui/ui/dialog/SearchDialog.java | 115 +++++++++++------- .../jadx/gui/utils/rx/CustomDisposable.java | 29 +++++ .../main/java/jadx/gui/utils/rx/RxUtils.java | 43 +++++++ .../resources/i18n/Messages_de_DE.properties | 4 + .../resources/i18n/Messages_en_US.properties | 6 +- .../resources/i18n/Messages_es_ES.properties | 4 + .../resources/i18n/Messages_ko_KR.properties | 4 + .../resources/i18n/Messages_pt_BR.properties | 4 + .../resources/i18n/Messages_zh_CN.properties | 4 + .../resources/i18n/Messages_zh_TW.properties | 4 + 12 files changed, 196 insertions(+), 51 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/utils/rx/CustomDisposable.java create mode 100644 jadx-gui/src/main/java/jadx/gui/utils/rx/RxUtils.java diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 4135ba9b..04c9514a 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -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; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java index a9953569..06ed83dd 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java @@ -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; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java index 377c95f4..c927aea5 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/SearchDialog.java @@ -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 textChanges = onTextFieldChanges(searchField); - Flowable searchEvents = Flowable.merge(textChanges, searchEmitter.getFlowable()); + Flowable 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 onTextFieldChanges(final JTextField textField) { - return Flowable.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); diff --git a/jadx-gui/src/main/java/jadx/gui/utils/rx/CustomDisposable.java b/jadx-gui/src/main/java/jadx/gui/utils/rx/CustomDisposable.java new file mode 100644 index 00000000..f63de60b --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/rx/CustomDisposable.java @@ -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(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/rx/RxUtils.java b/jadx-gui/src/main/java/jadx/gui/utils/rx/RxUtils.java new file mode 100644 index 00000000..c437f8ca --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/rx/RxUtils.java @@ -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 textFieldChanges(final JTextField textField) { + FlowableOnSubscribe 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 textFieldEnterPress(final JTextField textField) { + FlowableOnSubscribe 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(); + } + +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 89c3e418..db6a43c2 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 491006c7..80a61221 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index 9af5618f..6179ae9e 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 9ffb4372..223b5b6b 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -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=주석 diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index 14e5ba6b..a189e6d4 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -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=Nó 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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 82894340..22b68f97 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -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=注释 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index d1ee8bd0..1643daa2 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -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=註解 -- GitLab