mirror of
https://github.com/mitb-archive/filebot
synced 2024-08-13 17:03:45 -04:00
379 lines
11 KiB
Java
379 lines
11 KiB
Java
package net.filebot.ui;
|
|
|
|
import static javax.swing.ScrollPaneConstants.*;
|
|
import static net.filebot.Logging.*;
|
|
import static net.filebot.util.ui.SwingUI.*;
|
|
|
|
import java.awt.Dimension;
|
|
import java.awt.Window;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.KeyEvent;
|
|
import java.beans.PropertyChangeEvent;
|
|
import java.beans.PropertyChangeListener;
|
|
import java.net.URI;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.TreeSet;
|
|
import java.util.logging.Level;
|
|
|
|
import javax.swing.AbstractAction;
|
|
import javax.swing.BorderFactory;
|
|
import javax.swing.Icon;
|
|
import javax.swing.JButton;
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JPanel;
|
|
import javax.swing.JScrollPane;
|
|
import javax.swing.JTabbedPane;
|
|
import javax.swing.KeyStroke;
|
|
import javax.swing.SwingUtilities;
|
|
import javax.swing.SwingWorker;
|
|
import javax.swing.event.ChangeEvent;
|
|
import javax.swing.event.ChangeListener;
|
|
|
|
import ca.odell.glazedlists.BasicEventList;
|
|
import ca.odell.glazedlists.matchers.TextMatcherEditor;
|
|
import ca.odell.glazedlists.swing.AutoCompleteSupport;
|
|
import net.filebot.ResourceManager;
|
|
import net.filebot.Settings;
|
|
import net.filebot.util.ExceptionUtilities;
|
|
import net.filebot.util.ui.LabelProvider;
|
|
import net.filebot.util.ui.SelectButton;
|
|
import net.filebot.web.SearchResult;
|
|
import net.miginfocom.swing.MigLayout;
|
|
|
|
public abstract class AbstractSearchPanel<S, E> extends JComponent {
|
|
|
|
protected final JPanel tabbedPaneGroup = new JPanel(new MigLayout("nogrid, fill, insets 0", "align center", "[fill]8px[pref!]4px"));
|
|
|
|
protected final JTabbedPane tabbedPane = new JTabbedPane();
|
|
|
|
protected final HistoryPanel historyPanel = new HistoryPanel();
|
|
|
|
protected final SelectButtonTextField<S> searchTextField = new SelectButtonTextField<S>();
|
|
|
|
protected final BasicEventList<String> searchHistory = new BasicEventList<String>(100000);
|
|
|
|
public AbstractSearchPanel() {
|
|
historyPanel.setColumnHeader(2, "Duration");
|
|
|
|
JScrollPane historyScrollPane = new JScrollPane(historyPanel, VERTICAL_SCROLLBAR_AS_NEEDED, HORIZONTAL_SCROLLBAR_NEVER);
|
|
historyScrollPane.setBorder(BorderFactory.createEmptyBorder());
|
|
|
|
tabbedPane.addTab("History", ResourceManager.getIcon("action.find"), historyScrollPane);
|
|
|
|
tabbedPaneGroup.setBorder(BorderFactory.createTitledBorder("Search Results"));
|
|
tabbedPaneGroup.add(tabbedPane, "grow, wrap");
|
|
setLayout(new MigLayout("nogrid, novisualpadding, fill, insets 10px 10px 15px 10px", "align 45%", "[pref!]10px[fill]"));
|
|
|
|
add(searchTextField);
|
|
add(new JButton(searchAction), "gap 16px, h 2+pref!, id search, sgy button");
|
|
add(tabbedPaneGroup, "newline, grow");
|
|
|
|
searchTextField.getEditor().setAction(searchAction);
|
|
searchTextField.getSelectButton().setModel(Arrays.asList(getSearchEngines()));
|
|
searchTextField.getSelectButton().setLabelProvider(getSearchEngineLabelProvider());
|
|
|
|
searchTextField.getSelectButton().addPropertyChangeListener(SelectButton.SELECTED_VALUE, new PropertyChangeListener() {
|
|
|
|
@Override
|
|
public void propertyChange(PropertyChangeEvent evt) {
|
|
new SwingWorker<Collection<String>, Void>() {
|
|
|
|
private final S engine = searchTextField.getSelectButton().getSelectedValue();
|
|
|
|
@Override
|
|
protected Collection<String> doInBackground() throws Exception {
|
|
TreeSet<String> set = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
|
set.addAll(getHistory(engine));
|
|
return set;
|
|
}
|
|
|
|
@Override
|
|
protected void done() {
|
|
if (engine == searchTextField.getSelectButton().getSelectedValue()) {
|
|
try {
|
|
searchHistory.clear();
|
|
searchHistory.addAll(get());
|
|
} catch (Exception e) {
|
|
debug.log(Level.WARNING, e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
};
|
|
}.execute();
|
|
}
|
|
});
|
|
|
|
try {
|
|
// restore selected subtitle client
|
|
searchTextField.getSelectButton().setSelectedIndex(Integer.parseInt(getSettings().get("engine.selected", "0")));
|
|
} catch (Exception e) {
|
|
// log and ignore
|
|
debug.log(Level.WARNING, e.getMessage(), e);
|
|
}
|
|
|
|
// save selected client on change
|
|
searchTextField.getSelectButton().getSelectionModel().addChangeListener(new ChangeListener() {
|
|
|
|
@Override
|
|
public void stateChanged(ChangeEvent e) {
|
|
getSettings().put("engine.selected", Integer.toString(searchTextField.getSelectButton().getSelectedIndex()));
|
|
}
|
|
});
|
|
|
|
// high-performance auto-completion
|
|
AutoCompleteSupport<String> acs = AutoCompleteSupport.install(searchTextField.getEditor(), searchHistory);
|
|
acs.setTextMatchingStrategy(TextMatcherEditor.IDENTICAL_STRATEGY);
|
|
acs.setFilterMode(TextMatcherEditor.CONTAINS);
|
|
acs.setCorrectsCase(true);
|
|
acs.setStrict(false);
|
|
|
|
installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), searchAction);
|
|
}
|
|
|
|
protected abstract Collection<String> getHistory(S engine) throws Exception;
|
|
|
|
protected abstract S[] getSearchEngines();
|
|
|
|
protected abstract LabelProvider<S> getSearchEngineLabelProvider();
|
|
|
|
protected abstract Settings getSettings();
|
|
|
|
protected abstract RequestProcessor<?, E> createRequestProcessor();
|
|
|
|
private void search(RequestProcessor<?, E> requestProcessor) {
|
|
FileBotTab<?> tab = requestProcessor.tab;
|
|
|
|
tab.setTitle(requestProcessor.getTitle());
|
|
tab.setLoading(true);
|
|
tab.setIcon(requestProcessor.getIcon());
|
|
|
|
tab.addTo(tabbedPane);
|
|
|
|
tabbedPane.setSelectedComponent(tab);
|
|
|
|
// search in background
|
|
new SearchTask(requestProcessor).execute();
|
|
}
|
|
|
|
private final AbstractAction searchAction = new AbstractAction("Find", ResourceManager.getIcon("action.find")) {
|
|
|
|
@Override
|
|
public void actionPerformed(ActionEvent e) {
|
|
if (e.getActionCommand() == null || searchTextField.getText().trim().isEmpty()) {
|
|
// command triggered by auto-completion
|
|
return;
|
|
}
|
|
|
|
RequestProcessor<?, E> request = createRequestProcessor();
|
|
if (request != null) {
|
|
search(request);
|
|
}
|
|
}
|
|
};
|
|
|
|
private class SearchTask extends SwingWorker<Collection<? extends SearchResult>, Void> {
|
|
|
|
private final RequestProcessor<?, E> requestProcessor;
|
|
|
|
public SearchTask(RequestProcessor<?, E> requestProcessor) {
|
|
this.requestProcessor = requestProcessor;
|
|
}
|
|
|
|
@Override
|
|
protected Collection<? extends SearchResult> doInBackground() throws Exception {
|
|
long start = System.currentTimeMillis();
|
|
|
|
try {
|
|
return requestProcessor.search();
|
|
} finally {
|
|
requestProcessor.duration += (System.currentTimeMillis() - start);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void done() {
|
|
FileBotTab<?> tab = requestProcessor.tab;
|
|
|
|
// tab might have been closed
|
|
if (tab.isClosed())
|
|
return;
|
|
|
|
try {
|
|
Collection<? extends SearchResult> results = get();
|
|
|
|
SearchResult selectedSearchResult = null;
|
|
|
|
switch (results.size()) {
|
|
case 0:
|
|
log.log(Level.WARNING, String.format("'%s' has not been found.", requestProcessor.request.getSearchText()));
|
|
break;
|
|
case 1:
|
|
selectedSearchResult = results.iterator().next();
|
|
break;
|
|
default:
|
|
selectedSearchResult = requestProcessor.selectSearchResult(results, SwingUtilities.getWindowAncestor(AbstractSearchPanel.this));
|
|
break;
|
|
}
|
|
|
|
if (selectedSearchResult == null) {
|
|
tab.close();
|
|
return;
|
|
}
|
|
|
|
// set search result
|
|
requestProcessor.setSearchResult(selectedSearchResult);
|
|
|
|
tab.setTitle(requestProcessor.getTitle());
|
|
|
|
// fetch elements of the selected search result
|
|
new FetchTask(requestProcessor).execute();
|
|
} catch (Exception e) {
|
|
tab.close();
|
|
log.log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
private class FetchTask extends SwingWorker<Collection<E>, Void> {
|
|
|
|
private final RequestProcessor<?, E> requestProcessor;
|
|
|
|
public FetchTask(RequestProcessor<?, E> requestProcessor) {
|
|
this.requestProcessor = requestProcessor;
|
|
}
|
|
|
|
@Override
|
|
protected final Collection<E> doInBackground() throws Exception {
|
|
long start = System.currentTimeMillis();
|
|
|
|
try {
|
|
return requestProcessor.fetch();
|
|
} finally {
|
|
requestProcessor.duration += (System.currentTimeMillis() - start);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void done() {
|
|
FileBotTab<?> tab = requestProcessor.tab;
|
|
|
|
if (tab.isClosed())
|
|
return;
|
|
|
|
try {
|
|
// check if an exception occurred
|
|
Collection<E> elements = get();
|
|
|
|
requestProcessor.process(elements);
|
|
|
|
String title = requestProcessor.getTitle();
|
|
Icon icon = requestProcessor.getIcon();
|
|
String statusMessage = requestProcessor.getStatusMessage(elements);
|
|
|
|
historyPanel.add(title, requestProcessor.getLink(), icon, statusMessage, String.format("%,d ms", requestProcessor.getDuration()));
|
|
|
|
// close tab if no elements were fetched
|
|
if (get().size() <= 0) {
|
|
log.warning(statusMessage);
|
|
tab.close();
|
|
}
|
|
} catch (Exception e) {
|
|
tab.close();
|
|
log.log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
|
} finally {
|
|
tab.setLoading(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected static class Request {
|
|
|
|
private final String searchText;
|
|
|
|
public Request(String searchText) {
|
|
this.searchText = searchText;
|
|
}
|
|
|
|
public String getSearchText() {
|
|
return searchText;
|
|
}
|
|
|
|
}
|
|
|
|
protected abstract static class RequestProcessor<R extends Request, E> {
|
|
|
|
protected final R request;
|
|
|
|
private FileBotTab<JComponent> tab;
|
|
|
|
private SearchResult searchResult;
|
|
|
|
private long duration = 0;
|
|
|
|
public RequestProcessor(R request, JComponent component) {
|
|
this.request = request;
|
|
this.tab = new FileBotTab<JComponent>(component);
|
|
}
|
|
|
|
public abstract Collection<? extends SearchResult> search() throws Exception;
|
|
|
|
public abstract Collection<E> fetch() throws Exception;
|
|
|
|
public abstract void process(Collection<E> elements);
|
|
|
|
public abstract URI getLink();
|
|
|
|
public JComponent getComponent() {
|
|
return tab.getComponent();
|
|
}
|
|
|
|
public SearchResult getSearchResult() {
|
|
return searchResult;
|
|
}
|
|
|
|
public void setSearchResult(SearchResult searchResult) {
|
|
this.searchResult = searchResult;
|
|
}
|
|
|
|
public String getStatusMessage(Collection<E> result) {
|
|
return String.format("%d elements found", result.size());
|
|
}
|
|
|
|
public String getTitle() {
|
|
if (searchResult != null)
|
|
return searchResult.getName();
|
|
|
|
return request.getSearchText();
|
|
}
|
|
|
|
public Icon getIcon() {
|
|
return null;
|
|
}
|
|
|
|
protected SearchResult selectSearchResult(Collection<? extends SearchResult> searchResults, Window window) throws Exception {
|
|
// multiple results have been found, user must select one
|
|
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(window, searchResults);
|
|
configureSelectDialog(selectDialog);
|
|
|
|
selectDialog.setVisible(true);
|
|
|
|
// selected value or null if the dialog was canceled by the user
|
|
return selectDialog.getSelectedValue();
|
|
}
|
|
|
|
protected void configureSelectDialog(SelectDialog<SearchResult> selectDialog) {
|
|
selectDialog.setLocation(getOffsetLocation(selectDialog.getOwner()));
|
|
selectDialog.setIconImage(getImage(getIcon()));
|
|
selectDialog.setMinimumSize(new Dimension(250, 150));
|
|
selectDialog.pack();
|
|
}
|
|
|
|
public long getDuration() {
|
|
return duration;
|
|
}
|
|
}
|
|
|
|
}
|