* AutoCompleteSupport now works with the full movie/series/anime index in the back

* update movie/series/anime index files and exclude bad entries
This commit is contained in:
Reinhard Pointner 2014-01-09 18:26:25 +00:00
parent 94ceccf966
commit f466546788
9 changed files with 194 additions and 195 deletions

View File

@ -76,6 +76,7 @@ def getNamePermutations(names) {
res = res.findResults{ fn(it) }
}
out += res
out = out.findAll{ it.length() >= 2 && !(it =~ /^[a-z]/) && it =~ /^[.\p{L}\p{Digit}]/ } // MUST START WITH UNICODE LETTER
out = out.unique{ it.toLowerCase().normalizePunctuation() }.findAll{ it.length() > 0 }.toList()
out = out.size() <= 4 ? out : out.subList(0, 4)
return out

View File

@ -12,6 +12,8 @@ import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sourceforge.filebot.Language;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import org.kohsuke.args4j.spi.ExplicitBooleanOptionHandler;
@ -169,6 +171,10 @@ public class ArgumentBean {
return new Locale(lang);
}
public Language getLanguage() {
return Language.findLanguage(lang);
}
public Level getLogLevel() {
return Level.parse(log.toUpperCase());
}

View File

@ -8,10 +8,11 @@ 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.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -32,13 +33,11 @@ import javax.swing.event.ChangeListener;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.similarity.SeriesNameMatcher;
import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.tuned.ExceptionUtilities;
import net.sourceforge.tuned.ListChangeSynchronizer;
import net.sourceforge.tuned.ui.LabelProvider;
import net.sourceforge.tuned.ui.SelectButton;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.matchers.TextMatcherEditor;
import ca.odell.glazedlists.swing.AutoCompleteSupport;
@ -52,7 +51,7 @@ public abstract class AbstractSearchPanel<S, E> extends JComponent {
protected final SelectButtonTextField<S> searchTextField = new SelectButtonTextField<S>();
protected final EventList<String> searchHistory = createSearchHistory();
protected final BasicEventList<String> searchHistory = new BasicEventList<String>(100000);
public AbstractSearchPanel() {
historyPanel.setColumnHeader(2, "Duration");
@ -74,6 +73,35 @@ public abstract class AbstractSearchPanel<S, E> extends JComponent {
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 {
return getHistory(engine);
}
@Override
protected void done() {
if (engine == searchTextField.getSelectButton().getSelectedValue()) {
try {
searchHistory.clear();
searchHistory.addAll(get());
} catch (Exception e) {
Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getMessage(), e);
}
}
};
}.execute();
}
});
try {
// restore selected subtitle client
searchTextField.getSelectButton().setSelectedIndex(Integer.parseInt(getSettings().get("engine.selected", "0")));
@ -91,10 +119,18 @@ public abstract class AbstractSearchPanel<S, E> extends JComponent {
}
});
AutoCompleteSupport.install(searchTextField.getEditor(), searchHistory).setFilterMode(TextMatcherEditor.CONTAINS);
// 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();
@ -118,23 +154,6 @@ public abstract class AbstractSearchPanel<S, E> extends JComponent {
new SearchTask(requestProcessor).execute();
}
protected EventList<String> createSearchHistory() {
// create in-memory history
BasicEventList<String> history = new BasicEventList<String>();
// get the preferences node that contains the history entries
// and get a StringList that read and writes directly from and to the preferences
List<String> persistentHistory = getSettings().node("history").asList();
// add history from the preferences to the current in-memory history (for completion)
history.addAll(persistentHistory);
// perform all insert/add/remove operations on the in-memory history on the preferences node as well
ListChangeSynchronizer.syncEventListToList(history, persistentHistory);
return history;
}
private final AbstractAction searchAction = new AbstractAction("Find", ResourceManager.getIcon("action.find")) {
@Override
@ -200,12 +219,6 @@ public abstract class AbstractSearchPanel<S, E> extends JComponent {
// set search result
requestProcessor.setSearchResult(selectedSearchResult);
String historyEntry = requestProcessor.getHistoryEntry();
if (historyEntry != null && !searchHistory.contains(historyEntry)) {
searchHistory.add(historyEntry);
}
tab.setTitle(requestProcessor.getTitle());
// fetch elements of the selected search result
@ -330,14 +343,6 @@ public abstract class AbstractSearchPanel<S, E> extends JComponent {
return request.getSearchText();
}
public String getHistoryEntry() {
SeriesNameMatcher nameMatcher = new SeriesNameMatcher();
// the common word sequence of query and search result
// common name will maintain the exact word characters (incl. case) of the first argument
return nameMatcher.matchByFirstCommonWordSequence(searchResult.getName(), request.getSearchText());
}
public Icon getIcon() {
return null;
}
@ -362,7 +367,6 @@ public abstract class AbstractSearchPanel<S, E> extends JComponent {
public long getDuration() {
return duration;
}
}
}

View File

@ -1,7 +1,5 @@
package net.sourceforge.filebot.ui;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
@ -34,185 +32,170 @@ import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.tuned.ui.SelectButton;
import net.sourceforge.tuned.ui.TunedUtilities;
public class SelectButtonTextField<T> extends JComponent {
private SelectButton<T> selectButton = new SelectButton<T>();
private JComboBox editor = new JComboBox();
public SelectButtonTextField() {
selectButton.addActionListener(textFieldFocusOnClick);
editor.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 1, ((LineBorder) selectButton.getBorder()).getLineColor()));
setLayout(new MigLayout("nogrid, fill"));
add(selectButton, "h pref!, w pref!, sizegroupy this");
add(editor, "gap 0, w 195px!, sizegroupy this");
editor.setRenderer(new CompletionCellRenderer());
editor.setPrototypeDisplayValue("X");
editor.setUI(new TextFieldComboBoxUI());
TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.CTRL_MASK), new SpinClientAction(-1));
TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.CTRL_MASK), new SpinClientAction(1));
}
public String getText() {
return ((TextFieldComboBoxUI) editor.getUI()).getEditor().getText();
}
public JComboBox getEditor() {
return editor;
}
public SelectButton<T> getSelectButton() {
return selectButton;
}
private final ActionListener textFieldFocusOnClick = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getEditor().requestFocus();
}
};
private class SpinClientAction extends AbstractAction {
private int spin;
public SpinClientAction(int spin) {
super(String.format("Spin%+d", spin));
this.spin = spin;
}
@Override
public void actionPerformed(ActionEvent e) {
selectButton.spinValue(spin);
}
}
private class CompletionCellRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
setBorder(new EmptyBorder(1, 4, 1, 4));
String highlightText = SelectButtonTextField.this.getText().substring(0, ((TextFieldComboBoxUI) editor.getUI()).getEditor().getSelectionStart());
// highlight the matching sequence
Matcher matcher = Pattern.compile(highlightText, Pattern.LITERAL | Pattern.CASE_INSENSITIVE).matcher(value.toString());
// use no-break, because we really don't want line-wrapping in our table cells
StringBuffer htmlText = new StringBuffer("<html><nobr>");
if (matcher.find()) {
matcher.appendReplacement(htmlText, "<span style='font-weight: bold; text-decoration: underline;'>$0</span>");
}
matcher.appendTail(htmlText);
htmlText.append("</nobr></html>");
setText(htmlText.toString());
return this;
}
}
private class TextFieldComboBoxUI extends BasicComboBoxUI {
@Override
protected JButton createArrowButton() {
return new JButton(ResourceManager.getIcon("action.list"));
}
@Override
public void configureArrowButton() {
super.configureArrowButton();
arrowButton.setContentAreaFilled(false);
arrowButton.setFocusable(false);
}
@Override
protected void configureEditor() {
JTextComponent editor = getEditor();
editor.setEnabled(comboBox.isEnabled());
editor.setFocusable(comboBox.isFocusable());
editor.setFont(comboBox.getFont());
editor.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 3));
editor.addFocusListener(createFocusListener());
editor.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void changedUpdate(DocumentEvent e) {
popup.getList().repaint();
}
@Override
public void insertUpdate(DocumentEvent e) {
popup.getList().repaint();
}
@Override
public void removeUpdate(DocumentEvent e) {
popup.getList().repaint();
}
});
popup.getList().setPrototypeCellValue("X");
}
public JTextComponent getEditor() {
return (JTextComponent) editor;
}
@Override
protected ComboPopup createPopup() {
return new BasicComboPopup(comboBox) {
@Override
public void show(Component invoker, int x, int y) {
super.show(invoker, x - selectButton.getWidth(), y);
}
@Override
protected Rectangle computePopupBounds(int px, int py, int pw, int ph) {
Rectangle bounds = super.computePopupBounds(px, py, pw, ph);
bounds.width += selectButton.getWidth();
return bounds;
}
};
}
@Override
protected FocusListener createFocusListener() {
return new FocusHandler() {
/**
* Prevent action events from being fired on focusLost.
*/
@ -224,7 +207,7 @@ public class SelectButtonTextField<T> extends JComponent {
}
};
}
}
}

View File

@ -13,10 +13,12 @@ import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.TreeSet;
import javax.swing.AbstractAction;
import javax.swing.Icon;
@ -26,13 +28,15 @@ import javax.swing.JSpinner;
import javax.swing.KeyStroke;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.Language;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.WebServices;
import net.sourceforge.filebot.media.MediaDetection;
import net.sourceforge.filebot.similarity.Normalization;
import net.sourceforge.filebot.ui.AbstractSearchPanel;
import net.sourceforge.filebot.ui.FileBotList;
import net.sourceforge.filebot.ui.FileBotListExportHandler;
import net.sourceforge.filebot.ui.FileBotTab;
import net.sourceforge.filebot.Language;
import net.sourceforge.filebot.ui.LanguageComboBox;
import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.ui.transfer.ArrayTransferable;
@ -77,6 +81,18 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListProvider, E
TunedUtilities.installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_MASK), new SpinSeasonAction(-1));
}
@Override
protected Collection<String> getHistory(EpisodeListProvider engine) throws Exception {
final List<String> names = new ArrayList<String>(100000);
final SearchResult[] index = (engine == WebServices.AniDB) ? MediaDetection.releaseInfo.getAnidbIndex() : MediaDetection.releaseInfo.getTheTVDBIndex();
for (SearchResult it : index) {
for (String n : it.getEffectiveNames()) {
names.add(Normalization.removeTrailingBrackets(n));
}
}
return new TreeSet<String>(names);
}
@Override
protected EpisodeListProvider[] getSearchEngines() {
return WebServices.getEpisodeListProviders();

View File

@ -17,6 +17,7 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;
import java.util.logging.Level;
import javax.swing.AbstractAction;
@ -32,13 +33,16 @@ import javax.swing.JTextField;
import javax.swing.border.TitledBorder;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.Language;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.WebServices;
import net.sourceforge.filebot.media.MediaDetection;
import net.sourceforge.filebot.similarity.Normalization;
import net.sourceforge.filebot.ui.AbstractSearchPanel;
import net.sourceforge.filebot.Language;
import net.sourceforge.filebot.ui.LanguageComboBox;
import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.web.Movie;
import net.sourceforge.filebot.web.OpenSubtitlesClient;
import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.filebot.web.SubtitleDescriptor;
@ -138,6 +142,19 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
}
};
protected Collection<String> getHistory(SubtitleProvider engine) throws Exception {
final List<String> names = new ArrayList<String>(200000);
for (Movie it : MediaDetection.releaseInfo.getMovieList()) {
names.addAll(it.getEffectiveNamesWithoutYear());
}
for (SearchResult it : MediaDetection.releaseInfo.getTheTVDBIndex()) {
for (String n : it.getEffectiveNames()) {
names.add(Normalization.removeTrailingBrackets(n));
}
}
return new TreeSet<String>(names);
};
@Override
protected SubtitleProvider[] getSearchEngines() {
return WebServices.getSubtitleProviders();

View File

@ -155,7 +155,7 @@ public class AnidbClient extends AbstractEpisodeListProvider {
}
/**
* This method is (and must be!) overridden by {@link WebServices.AnidbClientWithLocalSearch} to use our own anime index from sourceforge (as to not abuse anidb servers)
* This method is (and must be!) overridden by WebServices.AnidbClientWithLocalSearch to use our own anime index from sourceforge (as to not abuse anidb servers)
*/
public synchronized List<AnidbSearchResult> getAnimeTitles() throws Exception {
URL url = new URL("http", host, "/api/anime-titles.dat.gz");
@ -176,6 +176,11 @@ public class AnidbClient extends AbstractEpisodeListProvider {
languageOrder.add("en");
languageOrder.add("ja");
List<String> typeOrder = new ArrayList<String>();
typeOrder.add("1");
typeOrder.add("4");
typeOrder.add("2");
// fetch data
Map<Integer, List<Object[]>> entriesByAnime = new HashMap<Integer, List<Object[]>>(65536);
@ -190,7 +195,7 @@ public class AnidbClient extends AbstractEpisodeListProvider {
String language = matcher.group(3);
String title = matcher.group(4);
if (aid > 0 && title.length() > 0 && languageOrder.contains(language)) {
if (aid > 0 && title.length() > 0 && typeOrder.contains(type) && languageOrder.contains(language)) {
List<Object[]> names = entriesByAnime.get(aid);
if (names == null) {
names = new ArrayList<Object[]>();
@ -200,7 +205,7 @@ public class AnidbClient extends AbstractEpisodeListProvider {
// resolve HTML entities
title = Jsoup.parse(title).text();
names.add(new Object[] { Integer.parseInt(type), languageOrder.indexOf(language), title });
names.add(new Object[] { typeOrder.indexOf(type), languageOrder.indexOf(language), title });
}
}
}

View File

@ -1,7 +1,5 @@
package net.sourceforge.tuned.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
@ -29,82 +27,72 @@ import javax.swing.JPopupMenu;
import javax.swing.SingleSelectionModel;
import javax.swing.SwingConstants;
public class SelectButton<T> extends JButton {
public static final String SELECTED_VALUE = "selected value";
private final Color beginColor = new Color(0xF0EEE4);
private final Color endColor = new Color(0xE0DED4);
private final Color beginColorHover = beginColor;
private final Color endColorHover = new Color(0xD8D7CD);
private final SelectIcon selectIcon = new SelectIcon();
private List<T> model = Collections.emptyList();
private SingleSelectionModel selectionModel = new DefaultSingleSelectionModel();
private LabelProvider<T> labelProvider = new NullLabelProvider<T>();
private boolean hover = false;
public SelectButton() {
setContentAreaFilled(false);
setFocusable(false);
super.setIcon(selectIcon);
setHorizontalAlignment(SwingConstants.CENTER);
setVerticalAlignment(SwingConstants.CENTER);
setBorder(BorderFactory.createLineBorder(new Color(0xA4A4A4), 1));
setPreferredSize(new Dimension(32, 22));
addActionListener(new OpenPopupOnClick());
}
public void setModel(Collection<T> model) {
this.model = new ArrayList<T>(model);
setSelectedIndex(0);
}
public LabelProvider<T> getLabelProvider() {
return labelProvider;
}
public void setLabelProvider(LabelProvider<T> labelProvider) {
this.labelProvider = labelProvider;
// update icon
this.setIcon(labelProvider.getIcon(getSelectedValue()));
}
@Override
public void setIcon(Icon icon) {
selectIcon.setInnerIcon(icon);
repaint();
}
public void setSelectedValue(T value) {
setSelectedIndex(model.indexOf(value));
}
public T getSelectedValue() {
if (!selectionModel.isSelected())
return null;
return model.get(selectionModel.getSelectedIndex());
}
public void setSelectedIndex(int i) {
if (i < 0 || i >= model.size()) {
@ -112,171 +100,150 @@ public class SelectButton<T> extends JButton {
setIcon(null);
return;
}
if (i != selectionModel.getSelectedIndex()) {
selectionModel.setSelectedIndex(i);
T value = model.get(i);
setIcon(labelProvider.getIcon(value));
firePropertyChange(SELECTED_VALUE, null, value);
}
selectionModel.setSelectedIndex(i);
T value = model.get(i);
setIcon(labelProvider.getIcon(value));
firePropertyChange(SELECTED_VALUE, null, value);
}
public int getSelectedIndex() {
return selectionModel.getSelectedIndex();
}
public SingleSelectionModel getSelectionModel() {
return selectionModel;
}
public void spinValue(int spin) {
int size = model.size();
spin = spin % size;
int next = getSelectedIndex() + spin;
if (next < 0)
next += size;
else if (next >= size)
next -= size;
setSelectedIndex(next);
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Rectangle bounds = new Rectangle(getSize());
if (hover)
g2d.setPaint(GradientStyle.TOP_TO_BOTTOM.getGradientPaint(bounds, beginColorHover, endColorHover));
else
g2d.setPaint(GradientStyle.TOP_TO_BOTTOM.getGradientPaint(bounds, beginColor, endColor));
g2d.fill(bounds);
super.paintComponent(g);
}
@Override
protected void processMouseEvent(MouseEvent e) {
switch (e.getID()) {
case MouseEvent.MOUSE_ENTERED:
hover = true;
repaint();
break;
case MouseEvent.MOUSE_EXITED:
hover = false;
repaint();
break;
case MouseEvent.MOUSE_ENTERED:
hover = true;
repaint();
break;
case MouseEvent.MOUSE_EXITED:
hover = false;
repaint();
break;
}
super.processMouseEvent(e);
}
private class OpenPopupOnClick implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JPopupMenu popup = new JPopupMenu();
for (T value : model) {
SelectPopupMenuItem item = new SelectPopupMenuItem(labelProvider.getText(value), labelProvider.getIcon(value), value);
if (value == getSelectedValue())
item.setSelected(true);
popup.add(item);
}
popup.show(SelectButton.this, 0, getHeight() - 1);
}
}
private class SelectPopupMenuItem extends JMenuItem implements ActionListener {
private final T value;
public SelectPopupMenuItem(String text, Icon icon, T value) {
super(text, icon);
this.value = value;
setMargin(new Insets(3, 0, 3, 0));
addActionListener(this);
}
@Override
public void setSelected(boolean selected) {
setFont(getFont().deriveFont(selected ? Font.BOLD : Font.PLAIN));
}
@Override
public void actionPerformed(ActionEvent e) {
setSelectedValue(value);
}
}
private static class SelectIcon implements Icon {
private final GeneralPath arrow;
private Icon icon;
public SelectIcon() {
arrow = new GeneralPath(Path2D.WIND_EVEN_ODD, 3);
int x = 25;
int y = 10;
arrow.moveTo(x - 2, y);
arrow.lineTo(x, y + 3);
arrow.lineTo(x + 3, y);
arrow.lineTo(x - 2, y);
}
public void setInnerIcon(Icon icon) {
this.icon = icon;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2d = (Graphics2D) g;
if (icon != null) {
icon.paintIcon(c, g2d, 4, 3);
}
g2d.setPaint(Color.BLACK);
g2d.fill(arrow);
}
public int getIconWidth() {
return 30;
}
public int getIconHeight() {
return 20;
}
}
}

View File

@ -125,7 +125,7 @@
^Video$
^VIDEO_TS$
^Videos$
^volume[0-9]?$
^volume\D?\d?$
^Volumes$
^watch$
^www$