* Lazy XPath evaluation for EpisodeList/Subtitle Clients
* AbstractSearchPanel (used in SubtitlePanel only so far) * started using GlazedLists * replaced searchtextfield with customized combobox (will be used for completion in the future) * renamed FileFormat to FileUtil and move to tuned * removed ESC shortcut
This commit is contained in:
parent
405f04b9dd
commit
adb4d68055
Binary file not shown.
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
@ -22,7 +22,7 @@ public class Main {
|
||||||
/**
|
/**
|
||||||
* @param args
|
* @param args
|
||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String... args) {
|
||||||
|
|
||||||
final Arguments arguments = new Arguments(args);
|
final Arguments arguments = new Arguments(args);
|
||||||
|
|
||||||
|
@ -36,15 +36,16 @@ public class Main {
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
||||||
}
|
}
|
||||||
|
|
||||||
final FileBotWindow window = new FileBotWindow();
|
|
||||||
|
|
||||||
// publish messages from arguments to the newly created components
|
|
||||||
arguments.publishMessages();
|
|
||||||
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
SwingUtilities.invokeLater(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
FileBotWindow window = new FileBotWindow();
|
||||||
|
|
||||||
|
// publish messages from arguments to the newly created components
|
||||||
|
arguments.publishMessages();
|
||||||
|
|
||||||
|
// start
|
||||||
window.setVisible(true);
|
window.setVisible(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,8 @@ import java.nio.channels.FileChannel;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.FileUtil;
|
||||||
|
|
||||||
|
|
||||||
public class FileBotUtil {
|
public class FileBotUtil {
|
||||||
|
|
||||||
|
@ -42,15 +44,6 @@ public class FileBotUtil {
|
||||||
return INVALID_CHARACTERS_PATTERN.matcher(filename).find();
|
return INVALID_CHARACTERS_PATTERN.matcher(filename).find();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Throwable getRootCause(Throwable t) {
|
|
||||||
while (t.getCause() != null) {
|
|
||||||
t = t.getCause();
|
|
||||||
}
|
|
||||||
|
|
||||||
return t;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String[] TORRENT_FILE_EXTENSIONS = { "torrent" };
|
private static final String[] TORRENT_FILE_EXTENSIONS = { "torrent" };
|
||||||
private static final String[] SFV_FILE_EXTENSIONS = { "sfv" };
|
private static final String[] SFV_FILE_EXTENSIONS = { "sfv" };
|
||||||
private static final String[] LIST_FILE_EXTENSIONS = { "txt", "list", "" };
|
private static final String[] LIST_FILE_EXTENSIONS = { "txt", "list", "" };
|
||||||
|
@ -84,7 +77,7 @@ public class FileBotUtil {
|
||||||
|
|
||||||
private static boolean containsOnly(List<File> files, String[] extensions) {
|
private static boolean containsOnly(List<File> files, String[] extensions) {
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
if (!FileFormat.hasExtension(file, extensions))
|
if (!FileUtil.hasExtension(file, extensions))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +106,7 @@ public class FileBotUtil {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(File dir, String name) {
|
public boolean accept(File dir, String name) {
|
||||||
return FileFormat.hasExtension(name, SUBTITLE_FILE_EXTENSIONS);
|
return FileUtil.hasExtension(name, SUBTITLE_FILE_EXTENSIONS);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,17 +2,16 @@
|
||||||
package net.sourceforge.filebot;
|
package net.sourceforge.filebot;
|
||||||
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.prefs.BackingStoreException;
|
import java.util.prefs.BackingStoreException;
|
||||||
import java.util.prefs.Preferences;
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.PreferencesList;
|
||||||
|
import net.sourceforge.tuned.PreferencesMap;
|
||||||
|
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
|
|
||||||
|
@ -37,17 +36,7 @@ public class Settings {
|
||||||
|
|
||||||
|
|
||||||
private Settings() {
|
private Settings() {
|
||||||
this.prefs = Preferences.userRoot().node(ROOT);
|
prefs = Preferences.userRoot().node(ROOT);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void putString(String key, String value) {
|
|
||||||
prefs.put(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public String getString(String key, String def) {
|
|
||||||
return prefs.get(key, def);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,125 +50,13 @@ public class Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void putBoolean(String key, boolean value) {
|
public List<String> asStringList(String key) {
|
||||||
prefs.putBoolean(key, value);
|
return PreferencesList.map(prefs.node(key), String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean getBoolean(String key, boolean def) {
|
public Map<String, Boolean> asBooleanMap(String key) {
|
||||||
return prefs.getBoolean(key, def);
|
return PreferencesMap.map(prefs.node(key), Boolean.class);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Collection<String> getStringList(String key) {
|
|
||||||
Preferences listNode = prefs.node(key);
|
|
||||||
|
|
||||||
List<String> list = new ArrayList<String>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (String nodeKey : listNode.keys()) {
|
|
||||||
list.add(listNode.get(nodeKey, null));
|
|
||||||
}
|
|
||||||
} catch (BackingStoreException e) {
|
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void putStringList(String key, Collection<String> list) {
|
|
||||||
Preferences listNode = getClearNode(key);
|
|
||||||
|
|
||||||
int i = 0;
|
|
||||||
|
|
||||||
for (String entry : list) {
|
|
||||||
listNode.put(Integer.toString(i), entry);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Map<String, String> getStringMap(String key) {
|
|
||||||
Preferences mapNode = prefs.node(key);
|
|
||||||
|
|
||||||
Map<String, String> map = new HashMap<String, String>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (String mapNodeKey : mapNode.keys()) {
|
|
||||||
map.put(mapNodeKey, mapNode.get(mapNodeKey, null));
|
|
||||||
}
|
|
||||||
} catch (BackingStoreException e) {
|
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void putStringMap(String key, Map<String, String> map) {
|
|
||||||
Preferences mapNode = getClearNode(key);
|
|
||||||
|
|
||||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
|
||||||
mapNode.put(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Map<String, Integer> getIntegerMap(String key) {
|
|
||||||
Map<String, String> entries = getStringMap(key);
|
|
||||||
|
|
||||||
Map<String, Integer> map = new HashMap<String, Integer>(entries.size());
|
|
||||||
|
|
||||||
for (Entry<String, String> entry : entries.entrySet()) {
|
|
||||||
try {
|
|
||||||
map.put(entry.getKey(), Integer.valueOf(entry.getValue()));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void putIntegerMap(String key, Map<String, Integer> map) {
|
|
||||||
Map<String, String> entries = new HashMap<String, String>();
|
|
||||||
|
|
||||||
for (Entry<String, Integer> entry : map.entrySet()) {
|
|
||||||
entries.put(entry.getKey(), entry.getValue().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
putStringMap(key, entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Map<String, Boolean> getBooleanMap(String key) {
|
|
||||||
Map<String, String> entries = getStringMap(key);
|
|
||||||
|
|
||||||
Map<String, Boolean> map = new HashMap<String, Boolean>(entries.size());
|
|
||||||
|
|
||||||
for (Entry<String, String> entry : entries.entrySet()) {
|
|
||||||
map.put(entry.getKey(), Boolean.valueOf(entry.getValue()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void putBooleanMap(String key, Map<String, Boolean> map) {
|
|
||||||
Map<String, String> entries = new HashMap<String, String>();
|
|
||||||
|
|
||||||
for (Entry<String, Boolean> entry : map.entrySet()) {
|
|
||||||
entries.put(entry.getKey(), entry.getValue().toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
putStringMap(key, entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void putBooleanMapEntry(String nodeKey, String mapKey, Boolean value) {
|
|
||||||
prefs.node(nodeKey).put(mapKey, value.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -193,16 +70,4 @@ public class Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Preferences getClearNode(String nodeName) {
|
|
||||||
Preferences node = prefs.node(nodeName);
|
|
||||||
|
|
||||||
try {
|
|
||||||
node.clear();
|
|
||||||
} catch (BackingStoreException e) {
|
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 208 B |
|
@ -0,0 +1,414 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Window;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.Box;
|
||||||
|
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.ScrollPaneConstants;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
|
import net.sourceforge.filebot.web.SearchResult;
|
||||||
|
import net.sourceforge.tuned.ExceptionUtil;
|
||||||
|
import net.sourceforge.tuned.ProgressIterator;
|
||||||
|
import net.sourceforge.tuned.ui.SelectButtonTextField;
|
||||||
|
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
||||||
|
import net.sourceforge.tuned.ui.TunedUtil;
|
||||||
|
import ca.odell.glazedlists.EventList;
|
||||||
|
import ca.odell.glazedlists.swing.AutoCompleteSupport;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class AbstractSearchPanel<S, E, T extends JComponent> extends FileBotPanel {
|
||||||
|
|
||||||
|
private final JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.TOP, JTabbedPane.WRAP_TAB_LAYOUT);
|
||||||
|
|
||||||
|
private final HistoryPanel historyPanel = new HistoryPanel();
|
||||||
|
|
||||||
|
private final SelectButtonTextField<S> searchField;
|
||||||
|
|
||||||
|
private final EventList<String> searchHistory;
|
||||||
|
|
||||||
|
|
||||||
|
public AbstractSearchPanel(String title, Icon icon, EventList<String> searchHistory) {
|
||||||
|
super(title, icon);
|
||||||
|
|
||||||
|
this.searchHistory = searchHistory;
|
||||||
|
|
||||||
|
setLayout(new BorderLayout(10, 5));
|
||||||
|
|
||||||
|
searchField = new SelectButtonTextField<S>();
|
||||||
|
|
||||||
|
Box searchBox = Box.createHorizontalBox();
|
||||||
|
searchBox.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||||
|
|
||||||
|
searchField.setMaximumSize(searchField.getPreferredSize());
|
||||||
|
|
||||||
|
searchBox.add(Box.createHorizontalGlue());
|
||||||
|
searchBox.add(searchField);
|
||||||
|
searchBox.add(Box.createHorizontalStrut(15));
|
||||||
|
searchBox.add(new JButton(searchAction));
|
||||||
|
searchBox.add(Box.createHorizontalGlue());
|
||||||
|
|
||||||
|
JPanel centerPanel = new JPanel(new BorderLayout());
|
||||||
|
centerPanel.setBorder(BorderFactory.createTitledBorder("Search Results"));
|
||||||
|
|
||||||
|
centerPanel.add(tabbedPane, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
historyPanel.setColumnHeader3("Duration");
|
||||||
|
|
||||||
|
JScrollPane historyScrollPane = new JScrollPane(historyPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
historyScrollPane.setBorder(BorderFactory.createEmptyBorder());
|
||||||
|
|
||||||
|
tabbedPane.addTab("History", ResourceManager.getIcon("tab.history"), historyScrollPane);
|
||||||
|
|
||||||
|
add(searchBox, BorderLayout.NORTH);
|
||||||
|
add(centerPanel, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
AutoCompleteSupport.install(searchField.getEditor(), searchHistory);
|
||||||
|
|
||||||
|
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract SearchTask createSearchTask();
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract void configureSelectDialog(SelectDialog<SearchResult> selectDialog);
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract FetchTask createFetchTask(SearchTask searchTask, SearchResult selectedSearchResult);
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract URI getLink(S client, SearchResult searchResult);
|
||||||
|
|
||||||
|
|
||||||
|
public List<String> getSearchHistory() {
|
||||||
|
return searchHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public HistoryPanel getHistoryPanel() {
|
||||||
|
return historyPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SelectButtonTextField<S> getSearchField() {
|
||||||
|
return searchField;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final AbstractAction searchAction = new AbstractAction("Find", ResourceManager.getIcon("action.find")) {
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
|
||||||
|
SearchTask searchTask = createSearchTask();
|
||||||
|
searchTask.addPropertyChangeListener(new SearchTaskListener());
|
||||||
|
|
||||||
|
searchTask.execute();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract class SearchTask extends SwingWorker<List<SearchResult>, Void> {
|
||||||
|
|
||||||
|
private final String searchText;
|
||||||
|
private final S client;
|
||||||
|
|
||||||
|
private final T tabPanel;
|
||||||
|
|
||||||
|
|
||||||
|
public SearchTask(S client, String searchText, T tabPanel) {
|
||||||
|
this.searchText = searchText;
|
||||||
|
this.client = client;
|
||||||
|
this.tabPanel = tabPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected abstract List<SearchResult> doInBackground() throws Exception;
|
||||||
|
|
||||||
|
|
||||||
|
public String getSearchText() {
|
||||||
|
return searchText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public S getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public T getTabPanel() {
|
||||||
|
return tabPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class SearchTaskListener extends SwingWorkerPropertyChangeAdapter {
|
||||||
|
|
||||||
|
private FileBotTab<T> tab;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void started(PropertyChangeEvent evt) {
|
||||||
|
SearchTask task = (SearchTask) evt.getSource();
|
||||||
|
|
||||||
|
tab = new FileBotTab<T>(task.getTabPanel());
|
||||||
|
|
||||||
|
tab.setTitle(task.getSearchText());
|
||||||
|
tab.setLoading(true);
|
||||||
|
tab.setIcon(searchField.getSelectButton().getIconProvider().getIcon(task.getClient()));
|
||||||
|
|
||||||
|
tab.addTo(tabbedPane);
|
||||||
|
|
||||||
|
tabbedPane.setSelectedComponent(tab);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void done(PropertyChangeEvent evt) {
|
||||||
|
// tab might have been closed
|
||||||
|
if (tab.isClosed())
|
||||||
|
return;
|
||||||
|
|
||||||
|
SearchTask task = (SearchTask) evt.getSource();
|
||||||
|
|
||||||
|
try {
|
||||||
|
SearchResult selectedResult = selectSearchResult(task);
|
||||||
|
|
||||||
|
if (selectedResult == null) {
|
||||||
|
if (task.get().isEmpty()) {
|
||||||
|
// no search results
|
||||||
|
MessageManager.showWarning(String.format("\"%s\" has not been found.", task.getSearchText()));
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String title = selectedResult.getName();
|
||||||
|
|
||||||
|
if (!searchHistory.contains(title)) {
|
||||||
|
searchHistory.add(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.setTitle(title);
|
||||||
|
|
||||||
|
FetchTask fetchTask = createFetchTask(task, selectedResult);
|
||||||
|
fetchTask.addPropertyChangeListener(new FetchTaskListener(tab));
|
||||||
|
|
||||||
|
fetchTask.execute();
|
||||||
|
} catch (Exception e) {
|
||||||
|
tab.close();
|
||||||
|
|
||||||
|
Throwable cause = ExceptionUtil.getRootCause(e);
|
||||||
|
|
||||||
|
MessageManager.showWarning(cause.getMessage());
|
||||||
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, cause.getMessage(), cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private SearchResult selectSearchResult(SearchTask task) throws Exception {
|
||||||
|
List<SearchResult> searchResults = task.get();
|
||||||
|
|
||||||
|
switch (searchResults.size()) {
|
||||||
|
case 0:
|
||||||
|
return null;
|
||||||
|
case 1:
|
||||||
|
return searchResults.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// multiple results have been found, user must selected one
|
||||||
|
Window window = SwingUtilities.getWindowAncestor(AbstractSearchPanel.this);
|
||||||
|
|
||||||
|
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(window, searchResults);
|
||||||
|
|
||||||
|
selectDialog.setIconImage(TunedUtil.getImage(searchField.getSelectButton().getIconProvider().getIcon(task.getClient())));
|
||||||
|
|
||||||
|
configureSelectDialog(selectDialog);
|
||||||
|
selectDialog.setVisible(true);
|
||||||
|
|
||||||
|
// selected value or null if canceled by the user
|
||||||
|
return selectDialog.getSelectedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract class FetchTask extends SwingWorker<Void, E> {
|
||||||
|
|
||||||
|
private long duration = -1;
|
||||||
|
private int count = 0;
|
||||||
|
|
||||||
|
private final S client;
|
||||||
|
private final SearchResult searchResult;
|
||||||
|
private final T tabPanel;
|
||||||
|
|
||||||
|
|
||||||
|
public FetchTask(S client, SearchResult searchResult, T tabPanel) {
|
||||||
|
this.client = client;
|
||||||
|
this.searchResult = searchResult;
|
||||||
|
this.tabPanel = tabPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
protected final Void doInBackground() throws Exception {
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
ProgressIterator<E> iterator = fetch();
|
||||||
|
|
||||||
|
while (!isCancelled() && iterator.hasNext()) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
publish(iterator.next());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
setProgress((iterator.getPosition() * 100) / iterator.getLength());
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
duration = System.currentTimeMillis() - start;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract ProgressIterator<E> fetch() throws Exception;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected abstract void process(List<E> elements);
|
||||||
|
|
||||||
|
|
||||||
|
public abstract String getStatusMessage();
|
||||||
|
|
||||||
|
|
||||||
|
public S getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SearchResult getSearchResult() {
|
||||||
|
return searchResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public T getTabPanel() {
|
||||||
|
return tabPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public long getDuration() {
|
||||||
|
return duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class FetchTaskListener extends SwingWorkerPropertyChangeAdapter {
|
||||||
|
|
||||||
|
private final FileBotTab<T> tab;
|
||||||
|
|
||||||
|
private CancelAction cancelOnClose;
|
||||||
|
|
||||||
|
|
||||||
|
public FetchTaskListener(FileBotTab<T> tab) {
|
||||||
|
this.tab = tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void started(PropertyChangeEvent evt) {
|
||||||
|
cancelOnClose = new CancelAction((SwingWorker<?, ?>) evt.getSource());
|
||||||
|
tab.getTabComponent().getCloseButton().addActionListener(cancelOnClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void done(PropertyChangeEvent evt) {
|
||||||
|
tab.getTabComponent().getCloseButton().removeActionListener(cancelOnClose);
|
||||||
|
|
||||||
|
FetchTask task = (FetchTask) evt.getSource();
|
||||||
|
|
||||||
|
// tab might still be open, even if task was cancelled
|
||||||
|
if (tab.isClosed() || task.isCancelled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// check if exception occurred
|
||||||
|
task.get();
|
||||||
|
|
||||||
|
String title = task.getSearchResult().toString();
|
||||||
|
URI link = getLink(task.getClient(), task.getSearchResult());
|
||||||
|
Icon icon = searchField.getSelectButton().getIconProvider().getIcon(task.getClient());
|
||||||
|
String info = task.getStatusMessage();
|
||||||
|
String duration = String.format("%,d ms", task.getDuration());
|
||||||
|
|
||||||
|
historyPanel.add(title, link, icon, info, duration);
|
||||||
|
|
||||||
|
// close tab if no elements were fetched
|
||||||
|
if (task.getCount() <= 0) {
|
||||||
|
MessageManager.showWarning(info);
|
||||||
|
tab.close();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
tab.close();
|
||||||
|
|
||||||
|
MessageManager.showWarning(ExceptionUtil.getRootCause(e).getMessage());
|
||||||
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class CancelAction implements ActionListener {
|
||||||
|
|
||||||
|
private final SwingWorker<?, ?> worker;
|
||||||
|
|
||||||
|
|
||||||
|
public CancelAction(SwingWorker<?, ?> worker) {
|
||||||
|
this.worker = worker;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
worker.cancel(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -53,7 +53,7 @@ public class FileBotPanel extends JPanel {
|
||||||
|
|
||||||
|
|
||||||
public FileBotPanel(String title, Icon icon) {
|
public FileBotPanel(String title, Icon icon) {
|
||||||
super(new BorderLayout(10, 0));
|
super(new BorderLayout(10, 10));
|
||||||
this.name = title;
|
this.name = title;
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,22 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.ui.panel.subtitle;
|
package net.sourceforge.filebot.ui;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.DefaultBoundedRangeModel;
|
||||||
|
import javax.swing.Icon;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JTabbedPane;
|
import javax.swing.JTabbedPane;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
import net.sourceforge.filebot.ui.FileBotTabComponent;
|
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
||||||
|
import net.sourceforge.tuned.ui.ProgressIndicator;
|
||||||
|
|
||||||
|
|
||||||
public class FileBotTab<T extends JComponent> extends JPanel {
|
public class FileBotTab<T extends JComponent> extends JPanel {
|
||||||
|
@ -22,7 +25,7 @@ public class FileBotTab<T extends JComponent> extends JPanel {
|
||||||
|
|
||||||
private final T component;
|
private final T component;
|
||||||
|
|
||||||
private ImageIcon icon;
|
private Icon icon;
|
||||||
|
|
||||||
private boolean loading = false;
|
private boolean loading = false;
|
||||||
|
|
||||||
|
@ -33,7 +36,14 @@ public class FileBotTab<T extends JComponent> extends JPanel {
|
||||||
this.component = component;
|
this.component = component;
|
||||||
tabComponent.getCloseButton().addActionListener(closeAction);
|
tabComponent.getCloseButton().addActionListener(closeAction);
|
||||||
|
|
||||||
add(component, BorderLayout.CENTER);
|
ProgressIndicator progress = new ProgressIndicator(new DefaultBoundedRangeModel(4, 0, 0, 10));
|
||||||
|
progress.setPaintBackground(true);
|
||||||
|
progress.setPaintText(false);
|
||||||
|
progress.setBackground(new Color(255, 255, 255, 70));
|
||||||
|
|
||||||
|
LoadingOverlayPane pane = new LoadingOverlayPane(this.component, progress);
|
||||||
|
|
||||||
|
add(pane, BorderLayout.CENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,7 +95,7 @@ public class FileBotTab<T extends JComponent> extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setIcon(ImageIcon icon) {
|
public void setIcon(Icon icon) {
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
|
|
||||||
if (!loading) {
|
if (!loading) {
|
||||||
|
@ -94,7 +104,7 @@ public class FileBotTab<T extends JComponent> extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ImageIcon getIcon() {
|
public Icon getIcon() {
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,13 @@ package net.sourceforge.filebot.ui;
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.CardLayout;
|
import java.awt.CardLayout;
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JFrame;
|
import javax.swing.JFrame;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.KeyStroke;
|
|
||||||
import javax.swing.OverlayLayout;
|
import javax.swing.OverlayLayout;
|
||||||
import javax.swing.ScrollPaneConstants;
|
import javax.swing.ScrollPaneConstants;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
|
@ -27,7 +24,6 @@ import net.sourceforge.tuned.MessageBus;
|
||||||
import net.sourceforge.tuned.MessageHandler;
|
import net.sourceforge.tuned.MessageHandler;
|
||||||
import net.sourceforge.tuned.ui.ShadowBorder;
|
import net.sourceforge.tuned.ui.ShadowBorder;
|
||||||
import net.sourceforge.tuned.ui.SimpleListModel;
|
import net.sourceforge.tuned.ui.SimpleListModel;
|
||||||
import net.sourceforge.tuned.ui.TunedUtil;
|
|
||||||
|
|
||||||
|
|
||||||
public class FileBotWindow extends JFrame implements ListSelectionListener {
|
public class FileBotWindow extends JFrame implements ListSelectionListener {
|
||||||
|
@ -55,9 +51,6 @@ public class FileBotWindow extends JFrame implements ListSelectionListener {
|
||||||
|
|
||||||
setContentPane(contentPane);
|
setContentPane(contentPane);
|
||||||
|
|
||||||
// Shortcut ESC
|
|
||||||
TunedUtil.registerActionForKeystroke(contentPane, KeyStroke.getKeyStroke("released ESCAPE"), closeAction);
|
|
||||||
|
|
||||||
setSize(760, 615);
|
setSize(760, 615);
|
||||||
|
|
||||||
selectionListPanel.setSelectedIndex(Settings.getSettings().getInt(Settings.SELECTED_PANEL, 3));
|
selectionListPanel.setSelectedIndex(Settings.getSettings().getInt(Settings.SELECTED_PANEL, 3));
|
||||||
|
@ -150,13 +143,4 @@ public class FileBotWindow extends JFrame implements ListSelectionListener {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private final AbstractAction closeAction = new AbstractAction("Close") {
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
setVisible(false);
|
|
||||||
dispose();
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import java.awt.Color;
|
||||||
import java.awt.FlowLayout;
|
import java.awt.FlowLayout;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.GridLayout;
|
import java.awt.GridLayout;
|
||||||
import java.net.URL;
|
import java.net.URI;
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
|
@ -66,8 +66,8 @@ public class HistoryPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void add(String column1, URL url, Icon icon, String column2, String column3) {
|
public void add(String column1, URI link, Icon icon, String column2, String column3) {
|
||||||
JLabel label1 = (url != null) ? new HyperlinkLabel(column1, url) : new JLabel(column1);
|
JLabel label1 = (link != null) ? new HyperlinkLabel(column1, link) : new JLabel(column1);
|
||||||
JLabel label2 = new JLabel(column2);
|
JLabel label2 = new JLabel(column2);
|
||||||
JLabel label3 = new JLabel(column3);
|
JLabel label3 = new JLabel(column3);
|
||||||
|
|
||||||
|
|
|
@ -26,10 +26,10 @@ import javax.swing.event.ChangeListener;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
import net.sourceforge.filebot.ui.FileBotTree;
|
import net.sourceforge.filebot.ui.FileBotTree;
|
||||||
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
||||||
|
import net.sourceforge.tuned.FileUtil;
|
||||||
import net.sourceforge.tuned.ui.GradientStyle;
|
import net.sourceforge.tuned.ui.GradientStyle;
|
||||||
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
||||||
import net.sourceforge.tuned.ui.notification.SeparatorBorder;
|
import net.sourceforge.tuned.ui.notification.SeparatorBorder;
|
||||||
|
@ -84,7 +84,7 @@ public class SplitPanel extends ToolPanel implements ChangeListener {
|
||||||
|
|
||||||
|
|
||||||
private long getSplitSize() {
|
private long getSplitSize() {
|
||||||
return spinnerModel.getNumber().intValue() * FileFormat.MEGA;
|
return spinnerModel.getNumber().intValue() * FileUtil.MEGA;
|
||||||
}
|
}
|
||||||
|
|
||||||
private UpdateTask updateTask;
|
private UpdateTask updateTask;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import java.util.Collection;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
import net.sourceforge.tuned.FileUtil;
|
||||||
|
|
||||||
|
|
||||||
public abstract class ToolPanel extends JComponent {
|
public abstract class ToolPanel extends JComponent {
|
||||||
|
@ -47,7 +47,7 @@ public abstract class ToolPanel extends JComponent {
|
||||||
count = String.format("%d files", files.size());
|
count = String.format("%d files", files.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
node.setUserObject(String.format("%s (%s, %s)", name, count, FileFormat.formatSize(totalSize)));
|
node.setUserObject(String.format("%s (%s, %s)", name, count, FileUtil.formatSize(totalSize)));
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,10 @@ import javax.swing.SwingWorker;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
import net.sourceforge.filebot.ui.FileBotTree;
|
import net.sourceforge.filebot.ui.FileBotTree;
|
||||||
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
|
||||||
|
import net.sourceforge.tuned.FileUtil;
|
||||||
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
import net.sourceforge.tuned.ui.LoadingOverlayPane;
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ public class TypePanel extends ToolPanel {
|
||||||
SortedMap<String, SortedSet<File>> map = new TreeMap<String, SortedSet<File>>();
|
SortedMap<String, SortedSet<File>> map = new TreeMap<String, SortedSet<File>>();
|
||||||
|
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
String extension = FileFormat.getExtension(file);
|
String extension = FileUtil.getExtension(file);
|
||||||
|
|
||||||
SortedSet<File> set = map.get(extension);
|
SortedSet<File> set = map.get(extension);
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,10 @@ import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileBotUtil;
|
import net.sourceforge.filebot.FileBotUtil;
|
||||||
import net.sourceforge.filebot.FileFormat;
|
|
||||||
import net.sourceforge.filebot.torrent.Torrent;
|
import net.sourceforge.filebot.torrent.Torrent;
|
||||||
import net.sourceforge.filebot.ui.FileBotList;
|
import net.sourceforge.filebot.ui.FileBotList;
|
||||||
import net.sourceforge.filebot.ui.transferablepolicies.FileTransferablePolicy;
|
import net.sourceforge.filebot.ui.transferablepolicies.FileTransferablePolicy;
|
||||||
|
import net.sourceforge.tuned.FileUtil;
|
||||||
|
|
||||||
|
|
||||||
class FileListTransferablePolicy extends FileTransferablePolicy {
|
class FileListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
@ -35,7 +35,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
|
||||||
@Override
|
@Override
|
||||||
protected void load(List<File> files) {
|
protected void load(List<File> files) {
|
||||||
if (files.size() > 1) {
|
if (files.size() > 1) {
|
||||||
list.setTitle(FileFormat.getFolderName(files.get(0).getParentFile()));
|
list.setTitle(FileUtil.getFolderName(files.get(0).getParentFile()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FileBotUtil.containsOnlyFolders(files)) {
|
if (FileBotUtil.containsOnlyFolders(files)) {
|
||||||
|
@ -50,12 +50,12 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
private void loadFolderList(List<File> folders) {
|
private void loadFolderList(List<File> folders) {
|
||||||
if (folders.size() == 1) {
|
if (folders.size() == 1) {
|
||||||
list.setTitle(FileFormat.getFolderName(folders.get(0)));
|
list.setTitle(FileUtil.getFolderName(folders.get(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (File folder : folders) {
|
for (File folder : folders) {
|
||||||
for (File file : folder.listFiles()) {
|
for (File file : folder.listFiles()) {
|
||||||
list.getModel().add(FileFormat.getFolderName(file));
|
list.getModel().add(FileUtil.getFolderName(file));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,12 +70,12 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (torrentFiles.size() == 1) {
|
if (torrentFiles.size() == 1) {
|
||||||
list.setTitle(FileFormat.getNameWithoutExtension(torrents.get(0).getName()));
|
list.setTitle(FileUtil.getNameWithoutExtension(torrents.get(0).getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Torrent torrent : torrents) {
|
for (Torrent torrent : torrents) {
|
||||||
for (Torrent.Entry entry : torrent.getFiles()) {
|
for (Torrent.Entry entry : torrent.getFiles()) {
|
||||||
list.getModel().add(FileFormat.getNameWithoutExtension(entry.getName()));
|
list.getModel().add(FileUtil.getNameWithoutExtension(entry.getName()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -87,7 +87,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void load(File file) {
|
protected void load(File file) {
|
||||||
list.getModel().add(FileFormat.getFileName(file));
|
list.getModel().add(FileUtil.getFileName(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,11 @@ import net.sourceforge.filebot.torrent.Torrent;
|
||||||
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
|
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
|
||||||
import net.sourceforge.filebot.ui.panel.rename.entry.StringEntry;
|
import net.sourceforge.filebot.ui.panel.rename.entry.StringEntry;
|
||||||
import net.sourceforge.filebot.ui.panel.rename.entry.TorrentEntry;
|
import net.sourceforge.filebot.ui.panel.rename.entry.TorrentEntry;
|
||||||
import net.sourceforge.filebot.ui.transferablepolicies.MultiTransferablePolicy;
|
import net.sourceforge.filebot.ui.transferablepolicies.CompositeTransferablePolicy;
|
||||||
import net.sourceforge.filebot.ui.transferablepolicies.TextTransferablePolicy;
|
import net.sourceforge.filebot.ui.transferablepolicies.TextTransferablePolicy;
|
||||||
|
|
||||||
|
|
||||||
class NamesListTransferablePolicy extends MultiTransferablePolicy {
|
class NamesListTransferablePolicy extends CompositeTransferablePolicy {
|
||||||
|
|
||||||
private final RenameList list;
|
private final RenameList list;
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,11 @@ import java.util.List;
|
||||||
import javax.swing.AbstractAction;
|
import javax.swing.AbstractAction;
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
import net.sourceforge.filebot.ui.MessageManager;
|
import net.sourceforge.filebot.ui.MessageManager;
|
||||||
import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry;
|
import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry;
|
||||||
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
|
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
|
||||||
|
import net.sourceforge.tuned.FileUtil;
|
||||||
|
|
||||||
|
|
||||||
public class RenameAction extends AbstractAction {
|
public class RenameAction extends AbstractAction {
|
||||||
|
@ -44,7 +44,7 @@ public class RenameAction extends AbstractAction {
|
||||||
FileEntry fileEntry = (FileEntry) fileEntries.get(i);
|
FileEntry fileEntry = (FileEntry) fileEntries.get(i);
|
||||||
File f = fileEntry.getFile();
|
File f = fileEntry.getFile();
|
||||||
|
|
||||||
String newName = nameEntries.get(i).toString() + FileFormat.getExtension(f, true);
|
String newName = nameEntries.get(i).toString() + FileUtil.getExtension(f, true);
|
||||||
|
|
||||||
File newFile = new File(f.getParentFile(), newName);
|
File newFile = new File(f.getParentFile(), newName);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ package net.sourceforge.filebot.ui.panel.rename.entry;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
import net.sourceforge.tuned.FileUtil;
|
||||||
|
|
||||||
|
|
||||||
public class FileEntry extends AbstractFileEntry {
|
public class FileEntry extends AbstractFileEntry {
|
||||||
|
@ -16,11 +16,11 @@ public class FileEntry extends AbstractFileEntry {
|
||||||
|
|
||||||
|
|
||||||
public FileEntry(File file) {
|
public FileEntry(File file) {
|
||||||
super(FileFormat.getFileName(file));
|
super(FileUtil.getFileName(file));
|
||||||
|
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.length = file.length();
|
this.length = file.length();
|
||||||
this.type = FileFormat.getFileType(file);
|
this.type = FileUtil.getFileType(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
package net.sourceforge.filebot.ui.panel.rename.entry;
|
package net.sourceforge.filebot.ui.panel.rename.entry;
|
||||||
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
|
||||||
import net.sourceforge.filebot.torrent.Torrent.Entry;
|
import net.sourceforge.filebot.torrent.Torrent.Entry;
|
||||||
|
import net.sourceforge.tuned.FileUtil;
|
||||||
|
|
||||||
|
|
||||||
public class TorrentEntry extends AbstractFileEntry {
|
public class TorrentEntry extends AbstractFileEntry {
|
||||||
|
@ -12,7 +12,7 @@ public class TorrentEntry extends AbstractFileEntry {
|
||||||
|
|
||||||
|
|
||||||
public TorrentEntry(Entry entry) {
|
public TorrentEntry(Entry entry) {
|
||||||
super(FileFormat.getNameWithoutExtension(entry.getName()));
|
super(FileUtil.getNameWithoutExtension(entry.getName()));
|
||||||
|
|
||||||
this.entry = entry;
|
this.entry = entry;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,29 @@
|
||||||
package net.sourceforge.filebot.ui.panel.search;
|
package net.sourceforge.filebot.ui.panel.search;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
import net.sourceforge.filebot.web.Episode;
|
import net.sourceforge.filebot.web.Episode;
|
||||||
import net.sourceforge.filebot.web.EpisodeListClient;
|
import net.sourceforge.filebot.web.EpisodeListClient;
|
||||||
|
import net.sourceforge.filebot.web.SearchResult;
|
||||||
|
|
||||||
|
|
||||||
class FetchEpisodeListTask extends SwingWorker<List<Episode>, Void> {
|
class FetchEpisodeListTask extends SwingWorker<List<Episode>, Void> {
|
||||||
|
|
||||||
private final String showName;
|
private final SearchResult searchResult;
|
||||||
private final EpisodeListClient searchEngine;
|
private final EpisodeListClient searchEngine;
|
||||||
private final int numberOfSeason;
|
private final int numberOfSeason;
|
||||||
|
|
||||||
private long duration = -1;
|
private long duration = -1;
|
||||||
|
|
||||||
|
|
||||||
public FetchEpisodeListTask(EpisodeListClient searchEngine, String showname, int numberOfSeason) {
|
public FetchEpisodeListTask(EpisodeListClient searchEngine, SearchResult searchResult, int numberOfSeason) {
|
||||||
showName = showname;
|
|
||||||
this.searchEngine = searchEngine;
|
this.searchEngine = searchEngine;
|
||||||
|
this.searchResult = searchResult;
|
||||||
this.numberOfSeason = numberOfSeason;
|
this.numberOfSeason = numberOfSeason;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,15 +33,25 @@ class FetchEpisodeListTask extends SwingWorker<List<Episode>, Void> {
|
||||||
protected List<Episode> doInBackground() throws Exception {
|
protected List<Episode> doInBackground() throws Exception {
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
List<Episode> episodes = searchEngine.getEpisodeList(showName, numberOfSeason);
|
Iterator<Episode> itr = searchEngine.getEpisodeList(searchResult, numberOfSeason);
|
||||||
|
|
||||||
|
ArrayList<Episode> list = new ArrayList<Episode>();
|
||||||
|
|
||||||
|
while (itr.hasNext())
|
||||||
|
list.add(itr.next());
|
||||||
|
|
||||||
duration = System.currentTimeMillis() - start;
|
duration = System.currentTimeMillis() - start;
|
||||||
return episodes;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getShowName() {
|
public EpisodeListClient getSearchEngine() {
|
||||||
return showName;
|
return searchEngine;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SearchResult getSearchResult() {
|
||||||
|
return searchResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,9 +64,4 @@ class FetchEpisodeListTask extends SwingWorker<List<Episode>, Void> {
|
||||||
return duration;
|
return duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public EpisodeListClient getSearchEngine() {
|
|
||||||
return searchEngine;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,8 @@ import java.awt.Window;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
import java.net.URL;
|
import java.net.URI;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
@ -32,8 +31,6 @@ import javax.swing.SwingUtilities;
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileBotUtil;
|
|
||||||
import net.sourceforge.filebot.Settings;
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
import net.sourceforge.filebot.ui.FileBotPanel;
|
import net.sourceforge.filebot.ui.FileBotPanel;
|
||||||
import net.sourceforge.filebot.ui.HistoryPanel;
|
import net.sourceforge.filebot.ui.HistoryPanel;
|
||||||
|
@ -43,10 +40,13 @@ import net.sourceforge.filebot.ui.transfer.SaveAction;
|
||||||
import net.sourceforge.filebot.ui.transfer.Saveable;
|
import net.sourceforge.filebot.ui.transfer.Saveable;
|
||||||
import net.sourceforge.filebot.web.Episode;
|
import net.sourceforge.filebot.web.Episode;
|
||||||
import net.sourceforge.filebot.web.EpisodeListClient;
|
import net.sourceforge.filebot.web.EpisodeListClient;
|
||||||
|
import net.sourceforge.filebot.web.SearchResult;
|
||||||
|
import net.sourceforge.tuned.ExceptionUtil;
|
||||||
import net.sourceforge.tuned.ui.SelectButton;
|
import net.sourceforge.tuned.ui.SelectButton;
|
||||||
|
import net.sourceforge.tuned.ui.SelectButtonTextField;
|
||||||
|
import net.sourceforge.tuned.ui.SimpleIconProvider;
|
||||||
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
||||||
import net.sourceforge.tuned.ui.TextCompletion;
|
import net.sourceforge.tuned.ui.TextCompletion;
|
||||||
import net.sourceforge.tuned.ui.TextFieldWithSelect;
|
|
||||||
import net.sourceforge.tuned.ui.TunedUtil;
|
import net.sourceforge.tuned.ui.TunedUtil;
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ public class SearchPanel extends FileBotPanel {
|
||||||
|
|
||||||
private SpinnerNumberModel seasonSpinnerModel = new SpinnerNumberModel(SeasonSpinnerEditor.ALL_SEASONS, SeasonSpinnerEditor.ALL_SEASONS, Integer.MAX_VALUE, 1);
|
private SpinnerNumberModel seasonSpinnerModel = new SpinnerNumberModel(SeasonSpinnerEditor.ALL_SEASONS, SeasonSpinnerEditor.ALL_SEASONS, Integer.MAX_VALUE, 1);
|
||||||
|
|
||||||
private TextFieldWithSelect<EpisodeListClient> searchField;
|
private SelectButtonTextField<EpisodeListClient> searchField;
|
||||||
|
|
||||||
private TextCompletion searchFieldCompletion;
|
private TextCompletion searchFieldCompletion;
|
||||||
|
|
||||||
|
@ -66,19 +66,12 @@ public class SearchPanel extends FileBotPanel {
|
||||||
public SearchPanel() {
|
public SearchPanel() {
|
||||||
super("Search", ResourceManager.getIcon("panel.search"));
|
super("Search", ResourceManager.getIcon("panel.search"));
|
||||||
|
|
||||||
List<SelectButton.Entry<EpisodeListClient>> episodeListClients = new ArrayList<SelectButton.Entry<EpisodeListClient>>();
|
searchField = new SelectButtonTextField<EpisodeListClient>();
|
||||||
|
|
||||||
for (EpisodeListClient client : EpisodeListClient.getAvailableEpisodeListClients()) {
|
searchField.getSelectButton().setModel(EpisodeListClient.getAvailableEpisodeListClients());
|
||||||
episodeListClients.add(new SelectButton.Entry<EpisodeListClient>(client, client.getIcon()));
|
searchField.getSelectButton().setIconProvider(SimpleIconProvider.forClass(EpisodeListClient.class));
|
||||||
}
|
|
||||||
|
|
||||||
searchField = new TextFieldWithSelect<EpisodeListClient>(episodeListClients);
|
searchField.getSelectButton().addPropertyChangeListener(SelectButton.SELECTED_VALUE, selectButtonListener);
|
||||||
searchField.getSelectButton().addPropertyChangeListener(SelectButton.SELECTED_VALUE_PROPERTY, searchFieldListener);
|
|
||||||
searchField.getTextField().setColumns(25);
|
|
||||||
|
|
||||||
searchFieldCompletion = new TextCompletion(searchField.getTextField());
|
|
||||||
searchFieldCompletion.addTerms(Settings.getSettings().getStringList(Settings.SEARCH_HISTORY));
|
|
||||||
searchFieldCompletion.hook();
|
|
||||||
|
|
||||||
historyPanel.setColumnHeader1("Show");
|
historyPanel.setColumnHeader1("Show");
|
||||||
historyPanel.setColumnHeader2("Number of Episodes");
|
historyPanel.setColumnHeader2("Number of Episodes");
|
||||||
|
@ -135,14 +128,14 @@ public class SearchPanel extends FileBotPanel {
|
||||||
seasonSpinnerModel.setValue(value);
|
seasonSpinnerModel.setValue(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final PropertyChangeListener searchFieldListener = new PropertyChangeListener() {
|
private final PropertyChangeListener selectButtonListener = new PropertyChangeListener() {
|
||||||
|
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
EpisodeListClient client = searchField.getSelectedValue();
|
EpisodeListClient client = searchField.getSelected();
|
||||||
|
|
||||||
if (!client.isSingleSeasonSupported()) {
|
if (!client.isSingleSeasonSupported()) {
|
||||||
seasonSpinnerModel.setMaximum(SeasonSpinnerEditor.ALL_SEASONS);
|
|
||||||
seasonSpinnerModel.setValue(SeasonSpinnerEditor.ALL_SEASONS);
|
seasonSpinnerModel.setValue(SeasonSpinnerEditor.ALL_SEASONS);
|
||||||
|
seasonSpinnerModel.setMaximum(SeasonSpinnerEditor.ALL_SEASONS);
|
||||||
} else {
|
} else {
|
||||||
seasonSpinnerModel.setMaximum(Integer.MAX_VALUE);
|
seasonSpinnerModel.setMaximum(Integer.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
@ -154,10 +147,9 @@ public class SearchPanel extends FileBotPanel {
|
||||||
private final AbstractAction searchAction = new AbstractAction("Find", ResourceManager.getIcon("action.find")) {
|
private final AbstractAction searchAction = new AbstractAction("Find", ResourceManager.getIcon("action.find")) {
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
searchField.clearTextSelection();
|
EpisodeListClient searchEngine = searchField.getSelected();
|
||||||
|
|
||||||
EpisodeListClient searchEngine = searchField.getSelectedValue();
|
SearchTask task = new SearchTask(searchEngine, searchField.getText(), seasonSpinnerModel.getNumber().intValue());
|
||||||
SearchTask task = new SearchTask(searchEngine, searchField.getTextField().getText(), seasonSpinnerModel.getNumber().intValue());
|
|
||||||
task.addPropertyChangeListener(new SearchTaskListener());
|
task.addPropertyChangeListener(new SearchTaskListener());
|
||||||
|
|
||||||
task.execute();
|
task.execute();
|
||||||
|
@ -182,10 +174,10 @@ public class SearchPanel extends FileBotPanel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
Component c = tabbedPane.getSelectedComponent();
|
Component comp = tabbedPane.getSelectedComponent();
|
||||||
|
|
||||||
if (c instanceof Saveable) {
|
if (comp instanceof Saveable) {
|
||||||
setSaveable((Saveable) c);
|
setSaveable((Saveable) comp);
|
||||||
super.actionPerformed(e);
|
super.actionPerformed(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,11 +185,11 @@ public class SearchPanel extends FileBotPanel {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
private class SearchTask extends SwingWorker<List<String>, Void> {
|
private class SearchTask extends SwingWorker<List<SearchResult>, Void> {
|
||||||
|
|
||||||
private String query;
|
private final String query;
|
||||||
private EpisodeListClient client;
|
private final EpisodeListClient client;
|
||||||
private int numberOfSeason;
|
private final int numberOfSeason;
|
||||||
|
|
||||||
|
|
||||||
public SearchTask(EpisodeListClient client, String query, int numberOfSeason) {
|
public SearchTask(EpisodeListClient client, String query, int numberOfSeason) {
|
||||||
|
@ -208,7 +200,7 @@ public class SearchPanel extends FileBotPanel {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> doInBackground() throws Exception {
|
protected List<SearchResult> doInBackground() throws Exception {
|
||||||
return client.search(query);
|
return client.search(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,14 +242,14 @@ public class SearchPanel extends FileBotPanel {
|
||||||
|
|
||||||
SearchTask task = (SearchTask) evt.getSource();
|
SearchTask task = (SearchTask) evt.getSource();
|
||||||
|
|
||||||
List<String> shows = null;
|
List<SearchResult> searchResults;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
shows = task.get();
|
searchResults = task.get();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
tabbedPane.remove(episodeList);
|
tabbedPane.remove(episodeList);
|
||||||
|
|
||||||
Throwable cause = FileBotUtil.getRootCause(e);
|
Throwable cause = ExceptionUtil.getRootCause(e);
|
||||||
|
|
||||||
MessageManager.showWarning(cause.getMessage());
|
MessageManager.showWarning(cause.getMessage());
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, cause.toString());
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, cause.toString());
|
||||||
|
@ -265,38 +257,43 @@ public class SearchPanel extends FileBotPanel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String showname = null;
|
SearchResult selectedResult = null;
|
||||||
|
/*
|
||||||
|
* NEEDED??? exact find without cache???
|
||||||
|
/// TODO: ??????
|
||||||
if (task.client.getFoundName(task.query) != null) {
|
if (task.client.getFoundName(task.query) != null) {
|
||||||
// a show matching the search term exactly has already been found
|
// a show matching the search term exactly has already been found
|
||||||
showname = task.client.getFoundName(task.query);
|
showname = task.client.getFoundName(task.query);
|
||||||
} else if (shows.size() == 1) {
|
}*/
|
||||||
|
|
||||||
|
if (searchResults.size() == 1) {
|
||||||
// only one show found, select this one
|
// only one show found, select this one
|
||||||
showname = shows.get(0);
|
selectedResult = searchResults.get(0);
|
||||||
} else if (shows.size() > 1) {
|
} else if (searchResults.size() > 1) {
|
||||||
// multiple shows found, let user selected one
|
// multiple shows found, let user selected one
|
||||||
Window window = SwingUtilities.getWindowAncestor(SearchPanel.this);
|
Window window = SwingUtilities.getWindowAncestor(SearchPanel.this);
|
||||||
|
|
||||||
SelectDialog<String> select = new SelectDialog<String>(window, shows);
|
SelectDialog<SearchResult> select = new SelectDialog<SearchResult>(window, searchResults);
|
||||||
|
|
||||||
select.setText("Select a Show:");
|
select.setText("Select a Show:");
|
||||||
select.setIconImage(episodeList.getIcon().getImage());
|
select.setIconImage(episodeList.getIcon().getImage());
|
||||||
select.setVisible(true);
|
select.setVisible(true);
|
||||||
|
|
||||||
showname = select.getSelectedValue();
|
selectedResult = select.getSelectedValue();
|
||||||
} else {
|
} else {
|
||||||
MessageManager.showWarning("\"" + task.query + "\" has not been found.");
|
MessageManager.showWarning("\"" + task.query + "\" has not been found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showname == null) {
|
if (selectedResult == null) {
|
||||||
tabbedPane.remove(episodeList);
|
tabbedPane.remove(episodeList);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchFieldCompletion.addTerm(showname);
|
String title = selectedResult.getName();
|
||||||
Settings.getSettings().putStringList(Settings.SEARCH_HISTORY, searchFieldCompletion.getTerms());
|
|
||||||
|
|
||||||
String title = showname;
|
searchFieldCompletion.addTerm(title);
|
||||||
|
//TODO fix
|
||||||
|
// Settings.getSettings().putStringList(Settings.SEARCH_HISTORY, searchFieldCompletion.getTerms());
|
||||||
|
|
||||||
if (task.numberOfSeason != SeasonSpinnerEditor.ALL_SEASONS) {
|
if (task.numberOfSeason != SeasonSpinnerEditor.ALL_SEASONS) {
|
||||||
title += String.format(" - Season %d", task.numberOfSeason);
|
title += String.format(" - Season %d", task.numberOfSeason);
|
||||||
|
@ -304,7 +301,7 @@ public class SearchPanel extends FileBotPanel {
|
||||||
|
|
||||||
episodeList.setTitle(title);
|
episodeList.setTitle(title);
|
||||||
|
|
||||||
FetchEpisodeListTask getEpisodesTask = new FetchEpisodeListTask(task.client, showname, task.numberOfSeason);
|
FetchEpisodeListTask getEpisodesTask = new FetchEpisodeListTask(task.client, selectedResult, task.numberOfSeason);
|
||||||
getEpisodesTask.addPropertyChangeListener(new FetchEpisodeListTaskListener(episodeList));
|
getEpisodesTask.addPropertyChangeListener(new FetchEpisodeListTaskListener(episodeList));
|
||||||
|
|
||||||
getEpisodesTask.execute();
|
getEpisodesTask.execute();
|
||||||
|
@ -331,13 +328,13 @@ public class SearchPanel extends FileBotPanel {
|
||||||
FetchEpisodeListTask task = (FetchEpisodeListTask) evt.getSource();
|
FetchEpisodeListTask task = (FetchEpisodeListTask) evt.getSource();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URL url = task.getSearchEngine().getEpisodeListUrl(task.getShowName(), task.getNumberOfSeason());
|
URI link = task.getSearchEngine().getEpisodeListLink(task.getSearchResult(), task.getNumberOfSeason());
|
||||||
|
|
||||||
Collection<Episode> episodes = task.get();
|
Collection<Episode> episodes = task.get();
|
||||||
|
|
||||||
String info = (episodes.size() > 0) ? String.format("%d episodes", episodes.size()) : "No episodes found";
|
String info = (episodes.size() > 0) ? String.format("%d episodes", episodes.size()) : "No episodes found";
|
||||||
|
|
||||||
historyPanel.add(episodeList.getTitle(), url, episodeList.getIcon(), info, NumberFormat.getInstance().format(task.getDuration()) + " ms");
|
historyPanel.add(episodeList.getTitle(), link, episodeList.getIcon(), info, NumberFormat.getInstance().format(task.getDuration()) + " ms");
|
||||||
|
|
||||||
if (episodes.size() <= 0)
|
if (episodes.size() <= 0)
|
||||||
tabbedPane.remove(episodeList);
|
tabbedPane.remove(episodeList);
|
||||||
|
@ -348,8 +345,10 @@ public class SearchPanel extends FileBotPanel {
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
tabbedPane.remove(episodeList);
|
tabbedPane.remove(episodeList);
|
||||||
|
|
||||||
MessageManager.showWarning(FileBotUtil.getRootCause(e).getMessage());
|
Throwable cause = ExceptionUtil.getRootCause(e);
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
|
||||||
|
MessageManager.showWarning(cause.getMessage());
|
||||||
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, cause.getMessage(), cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import javax.swing.event.TableModelEvent;
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
import net.sourceforge.tuned.FileUtil;
|
||||||
|
|
||||||
|
|
||||||
class ChecksumTableModel extends AbstractTableModel {
|
class ChecksumTableModel extends AbstractTableModel {
|
||||||
|
@ -38,7 +38,7 @@ class ChecksumTableModel extends AbstractTableModel {
|
||||||
|
|
||||||
if (columnIndex >= checksumColumnsOffset) {
|
if (columnIndex >= checksumColumnsOffset) {
|
||||||
File columnRoot = checksumColumnRoots.get(columnIndex - checksumColumnsOffset);
|
File columnRoot = checksumColumnRoots.get(columnIndex - checksumColumnsOffset);
|
||||||
return FileFormat.getFolderName(columnRoot);
|
return FileUtil.getFolderName(columnRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -17,13 +17,13 @@ import javax.swing.KeyStroke;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
import net.sourceforge.filebot.ui.FileBotPanel;
|
import net.sourceforge.filebot.ui.FileBotPanel;
|
||||||
import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
|
import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
|
||||||
import net.sourceforge.filebot.ui.SelectDialog;
|
import net.sourceforge.filebot.ui.SelectDialog;
|
||||||
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
import net.sourceforge.filebot.ui.transfer.LoadAction;
|
||||||
import net.sourceforge.filebot.ui.transfer.SaveAction;
|
import net.sourceforge.filebot.ui.transfer.SaveAction;
|
||||||
|
import net.sourceforge.tuned.FileUtil;
|
||||||
import net.sourceforge.tuned.MessageBus;
|
import net.sourceforge.tuned.MessageBus;
|
||||||
import net.sourceforge.tuned.ui.TunedUtil;
|
import net.sourceforge.tuned.ui.TunedUtil;
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ public class SfvPanel extends FileBotPanel {
|
||||||
@Override
|
@Override
|
||||||
protected String convertValueToString(Object value) {
|
protected String convertValueToString(Object value) {
|
||||||
File columnRoot = (File) value;
|
File columnRoot = (File) value;
|
||||||
return FileFormat.getFolderName(columnRoot);
|
return FileUtil.getFolderName(columnRoot);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ public class SfvPanel extends FileBotPanel {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
index = options.indexOf(selected);
|
index = options.indexOf(selected);
|
||||||
name = FileFormat.getFileName(selected);
|
name = FileUtil.getFileName(selected);
|
||||||
|
|
||||||
if (name.isEmpty())
|
if (name.isEmpty())
|
||||||
name = "name";
|
name = "name";
|
||||||
|
|
|
@ -16,7 +16,6 @@ import javax.swing.event.TableModelEvent;
|
||||||
import javax.swing.table.TableColumn;
|
import javax.swing.table.TableColumn;
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
|
||||||
import net.sourceforge.filebot.ui.panel.sfv.ChecksumTableModel.ChecksumTableModelEvent;
|
import net.sourceforge.filebot.ui.panel.sfv.ChecksumTableModel.ChecksumTableModelEvent;
|
||||||
import net.sourceforge.filebot.ui.panel.sfv.renderer.ChecksumTableCellRenderer;
|
import net.sourceforge.filebot.ui.panel.sfv.renderer.ChecksumTableCellRenderer;
|
||||||
import net.sourceforge.filebot.ui.panel.sfv.renderer.StateIconTableCellRenderer;
|
import net.sourceforge.filebot.ui.panel.sfv.renderer.StateIconTableCellRenderer;
|
||||||
|
@ -27,6 +26,7 @@ import net.sourceforge.filebot.ui.transfer.Saveable;
|
||||||
import net.sourceforge.filebot.ui.transfer.SaveableExportHandler;
|
import net.sourceforge.filebot.ui.transfer.SaveableExportHandler;
|
||||||
import net.sourceforge.filebot.ui.transfer.TransferablePolicyImportHandler;
|
import net.sourceforge.filebot.ui.transfer.TransferablePolicyImportHandler;
|
||||||
import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy;
|
import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy;
|
||||||
|
import net.sourceforge.tuned.FileUtil;
|
||||||
|
|
||||||
|
|
||||||
class SfvTable extends JTable implements Saveable {
|
class SfvTable extends JTable implements Saveable {
|
||||||
|
@ -103,7 +103,7 @@ class SfvTable extends JTable implements Saveable {
|
||||||
String name = "";
|
String name = "";
|
||||||
|
|
||||||
if (columnRoot != null)
|
if (columnRoot != null)
|
||||||
name = FileFormat.getFileName(columnRoot);
|
name = FileUtil.getFileName(columnRoot);
|
||||||
|
|
||||||
if (name.isEmpty())
|
if (name.isEmpty())
|
||||||
name = "name";
|
name = "name";
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot.ui.panel.subtitle;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.swing.SwingWorker;
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.web.MovieDescriptor;
|
|
||||||
import net.sourceforge.filebot.web.SubtitleClient;
|
|
||||||
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
|
||||||
|
|
||||||
|
|
||||||
class FetchSubtitleListTask extends SwingWorker<List<? extends SubtitleDescriptor>, Void> {
|
|
||||||
|
|
||||||
private final SubtitleClient client;
|
|
||||||
private final MovieDescriptor descriptor;
|
|
||||||
|
|
||||||
private long duration = -1;
|
|
||||||
|
|
||||||
|
|
||||||
public FetchSubtitleListTask(MovieDescriptor descriptor, SubtitleClient client) {
|
|
||||||
this.descriptor = descriptor;
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<? extends SubtitleDescriptor> doInBackground() throws Exception {
|
|
||||||
long start = System.currentTimeMillis();
|
|
||||||
|
|
||||||
List<? extends SubtitleDescriptor> list = client.getSubtitleList(descriptor);
|
|
||||||
|
|
||||||
duration = System.currentTimeMillis() - start;
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public SubtitleClient getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public MovieDescriptor getDescriptor() {
|
|
||||||
return descriptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public long getDuration() {
|
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
|
|
||||||
|
|
||||||
|
class Language implements Comparable<Language> {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final Locale locale;
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final ImageIcon icon;
|
||||||
|
|
||||||
|
|
||||||
|
public Language(String languageName) {
|
||||||
|
this.name = languageName;
|
||||||
|
this.locale = LanguageResolver.getDefault().getLocale(name);
|
||||||
|
|
||||||
|
this.code = (locale != null ? locale.getLanguage() : null);
|
||||||
|
|
||||||
|
this.icon = ResourceManager.getFlagIcon(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Locale getLocale() {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Icon getIcon() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj instanceof Language) {
|
||||||
|
return getName().equalsIgnoreCase(((Language) obj).getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Language language) {
|
||||||
|
return getName().compareToIgnoreCase(language.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import ca.odell.glazedlists.matchers.Matcher;
|
||||||
|
|
||||||
|
|
||||||
|
class LanguageMatcher implements Matcher<SubtitlePackage> {
|
||||||
|
|
||||||
|
private final Set<Language> languages;
|
||||||
|
|
||||||
|
|
||||||
|
public LanguageMatcher(Collection<Language> languages) {
|
||||||
|
this.languages = new TreeSet<Language>(languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(SubtitlePackage item) {
|
||||||
|
return languages.contains(item.getLanguage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||||
|
|
||||||
|
|
||||||
|
import ca.odell.glazedlists.EventList;
|
||||||
|
import ca.odell.glazedlists.event.ListEvent;
|
||||||
|
import ca.odell.glazedlists.event.ListEventListener;
|
||||||
|
import ca.odell.glazedlists.matchers.AbstractMatcherEditor;
|
||||||
|
import ca.odell.glazedlists.matchers.Matcher;
|
||||||
|
|
||||||
|
|
||||||
|
public class LanguageMatcherEditor extends AbstractMatcherEditor<SubtitlePackage> {
|
||||||
|
|
||||||
|
private final EventList<Language> languages;
|
||||||
|
|
||||||
|
|
||||||
|
public LanguageMatcherEditor(LanguageSelectionPanel languageSelectionPanel) {
|
||||||
|
this(languageSelectionPanel.getSelected());
|
||||||
|
currentMatcher = new LanguageMatcher(this.languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public LanguageMatcherEditor(EventList<Language> languages) {
|
||||||
|
this.languages = languages;
|
||||||
|
|
||||||
|
languages.addListEventListener(new LanguageSelectionListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class LanguageSelectionListener implements ListEventListener<Language> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void listChanged(ListEvent<Language> listChanges) {
|
||||||
|
boolean insert = false;
|
||||||
|
boolean delete = false;
|
||||||
|
|
||||||
|
while (listChanges.next()) {
|
||||||
|
int type = listChanges.getType();
|
||||||
|
|
||||||
|
insert |= (type == ListEvent.INSERT);
|
||||||
|
delete |= (type == ListEvent.DELETE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insert || delete) {
|
||||||
|
Matcher<SubtitlePackage> matcher = new LanguageMatcher(languages);
|
||||||
|
|
||||||
|
if (insert && !delete) {
|
||||||
|
fireRelaxed(matcher);
|
||||||
|
} else if (!insert && delete) {
|
||||||
|
fireConstrained(matcher);
|
||||||
|
} else {
|
||||||
|
fireChanged(matcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||||
|
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Cursor;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.FlowLayout;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.event.ItemEvent;
|
||||||
|
import java.awt.event.ItemListener;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JToggleButton;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.Settings;
|
||||||
|
import ca.odell.glazedlists.EventList;
|
||||||
|
import ca.odell.glazedlists.FunctionList;
|
||||||
|
import ca.odell.glazedlists.ListSelection;
|
||||||
|
import ca.odell.glazedlists.UniqueList;
|
||||||
|
import ca.odell.glazedlists.FunctionList.Function;
|
||||||
|
import ca.odell.glazedlists.event.ListEvent;
|
||||||
|
import ca.odell.glazedlists.event.ListEventListener;
|
||||||
|
|
||||||
|
|
||||||
|
public class LanguageSelectionPanel extends JPanel {
|
||||||
|
|
||||||
|
private final ListSelection<Language> selectionModel;
|
||||||
|
|
||||||
|
private final Map<String, Boolean> defaultSelection = new TreeMap<String, Boolean>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
private final Map<String, Boolean> globalSelection = Settings.getSettings().asBooleanMap(Settings.SUBTITLE_LANGUAGE);
|
||||||
|
|
||||||
|
|
||||||
|
public LanguageSelectionPanel(EventList<SubtitlePackage> source) {
|
||||||
|
super(new FlowLayout(FlowLayout.RIGHT, 5, 1));
|
||||||
|
|
||||||
|
defaultSelection.putAll(globalSelection);
|
||||||
|
|
||||||
|
EventList<Language> languageList = new FunctionList<SubtitlePackage, Language>(source, new LanguageFunction());
|
||||||
|
EventList<Language> languageSet = new UniqueList<Language>(languageList);
|
||||||
|
|
||||||
|
selectionModel = new ListSelection<Language>(languageSet);
|
||||||
|
|
||||||
|
selectionModel.getSource().addListEventListener(new SourceChangeHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public EventList<Language> getSelected() {
|
||||||
|
return selectionModel.getSelected();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private boolean isSelectedByDefault(Language language) {
|
||||||
|
Boolean selected = defaultSelection.get(language.getName());
|
||||||
|
|
||||||
|
if (selected != null)
|
||||||
|
return selected;
|
||||||
|
|
||||||
|
// deselected by default
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void setSelected(Language language, boolean selected) {
|
||||||
|
String key = language.getName();
|
||||||
|
|
||||||
|
defaultSelection.put(key, selected);
|
||||||
|
globalSelection.put(key, selected);
|
||||||
|
|
||||||
|
if (selected)
|
||||||
|
selectionModel.select(language);
|
||||||
|
else
|
||||||
|
selectionModel.deselect(selectionModel.getSource().indexOf(language));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide the binding between this panel and the source {@link EventList}.
|
||||||
|
*/
|
||||||
|
private class SourceChangeHandler implements ListEventListener<Language> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an inserted element.
|
||||||
|
*/
|
||||||
|
private void insert(int index) {
|
||||||
|
Language language = selectionModel.getSource().get(index);
|
||||||
|
|
||||||
|
LanguageToggleButton button = new LanguageToggleButton(language);
|
||||||
|
button.setSelected(isSelectedByDefault(language));
|
||||||
|
|
||||||
|
add(button, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a deleted element.
|
||||||
|
*/
|
||||||
|
private void delete(int index) {
|
||||||
|
remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the components list changes, this updates the panel.
|
||||||
|
*/
|
||||||
|
public void listChanged(ListEvent<Language> listChanges) {
|
||||||
|
while (listChanges.next()) {
|
||||||
|
int type = listChanges.getType();
|
||||||
|
int index = listChanges.getIndex();
|
||||||
|
|
||||||
|
if (type == ListEvent.INSERT) {
|
||||||
|
insert(index);
|
||||||
|
} else if (type == ListEvent.DELETE) {
|
||||||
|
delete(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// repaint the panel
|
||||||
|
revalidate();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class LanguageToggleButton extends JToggleButton implements ItemListener {
|
||||||
|
|
||||||
|
private final Language language;
|
||||||
|
|
||||||
|
|
||||||
|
public LanguageToggleButton(Language language) {
|
||||||
|
super(language.getIcon());
|
||||||
|
|
||||||
|
this.language = language;
|
||||||
|
|
||||||
|
setToolTipText(language.getName());
|
||||||
|
setContentAreaFilled(false);
|
||||||
|
setFocusPainted(false);
|
||||||
|
|
||||||
|
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||||
|
setPreferredSize(new Dimension(getIcon().getIconWidth(), getIcon().getIconHeight()));
|
||||||
|
|
||||||
|
addItemListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
|
// make transparent if not selected
|
||||||
|
if (!isSelected()) {
|
||||||
|
AlphaComposite composite = AlphaComposite.SrcOver.derive(0.2f);
|
||||||
|
g2d.setComposite(composite);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.paintComponent(g2d);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void itemStateChanged(ItemEvent e) {
|
||||||
|
LanguageSelectionPanel.this.setSelected(language, isSelected());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class LanguageFunction implements Function<SubtitlePackage, Language> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Language evaluate(SubtitlePackage sourceValue) {
|
||||||
|
return sourceValue.getLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -46,9 +46,9 @@ public class SubtitleCellRenderer extends IconViewCellRenderer {
|
||||||
|
|
||||||
setText(subtitle.getName());
|
setText(subtitle.getName());
|
||||||
|
|
||||||
info1.setText(subtitle.getLanguageName());
|
info1.setText(subtitle.getLanguage().getName());
|
||||||
|
|
||||||
icon = subtitle.getLanguageIcon();
|
icon = subtitle.getLanguage().getIcon();
|
||||||
|
|
||||||
info1.setIcon(icon);
|
info1.setIcon(icon);
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,10 @@
|
||||||
package net.sourceforge.filebot.ui.panel.subtitle;
|
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||||
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.awt.BorderLayout;
|
||||||
|
|
||||||
import javax.swing.BoxLayout;
|
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
|
||||||
import net.sourceforge.tuned.ui.SimpleListModel;
|
|
||||||
|
|
||||||
|
|
||||||
public class SubtitleDownloadPanel extends JPanel {
|
public class SubtitleDownloadPanel extends JPanel {
|
||||||
|
|
||||||
|
@ -17,9 +13,9 @@ public class SubtitleDownloadPanel extends JPanel {
|
||||||
|
|
||||||
|
|
||||||
public SubtitleDownloadPanel() {
|
public SubtitleDownloadPanel() {
|
||||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
setLayout(new BorderLayout());
|
||||||
|
|
||||||
add(packagePanel);
|
add(packagePanel, BorderLayout.CENTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,15 +23,4 @@ public class SubtitleDownloadPanel extends JPanel {
|
||||||
return packagePanel;
|
return packagePanel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addSubtitleDescriptors(List<? extends SubtitleDescriptor> subtitleDescriptors) {
|
|
||||||
SimpleListModel model = new SimpleListModel();
|
|
||||||
|
|
||||||
for (SubtitleDescriptor subtitleDescriptor : subtitleDescriptors) {
|
|
||||||
model.add(new SubtitlePackage(subtitleDescriptor));
|
|
||||||
}
|
|
||||||
//TODO real add, not setModel
|
|
||||||
packagePanel.setModel(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,34 +3,42 @@ package net.sourceforge.filebot.ui.panel.subtitle;
|
||||||
|
|
||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
|
||||||
import java.beans.PropertyChangeSupport;
|
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.SwingWorker.StateValue;
|
||||||
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
||||||
import net.sourceforge.tuned.DownloadTask;
|
import net.sourceforge.tuned.DownloadTask;
|
||||||
|
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
||||||
|
|
||||||
|
import org.jdesktop.beans.AbstractBean;
|
||||||
|
|
||||||
|
|
||||||
public class SubtitlePackage {
|
public class SubtitlePackage extends AbstractBean {
|
||||||
|
|
||||||
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
|
|
||||||
|
|
||||||
private final SubtitleDescriptor subtitleDescriptor;
|
private final SubtitleDescriptor subtitleDescriptor;
|
||||||
|
|
||||||
|
private final ArchiveType archiveType;
|
||||||
|
|
||||||
private final ImageIcon archiveIcon;
|
private final ImageIcon archiveIcon;
|
||||||
|
|
||||||
private final ImageIcon languageIcon;
|
private final Language language;
|
||||||
|
|
||||||
private DownloadTask downloadTask;
|
private DownloadTask downloadTask;
|
||||||
|
|
||||||
|
private StateValue downloadState = StateValue.PENDING;
|
||||||
|
|
||||||
|
private float downloadProgress = 0;
|
||||||
|
|
||||||
|
|
||||||
public SubtitlePackage(SubtitleDescriptor subtitleDescriptor) {
|
public SubtitlePackage(SubtitleDescriptor subtitleDescriptor) {
|
||||||
this.subtitleDescriptor = subtitleDescriptor;
|
this.subtitleDescriptor = subtitleDescriptor;
|
||||||
|
|
||||||
archiveIcon = ResourceManager.getArchiveIcon(subtitleDescriptor.getArchiveType());
|
this.language = new Language(subtitleDescriptor.getLanguageName());
|
||||||
languageIcon = ResourceManager.getFlagIcon(LanguageResolver.getDefault().getLanguageCode(subtitleDescriptor.getLanguageName()));
|
|
||||||
|
this.archiveType = ArchiveType.forName(subtitleDescriptor.getArchiveType());
|
||||||
|
this.archiveIcon = ResourceManager.getArchiveIcon(archiveType.getExtension());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,8 +47,13 @@ public class SubtitlePackage {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Language getLanguage() {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public ArchiveType getArchiveType() {
|
public ArchiveType getArchiveType() {
|
||||||
return ArchiveType.forName(subtitleDescriptor.getArchiveType());
|
return archiveType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,17 +62,13 @@ public class SubtitlePackage {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getLanguageName() {
|
@Override
|
||||||
return subtitleDescriptor.getLanguageName();
|
public String toString() {
|
||||||
|
return getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ImageIcon getLanguageIcon() {
|
public synchronized void download() {
|
||||||
return languageIcon;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public synchronized void startDownload() {
|
|
||||||
if (downloadTask != null)
|
if (downloadTask != null)
|
||||||
throw new IllegalStateException("Download has been started already");
|
throw new IllegalStateException("Download has been started already");
|
||||||
|
|
||||||
|
@ -70,31 +79,46 @@ public class SubtitlePackage {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public DownloadTask getDownloadTask() {
|
public StateValue getDownloadState() {
|
||||||
if (downloadTask == null)
|
return downloadState;
|
||||||
throw new IllegalStateException("Download has not been started");
|
|
||||||
|
|
||||||
return downloadTask;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
private void setDownloadState(StateValue downloadState) {
|
||||||
propertyChangeSupport.addPropertyChangeListener(listener);
|
this.downloadState = downloadState;
|
||||||
|
firePropertyChange("downloadState", null, downloadState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
public float getDownloadProgress() {
|
||||||
propertyChangeSupport.removePropertyChangeListener(listener);
|
return downloadProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class DownloadTaskPropertyChangeAdapter implements PropertyChangeListener {
|
private void setDownloadProgress(float downloadProgress) {
|
||||||
|
this.downloadProgress = downloadProgress;
|
||||||
|
firePropertyChange("downloadProgress", null, downloadProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class DownloadTaskPropertyChangeAdapter extends SwingWorkerPropertyChangeAdapter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void started(PropertyChangeEvent evt) {
|
||||||
propertyChangeSupport.firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
|
setDownloadState(StateValue.STARTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void done(PropertyChangeEvent evt) {
|
||||||
|
setDownloadState(StateValue.DONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void progress(PropertyChangeEvent evt) {
|
||||||
|
setDownloadProgress((Float) evt.getNewValue() / 100);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,186 +2,168 @@
|
||||||
package net.sourceforge.filebot.ui.panel.subtitle;
|
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.AlphaComposite;
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Cursor;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.FlowLayout;
|
|
||||||
import java.awt.Graphics;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.event.ItemEvent;
|
|
||||||
import java.awt.event.ItemListener;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.JList;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JToggleButton;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.ListModel;
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.Settings;
|
import ca.odell.glazedlists.BasicEventList;
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import ca.odell.glazedlists.EventList;
|
||||||
import net.sourceforge.tuned.ui.IconViewPanel;
|
import ca.odell.glazedlists.FilterList;
|
||||||
import net.sourceforge.tuned.ui.SimpleListModel;
|
import ca.odell.glazedlists.GlazedLists;
|
||||||
|
import ca.odell.glazedlists.ObservableElementList;
|
||||||
|
import ca.odell.glazedlists.swing.EventListModel;
|
||||||
|
|
||||||
|
|
||||||
public class SubtitlePackagePanel extends IconViewPanel {
|
public class SubtitlePackagePanel extends JPanel {
|
||||||
|
|
||||||
private final JPanel languageFilterPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 1));
|
private final EventList<SubtitlePackage> model = new BasicEventList<SubtitlePackage>();
|
||||||
|
|
||||||
private ListModel unfilteredModel = new SimpleListModel();
|
private final LanguageSelectionPanel languageSelection = new LanguageSelectionPanel(model);
|
||||||
private Map<String, Boolean> languageFilterSelection = new TreeMap<String, Boolean>(String.CASE_INSENSITIVE_ORDER);
|
|
||||||
|
|
||||||
|
|
||||||
public SubtitlePackagePanel() {
|
public SubtitlePackagePanel() {
|
||||||
setCellRenderer(new SubtitleCellRenderer());
|
super(new BorderLayout());
|
||||||
|
add(languageSelection, BorderLayout.NORTH);
|
||||||
languageFilterPanel.setOpaque(false);
|
add(new JScrollPane(createList()), BorderLayout.CENTER);
|
||||||
|
|
||||||
getHeaderPanel().add(languageFilterPanel, BorderLayout.EAST);
|
|
||||||
|
|
||||||
languageFilterSelection.putAll(Settings.getSettings().getBooleanMap(Settings.SUBTITLE_LANGUAGE));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
public EventList<SubtitlePackage> getModel() {
|
||||||
public void setModel(ListModel model) {
|
return model;
|
||||||
unfilteredModel = model;
|
|
||||||
|
|
||||||
updateLanguageFilterButtonPanel();
|
|
||||||
|
|
||||||
updateFilteredModel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
protected JList createList() {
|
||||||
public ListModel getModel() {
|
FilterList<SubtitlePackage> filterList = new FilterList<SubtitlePackage>(model, new LanguageMatcherEditor(languageSelection));
|
||||||
return unfilteredModel;
|
ObservableElementList<SubtitlePackage> observableList = new ObservableElementList<SubtitlePackage>(filterList, GlazedLists.beanConnector(SubtitlePackage.class));
|
||||||
|
|
||||||
|
JList list = new JList(new EventListModel<SubtitlePackage>(observableList));
|
||||||
|
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
private void updateLanguageFilterButtonPanel() {
|
||||||
|
|
||||||
private void updateLanguageFilterButtonPanel() {
|
SortedSet<String> languages = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
SortedSet<String> languages = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
|
for (int i = 0; i < unfilteredModel.getSize(); i++) {
|
||||||
|
SubtitlePackage subtitle = (SubtitlePackage) unfilteredModel.getElementAt(i);
|
||||||
|
languages.add(subtitle.getLanguageName());
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < unfilteredModel.getSize(); i++) {
|
languageFilterPanel.removeAll();
|
||||||
SubtitlePackage subtitle = (SubtitlePackage) unfilteredModel.getElementAt(i);
|
|
||||||
languages.add(subtitle.getLanguageName());
|
|
||||||
}
|
|
||||||
|
|
||||||
languageFilterPanel.removeAll();
|
for (String language : languages) {
|
||||||
|
LanguageFilterButton languageFilterButton = createLanguageFilterButton(language);
|
||||||
|
languageFilterButton.addItemListener(new LanguageFilterItemListener(language));
|
||||||
|
|
||||||
for (String language : languages) {
|
languageFilterPanel.add(languageFilterButton);
|
||||||
LanguageFilterButton languageFilterButton = createLanguageFilterButton(language);
|
|
||||||
languageFilterButton.addItemListener(new LanguageFilterItemListener(language));
|
|
||||||
|
|
||||||
languageFilterPanel.add(languageFilterButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void updateFilteredModel() {
|
|
||||||
SimpleListModel model = new SimpleListModel();
|
|
||||||
|
|
||||||
for (int i = 0; i < unfilteredModel.getSize(); i++) {
|
|
||||||
SubtitlePackage subtitle = (SubtitlePackage) unfilteredModel.getElementAt(i);
|
|
||||||
|
|
||||||
if (isLanguageSelected(subtitle.getLanguageName())) {
|
|
||||||
model.add(subtitle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.setModel(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
private void updateFilteredModel() {
|
||||||
|
SimpleListModel model = new SimpleListModel();
|
||||||
|
|
||||||
public boolean isLanguageSelected(String language) {
|
for (int i = 0; i < unfilteredModel.getSize(); i++) {
|
||||||
return !languageFilterSelection.containsKey(language) || languageFilterSelection.get(language);
|
SubtitlePackage subtitle = (SubtitlePackage) unfilteredModel.getElementAt(i);
|
||||||
}
|
|
||||||
|
|
||||||
|
if (isLanguageSelected(subtitle.getLanguageName())) {
|
||||||
public void setLanguageSelected(String language, boolean selected) {
|
model.add(subtitle);
|
||||||
languageFilterSelection.put(language, selected);
|
}
|
||||||
Settings.getSettings().putBooleanMapEntry(Settings.SUBTITLE_LANGUAGE, language, selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private LanguageFilterButton createLanguageFilterButton(String language) {
|
|
||||||
Locale locale = LanguageResolver.getDefault().getLocale(language);
|
|
||||||
|
|
||||||
boolean selected = isLanguageSelected(language);
|
|
||||||
|
|
||||||
if (locale != null)
|
|
||||||
return new LanguageFilterButton(locale, selected);
|
|
||||||
else
|
|
||||||
return new LanguageFilterButton(language, selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class LanguageFilterItemListener implements ItemListener {
|
|
||||||
|
|
||||||
private final String language;
|
|
||||||
|
|
||||||
|
|
||||||
public LanguageFilterItemListener(String language) {
|
|
||||||
this.language = language;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void itemStateChanged(ItemEvent e) {
|
|
||||||
boolean selected = (e.getStateChange() == ItemEvent.SELECTED);
|
|
||||||
|
|
||||||
setLanguageSelected(language, selected);
|
|
||||||
|
|
||||||
updateFilteredModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private class LanguageFilterButton extends JToggleButton {
|
|
||||||
|
|
||||||
public LanguageFilterButton(Locale locale, boolean selected) {
|
|
||||||
this(locale.getDisplayLanguage(Locale.ENGLISH), ResourceManager.getFlagIcon(locale.getLanguage()), selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public LanguageFilterButton(String language, boolean selected) {
|
|
||||||
this(language, ResourceManager.getFlagIcon(null), selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public LanguageFilterButton(String language, Icon icon, boolean selected) {
|
|
||||||
super(icon, selected);
|
|
||||||
|
|
||||||
setToolTipText(language);
|
|
||||||
setContentAreaFilled(false);
|
|
||||||
setFocusPainted(false);
|
|
||||||
|
|
||||||
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
|
||||||
|
|
||||||
setPreferredSize(new Dimension(icon.getIconWidth(), icon.getIconHeight()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void paintComponent(Graphics g) {
|
|
||||||
Graphics2D g2d = (Graphics2D) g;
|
|
||||||
|
|
||||||
// make transparent if not selected
|
|
||||||
if (!isSelected()) {
|
|
||||||
AlphaComposite composite = AlphaComposite.SrcOver.derive(0.2f);
|
|
||||||
g2d.setComposite(composite);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
super.paintComponent(g2d);
|
super.setModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public boolean isLanguageSelected(String language) {
|
||||||
|
return !languageFilterSelection.containsKey(language) || languageFilterSelection.get(language);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setLanguageSelected(String language, boolean selected) {
|
||||||
|
languageFilterSelection.put(language, selected);
|
||||||
|
|
||||||
|
Settings.getSettings().asBooleanMap(Settings.SUBTITLE_LANGUAGE).put(language, selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private LanguageFilterButton createLanguageFilterButton(String language) {
|
||||||
|
Locale locale = LanguageResolver.getDefault().getLocale(language);
|
||||||
|
|
||||||
|
boolean selected = isLanguageSelected(language);
|
||||||
|
|
||||||
|
if (locale != null)
|
||||||
|
return new LanguageFilterButton(locale, selected);
|
||||||
|
else
|
||||||
|
return new LanguageFilterButton(language, selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class LanguageFilterItemListener implements ItemListener {
|
||||||
|
|
||||||
|
private final String language;
|
||||||
|
|
||||||
|
|
||||||
|
public LanguageFilterItemListener(String language) {
|
||||||
|
this.language = language;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void itemStateChanged(ItemEvent e) {
|
||||||
|
boolean selected = (e.getStateChange() == ItemEvent.SELECTED);
|
||||||
|
|
||||||
|
setLanguageSelected(language, selected);
|
||||||
|
|
||||||
|
updateFilteredModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private class LanguageFilterButton extends JToggleButton {
|
||||||
|
|
||||||
|
public LanguageFilterButton(Locale locale, boolean selected) {
|
||||||
|
this(locale.getDisplayLanguage(Locale.ENGLISH), ResourceManager.getFlagIcon(locale.getLanguage()), selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public LanguageFilterButton(String language, boolean selected) {
|
||||||
|
this(language, ResourceManager.getFlagIcon(null), selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public LanguageFilterButton(String language, Icon icon, boolean selected) {
|
||||||
|
super(icon, selected);
|
||||||
|
|
||||||
|
setToolTipText(language);
|
||||||
|
setContentAreaFilled(false);
|
||||||
|
setFocusPainted(false);
|
||||||
|
|
||||||
|
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||||
|
|
||||||
|
setPreferredSize(new Dimension(icon.getIconWidth(), icon.getIconHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintComponent(Graphics g) {
|
||||||
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
|
// make transparent if not selected
|
||||||
|
if (!isSelected()) {
|
||||||
|
AlphaComposite composite = AlphaComposite.SrcOver.derive(0.2f);
|
||||||
|
g2d.setComposite(composite);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.paintComponent(g2d);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,285 +2,122 @@
|
||||||
package net.sourceforge.filebot.ui.panel.subtitle;
|
package net.sourceforge.filebot.ui.panel.subtitle;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.net.URI;
|
||||||
import java.awt.Window;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.beans.PropertyChangeEvent;
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.Box;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JTabbedPane;
|
|
||||||
import javax.swing.KeyStroke;
|
|
||||||
import javax.swing.ScrollPaneConstants;
|
|
||||||
import javax.swing.SwingConstants;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.SwingWorker;
|
|
||||||
import javax.swing.border.EmptyBorder;
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileBotUtil;
|
|
||||||
import net.sourceforge.filebot.Settings;
|
import net.sourceforge.filebot.Settings;
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
import net.sourceforge.filebot.ui.FileBotPanel;
|
import net.sourceforge.filebot.ui.AbstractSearchPanel;
|
||||||
import net.sourceforge.filebot.ui.HistoryPanel;
|
|
||||||
import net.sourceforge.filebot.ui.MessageManager;
|
|
||||||
import net.sourceforge.filebot.ui.SelectDialog;
|
import net.sourceforge.filebot.ui.SelectDialog;
|
||||||
import net.sourceforge.filebot.web.MovieDescriptor;
|
import net.sourceforge.filebot.web.SearchResult;
|
||||||
import net.sourceforge.filebot.web.SubtitleClient;
|
import net.sourceforge.filebot.web.SubtitleClient;
|
||||||
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
import net.sourceforge.filebot.web.SubtitleDescriptor;
|
||||||
import net.sourceforge.tuned.ui.SelectButton;
|
import net.sourceforge.tuned.BasicCachingList;
|
||||||
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
|
import net.sourceforge.tuned.FunctionIterator;
|
||||||
import net.sourceforge.tuned.ui.TextCompletion;
|
import net.sourceforge.tuned.ProgressIterator;
|
||||||
import net.sourceforge.tuned.ui.TextFieldWithSelect;
|
import net.sourceforge.tuned.FunctionIterator.Function;
|
||||||
import net.sourceforge.tuned.ui.TunedUtil;
|
import net.sourceforge.tuned.ui.SimpleIconProvider;
|
||||||
|
import ca.odell.glazedlists.EventList;
|
||||||
|
import ca.odell.glazedlists.GlazedLists;
|
||||||
|
|
||||||
|
|
||||||
public class SubtitlePanel extends FileBotPanel {
|
public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitlePackage, SubtitleDownloadPanel> {
|
||||||
|
|
||||||
private JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
|
|
||||||
|
|
||||||
private HistoryPanel historyPanel = new HistoryPanel();
|
|
||||||
|
|
||||||
private TextFieldWithSelect<SubtitleClient> searchField;
|
|
||||||
|
|
||||||
private TextCompletion searchFieldCompletion;
|
|
||||||
|
|
||||||
|
|
||||||
public SubtitlePanel() {
|
public SubtitlePanel() {
|
||||||
super("Subtitle", ResourceManager.getIcon("panel.subtitle"));
|
super("Subtitle", ResourceManager.getIcon("panel.subtitle"), globalSearchHistory());
|
||||||
|
|
||||||
List<SelectButton.Entry<SubtitleClient>> clients = new ArrayList<SelectButton.Entry<SubtitleClient>>();
|
getHistoryPanel().setColumnHeader1("Show / Movie");
|
||||||
|
getHistoryPanel().setColumnHeader2("Number of Subtitles");
|
||||||
|
|
||||||
for (SubtitleClient client : SubtitleClient.getAvailableSubtitleClients()) {
|
getSearchField().getSelectButton().setModel(SubtitleClient.getAvailableSubtitleClients());
|
||||||
clients.add(new SelectButton.Entry<SubtitleClient>(client, client.getIcon()));
|
getSearchField().getSelectButton().setIconProvider(SimpleIconProvider.forClass(SubtitleClient.class));
|
||||||
}
|
|
||||||
|
|
||||||
searchField = new TextFieldWithSelect<SubtitleClient>(clients);
|
|
||||||
searchField.getTextField().setColumns(25);
|
|
||||||
|
|
||||||
searchFieldCompletion = new TextCompletion(searchField.getTextField());
|
|
||||||
searchFieldCompletion.addTerms(Settings.getSettings().getStringList(Settings.SUBTITLE_HISTORY));
|
|
||||||
searchFieldCompletion.hook();
|
|
||||||
|
|
||||||
historyPanel.setColumnHeader1("Show / Movie");
|
|
||||||
historyPanel.setColumnHeader2("Number of Subtitles");
|
|
||||||
historyPanel.setColumnHeader3("Duration");
|
|
||||||
|
|
||||||
JPanel mainPanel = new JPanel(new BorderLayout(5, 5));
|
|
||||||
|
|
||||||
Box searchBox = Box.createHorizontalBox();
|
|
||||||
searchBox.setBorder(new EmptyBorder(5, 5, 5, 5));
|
|
||||||
|
|
||||||
searchField.setMaximumSize(searchField.getPreferredSize());
|
|
||||||
|
|
||||||
searchBox.add(Box.createHorizontalGlue());
|
|
||||||
searchBox.add(searchField);
|
|
||||||
searchBox.add(Box.createHorizontalStrut(15));
|
|
||||||
searchBox.add(new JButton(searchAction));
|
|
||||||
searchBox.add(Box.createHorizontalGlue());
|
|
||||||
|
|
||||||
JPanel centerPanel = new JPanel(new BorderLayout());
|
|
||||||
centerPanel.setBorder(BorderFactory.createTitledBorder("Search Results"));
|
|
||||||
|
|
||||||
Box buttonBox = Box.createHorizontalBox();
|
|
||||||
buttonBox.setBorder(new EmptyBorder(5, 5, 5, 5));
|
|
||||||
buttonBox.add(Box.createHorizontalGlue());
|
|
||||||
buttonBox.add(new JButton(saveAction));
|
|
||||||
buttonBox.add(Box.createHorizontalGlue());
|
|
||||||
|
|
||||||
centerPanel.add(tabbedPane, BorderLayout.CENTER);
|
|
||||||
centerPanel.add(buttonBox, BorderLayout.SOUTH);
|
|
||||||
|
|
||||||
JScrollPane historyScrollPane = new JScrollPane(historyPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
|
||||||
historyScrollPane.setBorder(BorderFactory.createEmptyBorder());
|
|
||||||
|
|
||||||
tabbedPane.addTab("History", ResourceManager.getIcon("tab.history"), historyScrollPane);
|
|
||||||
|
|
||||||
mainPanel.add(searchBox, BorderLayout.NORTH);
|
|
||||||
mainPanel.add(centerPanel, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
this.add(mainPanel, BorderLayout.CENTER);
|
|
||||||
|
|
||||||
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AbstractAction searchAction = new AbstractAction("Find", ResourceManager.getIcon("action.find")) {
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
private static EventList<String> globalSearchHistory() {
|
||||||
searchField.clearTextSelection();
|
return GlazedLists.eventList(new BasicCachingList<String>(Settings.getSettings().asStringList(Settings.SUBTITLE_HISTORY)));
|
||||||
|
}
|
||||||
SearchTask searchTask = new SearchTask(searchField.getSelectedValue(), searchField.getTextField().getText());
|
|
||||||
searchTask.addPropertyChangeListener(new SearchTaskListener());
|
|
||||||
|
|
||||||
searchTask.execute();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final AbstractAction saveAction = new AbstractAction("Down") {
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
//TODO save action
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private class SearchTask extends SwingWorker<List<MovieDescriptor>, Void> {
|
@Override
|
||||||
|
protected SearchTask createSearchTask() {
|
||||||
|
SubtitleDownloadPanel panel = new SubtitleDownloadPanel();
|
||||||
|
|
||||||
private final String query;
|
return new SubtitleSearchTask(getSearchField().getSelected(), getSearchField().getText(), panel);
|
||||||
private final SubtitleClient client;
|
}
|
||||||
|
|
||||||
|
|
||||||
public SearchTask(SubtitleClient client, String query) {
|
@Override
|
||||||
this.client = client;
|
protected void configureSelectDialog(SelectDialog<SearchResult> selectDialog) {
|
||||||
this.query = query;
|
selectDialog.setText("Select a Show / Movie:");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected FetchTask createFetchTask(SearchTask searchTask, SearchResult selectedSearchResult) {
|
||||||
|
return new SubtitleFetchTask(searchTask.getClient(), selectedSearchResult, searchTask.getTabPanel());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected URI getLink(SubtitleClient client, SearchResult result) {
|
||||||
|
return client.getSubtitleListLink(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class SubtitleSearchTask extends SearchTask {
|
||||||
|
|
||||||
|
public SubtitleSearchTask(SubtitleClient client, String searchText, SubtitleDownloadPanel panel) {
|
||||||
|
super(client, searchText, panel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<MovieDescriptor> doInBackground() throws Exception {
|
protected List<SearchResult> doInBackground() throws Exception {
|
||||||
return client.search(query);
|
return getClient().search(getSearchText());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class SearchTaskListener extends SwingWorkerPropertyChangeAdapter {
|
private class SubtitleFetchTask extends FetchTask {
|
||||||
|
|
||||||
private FileBotTab<SubtitleDownloadPanel> downloadPanel;
|
public SubtitleFetchTask(SubtitleClient client, SearchResult searchResult, SubtitleDownloadPanel tabPanel) {
|
||||||
|
super(client, searchResult, tabPanel);
|
||||||
|
|
||||||
@Override
|
|
||||||
public void started(PropertyChangeEvent evt) {
|
|
||||||
SearchTask task = (SearchTask) evt.getSource();
|
|
||||||
|
|
||||||
downloadPanel = new FileBotTab<SubtitleDownloadPanel>(new SubtitleDownloadPanel());
|
|
||||||
|
|
||||||
downloadPanel.setTitle(task.query);
|
|
||||||
downloadPanel.setLoading(true);
|
|
||||||
downloadPanel.setIcon(task.client.getIcon());
|
|
||||||
|
|
||||||
downloadPanel.addTo(tabbedPane);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void done(PropertyChangeEvent evt) {
|
protected ProgressIterator<SubtitlePackage> fetch() throws Exception {
|
||||||
// tab might have been closed
|
ProgressIterator<SubtitleDescriptor> descriptors = getClient().getSubtitleList(getSearchResult());
|
||||||
if (downloadPanel.isClosed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
SearchTask searchTask = (SearchTask) evt.getSource();
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<MovieDescriptor> desriptors = searchTask.get();
|
|
||||||
|
|
||||||
MovieDescriptor descriptor = selectDescriptor(desriptors, searchTask.client);
|
|
||||||
|
|
||||||
if (descriptor == null) {
|
|
||||||
// user canceled selection, or no subtitles available
|
|
||||||
if (desriptors.isEmpty()) {
|
|
||||||
MessageManager.showWarning(String.format("\"%s\" has not been found.", searchTask.query));
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadPanel.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchFieldCompletion.addTerm(descriptor.getTitle());
|
|
||||||
Settings.getSettings().putStringList(Settings.SUBTITLE_HISTORY, searchFieldCompletion.getTerms());
|
|
||||||
|
|
||||||
downloadPanel.setTitle(descriptor.getTitle());
|
|
||||||
|
|
||||||
FetchSubtitleListTask fetchListTask = new FetchSubtitleListTask(descriptor, searchTask.client);
|
|
||||||
fetchListTask.addPropertyChangeListener(new FetchSubtitleListTaskListener(downloadPanel));
|
|
||||||
|
|
||||||
fetchListTask.execute();
|
|
||||||
} catch (Exception e) {
|
|
||||||
downloadPanel.close();
|
|
||||||
|
|
||||||
Throwable cause = FileBotUtil.getRootCause(e);
|
|
||||||
|
|
||||||
MessageManager.showWarning(cause.getMessage());
|
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, cause.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return new FunctionIterator<SubtitleDescriptor, SubtitlePackage>(descriptors, new SubtitlePackageFunction());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private MovieDescriptor selectDescriptor(List<MovieDescriptor> descriptors, SubtitleClient client) {
|
@Override
|
||||||
switch (descriptors.size()) {
|
protected void process(List<SubtitlePackage> elements) {
|
||||||
case 0:
|
getTabPanel().getPackagePanel().getModel().addAll(elements);
|
||||||
return null;
|
|
||||||
case 1:
|
|
||||||
return descriptors.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// multiple shows found, let user selected one
|
|
||||||
Window window = SwingUtilities.getWindowAncestor(SubtitlePanel.this);
|
|
||||||
|
|
||||||
SelectDialog<MovieDescriptor> selectDialog = new SelectDialog<MovieDescriptor>(window, descriptors);
|
|
||||||
|
|
||||||
selectDialog.setText("Select a Show / Movie:");
|
|
||||||
selectDialog.setIconImage(client.getIcon().getImage());
|
|
||||||
selectDialog.setVisible(true);
|
|
||||||
|
|
||||||
// selected value or null if canceled by the user
|
|
||||||
return selectDialog.getSelectedValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getStatusMessage() {
|
||||||
|
if (getCount() > 0)
|
||||||
|
return String.format("%d subtitles", getCount());
|
||||||
|
|
||||||
|
return "No subtitles found";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class FetchSubtitleListTaskListener extends SwingWorkerPropertyChangeAdapter {
|
private static class SubtitlePackageFunction implements Function<SubtitleDescriptor, SubtitlePackage> {
|
||||||
|
|
||||||
private final FileBotTab<SubtitleDownloadPanel> downloadPanel;
|
|
||||||
|
|
||||||
|
|
||||||
public FetchSubtitleListTaskListener(FileBotTab<SubtitleDownloadPanel> downloadPanel) {
|
|
||||||
this.downloadPanel = downloadPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void done(PropertyChangeEvent evt) {
|
public SubtitlePackage evaluate(SubtitleDescriptor sourceValue) {
|
||||||
// tab might have been closed
|
return new SubtitlePackage(sourceValue);
|
||||||
if (downloadPanel.isClosed())
|
|
||||||
return;
|
|
||||||
|
|
||||||
FetchSubtitleListTask task = (FetchSubtitleListTask) evt.getSource();
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<? extends SubtitleDescriptor> subtitleDescriptors = task.get();
|
|
||||||
|
|
||||||
String info = (subtitleDescriptors.size() > 0) ? String.format("%d subtitles", subtitleDescriptors.size()) : "No subtitles found";
|
|
||||||
|
|
||||||
historyPanel.add(task.getDescriptor().toString(), null, task.getClient().getIcon(), info, NumberFormat.getInstance().format(task.getDuration()) + " ms");
|
|
||||||
|
|
||||||
if (subtitleDescriptors.isEmpty()) {
|
|
||||||
MessageManager.showWarning(info);
|
|
||||||
downloadPanel.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadPanel.setLoading(false);
|
|
||||||
|
|
||||||
downloadPanel.getComponent().getPackagePanel().setTitle(info);
|
|
||||||
downloadPanel.getComponent().addSubtitleDescriptors(subtitleDescriptors);
|
|
||||||
} catch (Exception e) {
|
|
||||||
downloadPanel.close();
|
|
||||||
|
|
||||||
MessageManager.showWarning(FileBotUtil.getRootCause(e).getMessage());
|
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import java.io.File;
|
||||||
import javax.swing.filechooser.FileFilter;
|
import javax.swing.filechooser.FileFilter;
|
||||||
|
|
||||||
import net.sourceforge.filebot.ui.transferablepolicies.FileTransferablePolicy;
|
import net.sourceforge.filebot.ui.transferablepolicies.FileTransferablePolicy;
|
||||||
import net.sourceforge.filebot.ui.transferablepolicies.MultiTransferablePolicy;
|
import net.sourceforge.filebot.ui.transferablepolicies.CompositeTransferablePolicy;
|
||||||
import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy;
|
import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy;
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ public class TransferablePolicyFileFilter extends FileFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
if (transferablePolicy instanceof MultiTransferablePolicy) {
|
if (transferablePolicy instanceof CompositeTransferablePolicy) {
|
||||||
MultiTransferablePolicy multi = (MultiTransferablePolicy) transferablePolicy;
|
CompositeTransferablePolicy multi = (CompositeTransferablePolicy) transferablePolicy;
|
||||||
return multi.getDescription(FileTransferablePolicy.class);
|
return multi.getDescription(FileTransferablePolicy.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,12 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
public class MultiTransferablePolicy implements TransferablePolicy {
|
public class CompositeTransferablePolicy implements TransferablePolicy {
|
||||||
|
|
||||||
private List<TransferablePolicy> policies = Collections.synchronizedList(new ArrayList<TransferablePolicy>());
|
private List<TransferablePolicy> policies = Collections.synchronizedList(new ArrayList<TransferablePolicy>());
|
||||||
|
|
||||||
|
|
||||||
public MultiTransferablePolicy() {
|
public CompositeTransferablePolicy() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,20 +5,22 @@ package net.sourceforge.filebot.web;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NavigableMap;
|
import java.util.Locale;
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator;
|
||||||
|
import net.sourceforge.tuned.ProgressIterator;
|
||||||
import net.sourceforge.tuned.XPathUtil;
|
import net.sourceforge.tuned.XPathUtil;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator.Function;
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
@ -27,9 +29,9 @@ import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class AnidbClient extends EpisodeListClient {
|
public class AnidbClient extends EpisodeListClient {
|
||||||
|
|
||||||
private NavigableMap<String, URL> cache = new TreeMap<String, URL>(String.CASE_INSENSITIVE_ORDER);
|
private final SearchResultCache cache = new SearchResultCache();
|
||||||
|
|
||||||
private String host = "anidb.info";
|
private final String host = "anidb.info";
|
||||||
|
|
||||||
|
|
||||||
public AnidbClient() {
|
public AnidbClient() {
|
||||||
|
@ -38,39 +40,32 @@ public class AnidbClient extends EpisodeListClient {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> search(String searchterm) throws IOException, SAXException {
|
public List<SearchResult> search(String searchterm) throws IOException, SAXException {
|
||||||
synchronized (cache) {
|
if (cache.containsKey(searchterm)) {
|
||||||
if (getFoundName(searchterm) != null) {
|
return Collections.singletonList(cache.get(searchterm));
|
||||||
return Arrays.asList(getFoundName(searchterm));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
||||||
|
|
||||||
List<Node> nodes = XPathUtil.selectNodes("//TABLE[@class='animelist']//TR/TD/ancestor::TR", dom);
|
List<Node> nodes = XPathUtil.selectNodes("//TABLE[@class='animelist']//TR/TD/ancestor::TR", dom);
|
||||||
|
|
||||||
LinkedHashMap<String, URL> searchResults = new LinkedHashMap<String, URL>(nodes.size());
|
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||||
|
|
||||||
if (!nodes.isEmpty())
|
if (!nodes.isEmpty())
|
||||||
for (Node node : nodes) {
|
for (Node node : nodes) {
|
||||||
String type = XPathUtil.selectString("./TD[contains(@class,'type')]", node);
|
Node titleNode = XPathUtil.selectNode("./TD[@class='name']/A", node);
|
||||||
|
|
||||||
// we only want shows
|
String title = XPathUtil.selectString(".", titleNode);
|
||||||
if (type.equalsIgnoreCase("tv series")) {
|
String href = XPathUtil.selectString("@href", titleNode);
|
||||||
Node titleNode = XPathUtil.selectNode("./TD[@class='name']/A", node);
|
|
||||||
|
|
||||||
String title = XPathUtil.selectString(".", titleNode);
|
String file = "/perl-bin/" + href;
|
||||||
String href = XPathUtil.selectString("@href", titleNode);
|
|
||||||
|
|
||||||
String file = "/perl-bin/" + href;
|
try {
|
||||||
|
URL url = new URL("http", host, file);
|
||||||
|
|
||||||
try {
|
searchResults.add(new HyperLink(title, url));
|
||||||
URL url = new URL("http", host, file);
|
} catch (MalformedURLException e) {
|
||||||
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href);
|
||||||
searchResults.put(title, url);
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -82,32 +77,44 @@ public class AnidbClient extends EpisodeListClient {
|
||||||
String header = XPathUtil.selectString("id('layout-content')//H1[1]", dom);
|
String header = XPathUtil.selectString("id('layout-content')//H1[1]", dom);
|
||||||
String title = header.replaceFirst("Anime:\\s*", "");
|
String title = header.replaceFirst("Anime:\\s*", "");
|
||||||
|
|
||||||
searchResults.put(title, getSearchUrl(searchterm));
|
searchResults.add(new HyperLink(title, getSearchUrl(searchterm)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (cache) {
|
cache.addAll(searchResults);
|
||||||
cache.putAll(searchResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<String>(searchResults.keySet());
|
return searchResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Episode> getEpisodeList(String showname, int season) throws IOException, SAXException {
|
public ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException {
|
||||||
|
|
||||||
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListUrl(showname, season));
|
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult, season));
|
||||||
|
|
||||||
List<Node> nodes = XPathUtil.selectNodes("id('eplist')//TR/TD/SPAN/ancestor::TR", dom);
|
List<Node> nodes = XPathUtil.selectNodes("id('eplist')//TR/TD/SPAN/ancestor::TR", dom);
|
||||||
|
|
||||||
ArrayList<Episode> list = new ArrayList<Episode>(nodes.size());
|
return new FunctionIterator<Node, Episode>(nodes, new EpisodeFunction(searchResult, nodes.size()));
|
||||||
|
}
|
||||||
|
|
||||||
NumberFormat f = NumberFormat.getInstance();
|
|
||||||
f.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
|
|
||||||
f.setGroupingUsed(false);
|
|
||||||
|
|
||||||
for (Node node : nodes) {
|
private static class EpisodeFunction implements Function<Node, Episode> {
|
||||||
|
|
||||||
|
private final SearchResult searchResult;
|
||||||
|
private final NumberFormat numberFormat;
|
||||||
|
|
||||||
|
|
||||||
|
public EpisodeFunction(SearchResult searchResult, int nodeCount) {
|
||||||
|
this.searchResult = searchResult;
|
||||||
|
|
||||||
|
numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
|
||||||
|
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodeCount).length(), 2));
|
||||||
|
numberFormat.setGroupingUsed(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Episode evaluate(Node node) {
|
||||||
String number = XPathUtil.selectString("./TD[contains(@class,'id')]/A", node);
|
String number = XPathUtil.selectString("./TD[contains(@class,'id')]/A", node);
|
||||||
String title = XPathUtil.selectString("./TD[@class='title']/LABEL/text()", node);
|
String title = XPathUtil.selectString("./TD[@class='title']/LABEL/text()", node);
|
||||||
|
|
||||||
|
@ -116,41 +123,29 @@ public class AnidbClient extends EpisodeListClient {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// try to format number of episode
|
// try to format number of episode
|
||||||
number = f.format(Integer.parseInt(number));
|
number = numberFormat.format(Integer.parseInt(number));
|
||||||
} catch (NumberFormatException ex) {
|
} catch (NumberFormatException ex) {
|
||||||
// leave it be
|
// leave it be
|
||||||
}
|
}
|
||||||
|
|
||||||
list.add(new Episode(showname, null, number, title));
|
// no seasons for anime
|
||||||
|
return new Episode(searchResult.getName(), null, number, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URL getEpisodeListUrl(String showname, int season) {
|
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||||
synchronized (cache) {
|
return ((HyperLink) searchResult).getUri();
|
||||||
return cache.get(showname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFoundName(String searchterm) {
|
|
||||||
synchronized (cache) {
|
|
||||||
if (cache.containsKey(searchterm)) {
|
|
||||||
return cache.floorKey(searchterm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException {
|
private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException {
|
||||||
String qs = URLEncoder.encode(searchterm, "UTF-8");
|
String qs = URLEncoder.encode(searchterm, "UTF-8");
|
||||||
String file = "/perl-bin/animedb.pl?show=animelist&orderby=name&orderdir=0&adb.search=" + qs + "&noalias=1¬inml=0";
|
|
||||||
|
// type=2 -> only TV Series
|
||||||
|
String file = "/perl-bin/animedb.pl?type=2&show=animelist&orderby.name=0.1&orderbar=0&noalias=1&do.search=Search&adb.search=" + qs;
|
||||||
|
|
||||||
return new URL("http", host, file);
|
return new URL("http", host, file);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,15 @@
|
||||||
package net.sourceforge.filebot.web;
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.ProgressIterator;
|
||||||
|
|
||||||
|
|
||||||
public abstract class EpisodeListClient {
|
public abstract class EpisodeListClient {
|
||||||
|
|
||||||
|
@ -27,43 +29,9 @@ public abstract class EpisodeListClient {
|
||||||
return Collections.unmodifiableList(registry);
|
return Collections.unmodifiableList(registry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final String name;
|
||||||
public static EpisodeListClient forName(String name) {
|
private final boolean singleSeasonSupported;
|
||||||
for (EpisodeListClient client : registry) {
|
private final ImageIcon icon;
|
||||||
if (client.getName().equalsIgnoreCase(name))
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List of shows
|
|
||||||
*/
|
|
||||||
public abstract List<String> search(String searchterm) throws Exception;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract String getFoundName(String searchterm);
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param showname
|
|
||||||
* @param season number of season, 0 for all seasons
|
|
||||||
*/
|
|
||||||
public abstract List<Episode> getEpisodeList(String showname, int season) throws Exception;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract URL getEpisodeListUrl(String showname, int season);
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isSingleSeasonSupported() {
|
|
||||||
return singleSeasonSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
private boolean singleSeasonSupported;
|
|
||||||
private ImageIcon icon;
|
|
||||||
|
|
||||||
|
|
||||||
public EpisodeListClient(String name, ImageIcon icon, boolean singleSeasonSupported) {
|
public EpisodeListClient(String name, ImageIcon icon, boolean singleSeasonSupported) {
|
||||||
|
@ -73,8 +41,17 @@ public abstract class EpisodeListClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getName() {
|
public abstract List<SearchResult> search(String searchterm) throws Exception;
|
||||||
return name;
|
|
||||||
|
|
||||||
|
public abstract ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract URI getEpisodeListLink(SearchResult searchResult, int season);
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isSingleSeasonSupported() {
|
||||||
|
return singleSeasonSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,9 +60,14 @@ public abstract class EpisodeListClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getName();
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
@ -45,6 +46,11 @@ public class HtmlUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Document getHtmlDocument(URI uri) throws IOException, SAXException {
|
||||||
|
return getHtmlDocument(uri.toURL());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Document getHtmlDocument(URL url) throws IOException, SAXException {
|
public static Document getHtmlDocument(URL url) throws IOException, SAXException {
|
||||||
URLConnection connection = url.openConnection();
|
URLConnection connection = url.openConnection();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
|
||||||
|
public class HyperLink extends SearchResult {
|
||||||
|
|
||||||
|
private final URI uri;
|
||||||
|
|
||||||
|
|
||||||
|
public HyperLink(String name, URI uri) {
|
||||||
|
super(name);
|
||||||
|
this.uri = uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public HyperLink(String name, URL url) {
|
||||||
|
super(name);
|
||||||
|
this.uri = URI.create(url.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public URI getUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,36 +2,19 @@
|
||||||
package net.sourceforge.filebot.web;
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
public class MovieDescriptor {
|
public class MovieDescriptor extends SearchResult {
|
||||||
|
|
||||||
private final String title;
|
private final int imdbId;
|
||||||
private final Integer imdbId;
|
|
||||||
|
|
||||||
|
|
||||||
public MovieDescriptor(String title) {
|
public MovieDescriptor(String name, int imdbId) {
|
||||||
this(title, null);
|
super(name);
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public MovieDescriptor(String title, Integer imdbId) {
|
|
||||||
this.title = title;
|
|
||||||
this.imdbId = imdbId;
|
this.imdbId = imdbId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getTitle() {
|
public int getImdbId() {
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Integer getImdbId() {
|
|
||||||
return imdbId;
|
return imdbId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return title;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
package net.sourceforge.filebot.web;
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Timer;
|
import java.util.Timer;
|
||||||
import java.util.TimerTask;
|
import java.util.TimerTask;
|
||||||
|
@ -10,6 +11,9 @@ import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sourceforge.filebot.Settings;
|
import net.sourceforge.filebot.Settings;
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator;
|
||||||
|
import net.sourceforge.tuned.ProgressIterator;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator.Function;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,19 +34,31 @@ public class OpenSubtitlesSubtitleClient extends SubtitleClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public List<MovieDescriptor> search(String query) throws Exception {
|
public List<SearchResult> search(String searchterm) throws Exception {
|
||||||
login();
|
login();
|
||||||
|
|
||||||
return client.searchMoviesOnIMDB(query);
|
List result = client.searchMoviesOnIMDB(searchterm);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<OpenSubtitlesSubtitleDescriptor> getSubtitleList(MovieDescriptor descriptor) throws Exception {
|
public ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception {
|
||||||
login();
|
login();
|
||||||
|
|
||||||
return client.searchSubtitles(descriptor.getImdbId());
|
int imdbId = ((MovieDescriptor) searchResult).getImdbId();
|
||||||
|
|
||||||
|
List<OpenSubtitlesSubtitleDescriptor> subtitles = client.searchSubtitles(imdbId);
|
||||||
|
|
||||||
|
return new FunctionIterator<OpenSubtitlesSubtitleDescriptor, SubtitleDescriptor>(subtitles, new SubtitleFunction());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getSubtitleListLink(SearchResult searchResult) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -119,4 +135,13 @@ public class OpenSubtitlesSubtitleClient extends SubtitleClient {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class SubtitleFunction implements Function<OpenSubtitlesSubtitleDescriptor, SubtitleDescriptor> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubtitleDescriptor evaluate(OpenSubtitlesSubtitleDescriptor sourceValue) {
|
||||||
|
return sourceValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract class SearchResult implements Serializable {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
|
||||||
|
public SearchResult(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
|
||||||
|
public class SearchResultCache {
|
||||||
|
|
||||||
|
private final Map<String, SearchResult> cache = Collections.synchronizedMap(new TreeMap<String, SearchResult>(String.CASE_INSENSITIVE_ORDER));
|
||||||
|
|
||||||
|
|
||||||
|
public boolean containsKey(String name) {
|
||||||
|
return cache.containsKey(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SearchResult get(String name) {
|
||||||
|
return cache.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void add(SearchResult searchResult) {
|
||||||
|
cache.put(searchResult.getName(), searchResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void addAll(Iterable<SearchResult> searchResults) {
|
||||||
|
for (SearchResult searchResult : searchResults) {
|
||||||
|
add(searchResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,21 +5,23 @@ package net.sourceforge.filebot.web;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import net.sourceforge.filebot.FileFormat;
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
|
import net.sourceforge.tuned.FileUtil;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator;
|
||||||
|
import net.sourceforge.tuned.ProgressIterator;
|
||||||
import net.sourceforge.tuned.XPathUtil;
|
import net.sourceforge.tuned.XPathUtil;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator.Function;
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
@ -28,7 +30,7 @@ import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class SubsceneSubtitleClient extends SubtitleClient {
|
public class SubsceneSubtitleClient extends SubtitleClient {
|
||||||
|
|
||||||
private final Map<MovieDescriptor, URL> cache = Collections.synchronizedMap(new HashMap<MovieDescriptor, URL>());
|
private final SearchResultCache cache = new SearchResultCache();
|
||||||
|
|
||||||
private final String host = "subscene.com";
|
private final String host = "subscene.com";
|
||||||
|
|
||||||
|
@ -39,13 +41,16 @@ public class SubsceneSubtitleClient extends SubtitleClient {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<MovieDescriptor> search(String searchterm) throws IOException, SAXException {
|
public List<SearchResult> search(String searchterm) throws IOException, SAXException {
|
||||||
|
if (cache.containsKey(searchterm)) {
|
||||||
|
return Collections.singletonList(cache.get(searchterm));
|
||||||
|
}
|
||||||
|
|
||||||
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
||||||
|
|
||||||
List<Node> nodes = XPathUtil.selectNodes("id('filmSearch')/A", dom);
|
List<Node> nodes = XPathUtil.selectNodes("id('filmSearch')/A", dom);
|
||||||
|
|
||||||
ArrayList<MovieDescriptor> results = new ArrayList<MovieDescriptor>(nodes.size());
|
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||||
|
|
||||||
for (Node node : nodes) {
|
for (Node node : nodes) {
|
||||||
String title = XPathUtil.selectString("text()", node);
|
String title = XPathUtil.selectString("text()", node);
|
||||||
|
@ -54,66 +59,80 @@ public class SubsceneSubtitleClient extends SubtitleClient {
|
||||||
try {
|
try {
|
||||||
URL url = new URL("http", host, href);
|
URL url = new URL("http", host, href);
|
||||||
|
|
||||||
MovieDescriptor descriptor = new MovieDescriptor(title);
|
searchResults.add(new HyperLink(title, url));
|
||||||
cache.put(descriptor, url);
|
|
||||||
results.add(descriptor);
|
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
cache.addAll(searchResults);
|
||||||
|
|
||||||
|
return searchResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<SubsceneSubtitleDescriptor> getSubtitleList(MovieDescriptor descriptor) throws IOException, SAXException {
|
public ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception {
|
||||||
URL url = cache.get(descriptor);
|
URL url = getSubtitleListLink(searchResult).toURL();
|
||||||
|
|
||||||
Document dom = HtmlUtil.getHtmlDocument(url);
|
Document dom = HtmlUtil.getHtmlDocument(url);
|
||||||
|
|
||||||
Pattern hrefPattern = Pattern.compile("javascript:Subtitle\\((\\d+), '(\\w+)', '0', '(\\d+)'\\);");
|
|
||||||
|
|
||||||
List<Node> nodes = XPathUtil.selectNodes("//TABLE[@class='filmSubtitleList']//A[@id]//ancestor::TR", dom);
|
List<Node> nodes = XPathUtil.selectNodes("//TABLE[@class='filmSubtitleList']//A[@id]//ancestor::TR", dom);
|
||||||
|
|
||||||
List<SubsceneSubtitleDescriptor> list = new ArrayList<SubsceneSubtitleDescriptor>();
|
return new FunctionIterator<Node, SubtitleDescriptor>(nodes, new SubtitleFunction(url));
|
||||||
|
|
||||||
for (Node node : nodes) {
|
|
||||||
try {
|
|
||||||
Node linkNode = XPathUtil.selectFirstNode("./TD[1]/A", node);
|
|
||||||
|
|
||||||
String href = XPathUtil.selectString("@href", linkNode);
|
|
||||||
|
|
||||||
String lang = XPathUtil.selectString("./SPAN[1]", linkNode);
|
|
||||||
String name = XPathUtil.selectString("./SPAN[2]", linkNode);
|
|
||||||
|
|
||||||
String author = XPathUtil.selectString("./TD[4]", node);
|
|
||||||
|
|
||||||
Matcher matcher = hrefPattern.matcher(href);
|
|
||||||
|
|
||||||
if (!matcher.matches())
|
|
||||||
throw new IllegalArgumentException("Cannot extract download parameters: " + href);
|
|
||||||
|
|
||||||
String subtitleId = matcher.group(1);
|
|
||||||
String typeId = matcher.group(2);
|
|
||||||
|
|
||||||
URL downloadUrl = getDownloadUrl(url, subtitleId, typeId);
|
|
||||||
|
|
||||||
list.add(new SubsceneSubtitleDescriptor(name, lang, author, typeId, downloadUrl, url));
|
|
||||||
} catch (Exception e) {
|
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Cannot parse subtitle node", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private URL getDownloadUrl(URL referer, String subtitleId, String typeId) throws MalformedURLException {
|
private static class SubtitleFunction implements Function<Node, SubtitleDescriptor> {
|
||||||
String basePath = FileFormat.getNameWithoutExtension(referer.getFile());
|
|
||||||
String path = String.format("%s-dlpath-%s/%s.zipx", basePath, subtitleId, typeId);
|
|
||||||
|
|
||||||
return new URL(referer.getProtocol(), referer.getHost(), path);
|
private final Pattern hrefPattern = Pattern.compile("javascript:Subtitle\\((\\d+), '(\\w+)', '0', '(\\d+)'\\);");
|
||||||
|
|
||||||
|
private final URL url;
|
||||||
|
|
||||||
|
|
||||||
|
public SubtitleFunction(URL url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SubtitleDescriptor evaluate(Node node) throws Exception {
|
||||||
|
Node linkNode = XPathUtil.selectFirstNode("./TD[1]/A", node);
|
||||||
|
|
||||||
|
String href = XPathUtil.selectString("@href", linkNode);
|
||||||
|
|
||||||
|
String lang = XPathUtil.selectString("./SPAN[1]", linkNode);
|
||||||
|
String name = XPathUtil.selectString("./SPAN[2]", linkNode);
|
||||||
|
|
||||||
|
String author = XPathUtil.selectString("./TD[4]", node);
|
||||||
|
|
||||||
|
Matcher matcher = hrefPattern.matcher(href);
|
||||||
|
|
||||||
|
if (!matcher.matches())
|
||||||
|
throw new IllegalArgumentException("Cannot extract download parameters: " + href);
|
||||||
|
|
||||||
|
String subtitleId = matcher.group(1);
|
||||||
|
String typeId = matcher.group(2);
|
||||||
|
|
||||||
|
URL downloadUrl = getDownloadUrl(url, subtitleId, typeId);
|
||||||
|
|
||||||
|
return new SubsceneSubtitleDescriptor(name, lang, author, typeId, downloadUrl, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private URL getDownloadUrl(URL referer, String subtitleId, String typeId) throws MalformedURLException {
|
||||||
|
String basePath = FileUtil.getNameWithoutExtension(referer.getFile());
|
||||||
|
String path = String.format("%s-dlpath-%s/%s.zipx", basePath, subtitleId, typeId);
|
||||||
|
|
||||||
|
return new URL(referer.getProtocol(), referer.getHost(), path);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getSubtitleListLink(SearchResult searchResult) {
|
||||||
|
return ((HyperLink) searchResult).getUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,15 @@
|
||||||
package net.sourceforge.filebot.web;
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.ProgressIterator;
|
||||||
|
|
||||||
|
|
||||||
public abstract class SubtitleClient {
|
public abstract class SubtitleClient {
|
||||||
|
|
||||||
|
@ -35,6 +38,15 @@ public abstract class SubtitleClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public abstract List<SearchResult> search(String searchterm) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception;
|
||||||
|
|
||||||
|
|
||||||
|
public abstract URI getSubtitleListLink(SearchResult searchResult);
|
||||||
|
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -45,12 +57,6 @@ public abstract class SubtitleClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public abstract List<MovieDescriptor> search(String query) throws Exception;
|
|
||||||
|
|
||||||
|
|
||||||
public abstract List<? extends SubtitleDescriptor> getSubtitleList(MovieDescriptor descriptor) throws Exception;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return name;
|
return name;
|
||||||
|
|
|
@ -5,21 +5,23 @@ package net.sourceforge.filebot.web;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NavigableMap;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator;
|
||||||
|
import net.sourceforge.tuned.ProgressIterator;
|
||||||
import net.sourceforge.tuned.XPathUtil;
|
import net.sourceforge.tuned.XPathUtil;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator.Function;
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
@ -28,9 +30,9 @@ import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class TVRageClient extends EpisodeListClient {
|
public class TVRageClient extends EpisodeListClient {
|
||||||
|
|
||||||
private NavigableMap<String, URL> cache = new TreeMap<String, URL>(String.CASE_INSENSITIVE_ORDER);
|
private final SearchResultCache cache = new SearchResultCache();
|
||||||
|
|
||||||
private String host = "www.tvrage.com";
|
private final String host = "www.tvrage.com";
|
||||||
|
|
||||||
|
|
||||||
public TVRageClient() {
|
public TVRageClient() {
|
||||||
|
@ -39,18 +41,16 @@ public class TVRageClient extends EpisodeListClient {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> search(String searchterm) throws IOException, SAXException {
|
public List<SearchResult> search(String searchterm) throws IOException, SAXException {
|
||||||
synchronized (cache) {
|
if (cache.containsKey(searchterm)) {
|
||||||
if (getFoundName(searchterm) != null) {
|
return Collections.singletonList(cache.get(searchterm));
|
||||||
return Arrays.asList(getFoundName(searchterm));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
||||||
|
|
||||||
List<Node> nodes = XPathUtil.selectNodes("id('search_begin')/TABLE[1]/*/TR/TD/A[1]", dom);
|
List<Node> nodes = XPathUtil.selectNodes("id('search_begin')/TABLE[1]/*/TR/TD/A[1]", dom);
|
||||||
|
|
||||||
LinkedHashMap<String, URL> searchResults = new LinkedHashMap<String, URL>(nodes.size());
|
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||||
|
|
||||||
for (Node node : nodes) {
|
for (Node node : nodes) {
|
||||||
String href = XPathUtil.selectString("@href", node);
|
String href = XPathUtil.selectString("@href", node);
|
||||||
|
@ -58,30 +58,44 @@ public class TVRageClient extends EpisodeListClient {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URL url = new URL(href);
|
URL url = new URL(href);
|
||||||
searchResults.put(title, url);
|
|
||||||
|
searchResults.add(new HyperLink(title, url));
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (cache) {
|
cache.addAll(searchResults);
|
||||||
cache.putAll(searchResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<String>(searchResults.keySet());
|
return searchResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Episode> getEpisodeList(String showname, int season) throws IOException, SAXException {
|
public ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException {
|
||||||
|
|
||||||
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListUrl(showname, season));
|
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult, season));
|
||||||
|
|
||||||
List<Node> nodes = XPathUtil.selectNodes("//TABLE[@class='b']//TR[@id='brow']", dom);
|
List<Node> nodes = XPathUtil.selectNodes("//TABLE[@class='b']//TR[@id='brow']", dom);
|
||||||
|
|
||||||
ArrayList<Episode> episodes = new ArrayList<Episode>();
|
return new FunctionIterator<Node, Episode>(nodes, new EpisodeFunction(searchResult, season));
|
||||||
|
}
|
||||||
|
|
||||||
for (Node node : nodes) {
|
|
||||||
|
private static class EpisodeFunction implements Function<Node, Episode> {
|
||||||
|
|
||||||
|
private final SearchResult searchResult;
|
||||||
|
private final int season;
|
||||||
|
|
||||||
|
|
||||||
|
public EpisodeFunction(SearchResult searchResult, int season) {
|
||||||
|
this.searchResult = searchResult;
|
||||||
|
this.season = season;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Episode evaluate(Node node) {
|
||||||
String seasonAndEpisodeNumber = XPathUtil.selectString("./TD[2]/A", node);
|
String seasonAndEpisodeNumber = XPathUtil.selectString("./TD[2]/A", node);
|
||||||
String title = XPathUtil.selectString("./TD[5]", node);
|
String title = XPathUtil.selectString("./TD[5]", node);
|
||||||
|
|
||||||
|
@ -105,49 +119,33 @@ public class TVRageClient extends EpisodeListClient {
|
||||||
} else {
|
} else {
|
||||||
episodeNumber = seasonAndEpisodeNumber;
|
episodeNumber = seasonAndEpisodeNumber;
|
||||||
}
|
}
|
||||||
episodes.add(new Episode(showname, seasonNumber, episodeNumber, title));
|
|
||||||
|
|
||||||
|
return new Episode(searchResult.getName(), seasonNumber, episodeNumber, title);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return episodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public URL getEpisodeListUrl(String showname, int season) {
|
|
||||||
try {
|
|
||||||
URL baseUrl = null;
|
|
||||||
|
|
||||||
synchronized (cache) {
|
|
||||||
baseUrl = cache.get(showname);
|
|
||||||
}
|
|
||||||
|
|
||||||
String seasonString = "all";
|
|
||||||
|
|
||||||
if (season >= 1) {
|
|
||||||
seasonString = Integer.toString(season);
|
|
||||||
}
|
|
||||||
|
|
||||||
String file = baseUrl.getFile() + "/episode_list/" + seasonString;
|
|
||||||
|
|
||||||
return new URL("http", host, file);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getFoundName(String searchterm) {
|
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||||
synchronized (cache) {
|
URI baseUri = ((HyperLink) searchResult).getUri();
|
||||||
if (cache.containsKey(searchterm)) {
|
|
||||||
return cache.floorKey(searchterm);
|
String seasonString = "all";
|
||||||
}
|
|
||||||
|
if (season >= 1) {
|
||||||
|
seasonString = Integer.toString(season);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
String path = baseUri.getPath() + "/episode_list/" + seasonString;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return new URI("http", host, path, null);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,20 +5,23 @@ package net.sourceforge.filebot.web;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NavigableMap;
|
import java.util.Locale;
|
||||||
import java.util.TreeMap;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sourceforge.filebot.resources.ResourceManager;
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator;
|
||||||
|
import net.sourceforge.tuned.ProgressIterator;
|
||||||
import net.sourceforge.tuned.XPathUtil;
|
import net.sourceforge.tuned.XPathUtil;
|
||||||
|
import net.sourceforge.tuned.FunctionIterator.Function;
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
@ -27,9 +30,9 @@ import org.xml.sax.SAXException;
|
||||||
|
|
||||||
public class TvdotcomClient extends EpisodeListClient {
|
public class TvdotcomClient extends EpisodeListClient {
|
||||||
|
|
||||||
private NavigableMap<String, URL> cache = new TreeMap<String, URL>(String.CASE_INSENSITIVE_ORDER);
|
private final SearchResultCache cache = new SearchResultCache();
|
||||||
|
|
||||||
private String host = "www.tv.com";
|
private final String host = "www.tv.com";
|
||||||
|
|
||||||
|
|
||||||
public TvdotcomClient() {
|
public TvdotcomClient() {
|
||||||
|
@ -38,18 +41,16 @@ public class TvdotcomClient extends EpisodeListClient {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> search(String searchterm) throws IOException, SAXException {
|
public List<SearchResult> search(String searchterm) throws IOException, SAXException {
|
||||||
synchronized (cache) {
|
if (cache.containsKey(searchterm)) {
|
||||||
if (getFoundName(searchterm) != null) {
|
return Collections.singletonList(cache.get(searchterm));
|
||||||
return Arrays.asList(getFoundName(searchterm));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm));
|
||||||
|
|
||||||
List<Node> nodes = XPathUtil.selectNodes("id('search-results')//SPAN/A", dom);
|
List<Node> nodes = XPathUtil.selectNodes("id('search-results')//SPAN/A", dom);
|
||||||
|
|
||||||
LinkedHashMap<String, URL> searchResults = new LinkedHashMap<String, URL>(nodes.size());
|
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||||
|
|
||||||
for (Node node : nodes) {
|
for (Node node : nodes) {
|
||||||
String category = node.getParentNode().getTextContent();
|
String category = node.getParentNode().getTextContent();
|
||||||
|
@ -62,45 +63,53 @@ public class TvdotcomClient extends EpisodeListClient {
|
||||||
try {
|
try {
|
||||||
URL url = new URL(href);
|
URL url = new URL(href);
|
||||||
|
|
||||||
searchResults.put(title, url);
|
searchResults.add(new HyperLink(title, url));
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException e) {
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (cache) {
|
cache.addAll(searchResults);
|
||||||
cache.putAll(searchResults);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<String>(searchResults.keySet());
|
return searchResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Episode> getEpisodeList(String showname, int season) throws IOException, SAXException {
|
public ProgressIterator<Episode> getEpisodeList(SearchResult searchResult, int season) throws IOException, SAXException {
|
||||||
|
|
||||||
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListUrl(showname, season));
|
Document dom = HtmlUtil.getHtmlDocument(getEpisodeListLink(searchResult, season));
|
||||||
|
|
||||||
List<Node> nodes = XPathUtil.selectNodes("id('episode-listing')/DIV/TABLE/TR/TD/ancestor::TR", dom);
|
List<Node> nodes = XPathUtil.selectNodes("id('episode-listing')/DIV/TABLE/TR/TD/ancestor::TR", dom);
|
||||||
|
|
||||||
String seasonString = null;
|
return new FunctionIterator<Node, Episode>(nodes, new EpisodeFunction(searchResult, season, nodes.size()));
|
||||||
|
}
|
||||||
|
|
||||||
if (season >= 1)
|
|
||||||
seasonString = Integer.toString(season);
|
|
||||||
|
|
||||||
ArrayList<Episode> episodes = new ArrayList<Episode>(nodes.size());
|
private static class EpisodeFunction implements Function<Node, Episode> {
|
||||||
|
|
||||||
NumberFormat numberFormat = NumberFormat.getInstance();
|
private final SearchResult searchResult;
|
||||||
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
|
private final NumberFormat numberFormat;
|
||||||
numberFormat.setGroupingUsed(false);
|
|
||||||
|
|
||||||
Integer episodeOffset = null;
|
private Integer episodeOffset = null;
|
||||||
|
private String seasonString = null;
|
||||||
|
|
||||||
if (season == 1)
|
|
||||||
episodeOffset = 0;
|
|
||||||
|
|
||||||
for (Node node : nodes) {
|
public EpisodeFunction(SearchResult searchResult, int season, int nodeCount) {
|
||||||
|
this.searchResult = searchResult;
|
||||||
|
|
||||||
|
numberFormat = NumberFormat.getInstance(Locale.ENGLISH);
|
||||||
|
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodeCount).length(), 2));
|
||||||
|
numberFormat.setGroupingUsed(false);
|
||||||
|
|
||||||
|
if (season >= 1)
|
||||||
|
seasonString = Integer.toString(season);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Episode evaluate(Node node) {
|
||||||
String episodeNumber = XPathUtil.selectString("./TD[1]", node);
|
String episodeNumber = XPathUtil.selectString("./TD[1]", node);
|
||||||
String title = XPathUtil.selectString("./TD[2]/A", node);
|
String title = XPathUtil.selectString("./TD[2]/A", node);
|
||||||
|
|
||||||
|
@ -113,47 +122,32 @@ public class TvdotcomClient extends EpisodeListClient {
|
||||||
|
|
||||||
episodeNumber = numberFormat.format(n - episodeOffset);
|
episodeNumber = numberFormat.format(n - episodeOffset);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
// episode number may be "Pilot", "Special", etc.
|
// episode number may be "Pilot", "Special", ...
|
||||||
}
|
}
|
||||||
|
|
||||||
episodes.add(new Episode(showname, seasonString, episodeNumber, title));
|
return new Episode(searchResult.getName(), seasonString, episodeNumber, title);
|
||||||
}
|
}
|
||||||
|
|
||||||
return episodes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URL getEpisodeListUrl(String showname, int season) {
|
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||||
|
String summaryFile = null;
|
||||||
|
|
||||||
|
summaryFile = ((HyperLink) searchResult).getUri().getPath();
|
||||||
|
|
||||||
|
String base = summaryFile.substring(0, summaryFile.indexOf("summary.html"));
|
||||||
|
String file = base + "episode_listings.html";
|
||||||
|
String query = "season=" + season;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String summaryFile = null;
|
return new URI("http", host, file, query, null);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
synchronized (cache) {
|
throw new RuntimeException(e);
|
||||||
summaryFile = cache.get(showname).getFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
String base = summaryFile.substring(0, summaryFile.indexOf("summary.html"));
|
|
||||||
String episodelistFile = base + "episode_listings.html&season=" + season;
|
|
||||||
|
|
||||||
return new URL("http", host, episodelistFile);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFoundName(String searchterm) {
|
|
||||||
synchronized (cache) {
|
|
||||||
if (cache.containsKey(searchterm)) {
|
|
||||||
return cache.floorKey(searchterm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException {
|
private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException {
|
||||||
String qs = URLEncoder.encode(searchterm, "UTF-8");
|
String qs = URLEncoder.encode(searchterm, "UTF-8");
|
||||||
String file = "/search.php?qs=" + qs + "&type=11&stype=all";
|
String file = "/search.php?qs=" + qs + "&type=11&stype=all";
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
|
||||||
|
package net.sourceforge.tuned;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.AbstractList;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class BasicCachingList<E> extends AbstractList<E> {
|
||||||
|
|
||||||
|
private final List<E> source;
|
||||||
|
private final ArrayList<E> cache;
|
||||||
|
|
||||||
|
|
||||||
|
public BasicCachingList(List<E> source) {
|
||||||
|
this.source = source;
|
||||||
|
|
||||||
|
int sourceSize = source.size();
|
||||||
|
|
||||||
|
this.cache = new ArrayList<E>(sourceSize);
|
||||||
|
|
||||||
|
// fill cache with null values
|
||||||
|
for (int i = 0; i < sourceSize; i++) {
|
||||||
|
cache.add(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized E get(int index) {
|
||||||
|
E value = cache.get(index);
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
value = source.get(index);
|
||||||
|
cache.set(index, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized boolean add(E value) {
|
||||||
|
cache.add(value);
|
||||||
|
return source.add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized E remove(int index) {
|
||||||
|
source.remove(index);
|
||||||
|
return cache.remove(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public synchronized int size() {
|
||||||
|
return cache.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,14 +1,11 @@
|
||||||
|
|
||||||
package net.sourceforge.filebot;
|
package net.sourceforge.tuned;
|
||||||
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.NumberFormat;
|
|
||||||
|
|
||||||
|
|
||||||
public class FileFormat {
|
public class FileUtil {
|
||||||
|
|
||||||
private static final NumberFormat numberFormat = NumberFormat.getNumberInstance();
|
|
||||||
|
|
||||||
public final static long KILO = 1024;
|
public final static long KILO = 1024;
|
||||||
|
|
||||||
|
@ -16,18 +13,14 @@ public class FileFormat {
|
||||||
|
|
||||||
public final static long GIGA = MEGA * 1024;
|
public final static long GIGA = MEGA * 1024;
|
||||||
|
|
||||||
static {
|
|
||||||
numberFormat.setMaximumFractionDigits(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static String formatSize(long size) {
|
public static String formatSize(long size) {
|
||||||
if (size >= MEGA)
|
if (size >= MEGA)
|
||||||
return numberFormat.format((double) size / MEGA) + " MB";
|
return String.format("%d MB", (double) size / MEGA);
|
||||||
else if (size >= KILO)
|
else if (size >= KILO)
|
||||||
return numberFormat.format((double) size / KILO) + " KB";
|
return String.format("%d KB", (double) size / KILO);
|
||||||
else
|
else
|
||||||
return numberFormat.format(size) + " Byte";
|
return String.format("%d Byte", size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
package net.sourceforge.tuned;
|
package net.sourceforge.tuned;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
|
||||||
public class FunctionIterator<S, T> implements Iterator<T> {
|
public class FunctionIterator<S, T> implements ProgressIterator<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Function transforms one Object into another.
|
* A Function transforms one Object into another.
|
||||||
|
@ -22,20 +23,30 @@ public class FunctionIterator<S, T> implements Iterator<T> {
|
||||||
* @param sourceValue - the Object to transform
|
* @param sourceValue - the Object to transform
|
||||||
* @return the transformed version of the object
|
* @return the transformed version of the object
|
||||||
*/
|
*/
|
||||||
public T evaluate(S sourceValue);
|
public T evaluate(S sourceValue) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Iterator<S> sourceIterator;
|
private final Iterator<S> sourceIterator;
|
||||||
private final Function<S, T> function;
|
private final Function<S, T> function;
|
||||||
|
private final int length;
|
||||||
|
|
||||||
|
private int position = 0;
|
||||||
|
|
||||||
|
|
||||||
public FunctionIterator(Iterable<S> source, Function<S, T> function) {
|
public FunctionIterator(Collection<S> source, Function<S, T> function) {
|
||||||
this(source.iterator(), function);
|
this(source.iterator(), source.size(), function);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public FunctionIterator(Iterator<S> iterator, Function<S, T> function) {
|
//TODO TEST case!!! for piped functions -> correct progress
|
||||||
|
public FunctionIterator(ProgressIterator<S> iterator, Function<S, T> function) {
|
||||||
|
this(iterator, iterator.getLength(), function);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public FunctionIterator(Iterator<S> iterator, int length, Function<S, T> function) {
|
||||||
this.sourceIterator = iterator;
|
this.sourceIterator = iterator;
|
||||||
|
this.length = length;
|
||||||
this.function = function;
|
this.function = function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,20 +85,38 @@ public class FunctionIterator<S, T> implements Iterator<T> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
cache = transform(sourceIterator.next());
|
cache = transform(sourceIterator.next());
|
||||||
} catch (RuntimeException e) {
|
} catch (Exception e) {
|
||||||
currentException = e;
|
currentException = ExceptionUtil.asRuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
position++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache;
|
return cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private T transform(S sourceValue) {
|
private T transform(S sourceValue) throws Exception {
|
||||||
return function.evaluate(sourceValue);
|
return function.evaluate(sourceValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPosition() {
|
||||||
|
if (sourceIterator instanceof FunctionIterator) {
|
||||||
|
return ((ProgressIterator<?>) sourceIterator).getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLength() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The remove operation is not supported by this implementation of <code>Iterator</code>.
|
* The remove operation is not supported by this implementation of <code>Iterator</code>.
|
||||||
*
|
*
|
||||||
|
@ -98,4 +127,5 @@ public class FunctionIterator<S, T> implements Iterator<T> {
|
||||||
public void remove() {
|
public void remove() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -40,7 +39,7 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
return adapter.get(prefs, (String) key);
|
return adapter.get(prefs, (String) key);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("Key must be a String");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,7 +57,7 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
@Override
|
@Override
|
||||||
public T remove(Object key) {
|
public T remove(Object key) {
|
||||||
if (key instanceof String) {
|
if (key instanceof String) {
|
||||||
prefs.remove((String) key);
|
adapter.remove(prefs, (String) key);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -67,7 +66,7 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
|
|
||||||
public String[] keys() {
|
public String[] keys() {
|
||||||
try {
|
try {
|
||||||
return prefs.keys();
|
return adapter.keys(prefs);
|
||||||
} catch (BackingStoreException e) {
|
} catch (BackingStoreException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -76,10 +75,8 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {
|
||||||
try {
|
for (String key : keys()) {
|
||||||
prefs.clear();
|
adapter.remove(prefs, key);
|
||||||
} catch (BackingStoreException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,7 +128,7 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<String> keySet() {
|
public Set<String> keySet() {
|
||||||
return new HashSet<String>(Arrays.asList(keys()));
|
return new LinkedHashSet<String>(Arrays.asList(keys()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -213,14 +210,44 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
|
|
||||||
public static interface Adapter<T> {
|
public static interface Adapter<T> {
|
||||||
|
|
||||||
|
public String[] keys(Preferences prefs) throws BackingStoreException;
|
||||||
|
|
||||||
|
|
||||||
public T get(Preferences prefs, String key);
|
public T get(Preferences prefs, String key);
|
||||||
|
|
||||||
|
|
||||||
public void put(Preferences prefs, String key, T value);
|
public void put(Preferences prefs, String key, T value);
|
||||||
|
|
||||||
|
|
||||||
|
public void remove(Preferences prefs, String key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class StringAdapter implements Adapter<String> {
|
public static abstract class AbstractAdapter<T> implements Adapter<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract T get(Preferences prefs, String key);
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public abstract void put(Preferences prefs, String key, T value);
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] keys(Preferences prefs) throws BackingStoreException {
|
||||||
|
return prefs.keys();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(Preferences prefs, String key) {
|
||||||
|
prefs.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class StringAdapter extends AbstractAdapter<String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String get(Preferences prefs, String key) {
|
public String get(Preferences prefs, String key) {
|
||||||
|
@ -236,7 +263,7 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class SimpleAdapter<T> implements Adapter<T> {
|
public static class SimpleAdapter<T> extends AbstractAdapter<T> {
|
||||||
|
|
||||||
private final Constructor<T> constructor;
|
private final Constructor<T> constructor;
|
||||||
|
|
||||||
|
@ -260,6 +287,7 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
try {
|
try {
|
||||||
return constructor.newInstance(stringValue);
|
return constructor.newInstance(stringValue);
|
||||||
} catch (InvocationTargetException e) {
|
} catch (InvocationTargetException e) {
|
||||||
|
// try to throw the cause directly, e.g. NumberFormatException
|
||||||
throw ExceptionUtil.asRuntimeException(e.getCause());
|
throw ExceptionUtil.asRuntimeException(e.getCause());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
@ -275,7 +303,7 @@ public class PreferencesMap<T> implements Map<String, T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class SerializableAdapter<T extends Serializable> implements Adapter<T> {
|
public static class SerializableAdapter<T extends Serializable> extends AbstractAdapter<T> {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
package net.sourceforge.tuned;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
|
||||||
|
public interface ProgressIterator<E> extends Iterator<E> {
|
||||||
|
|
||||||
|
public int getPosition();
|
||||||
|
|
||||||
|
|
||||||
|
public int getLength();
|
||||||
|
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import java.awt.SystemColor;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.event.MouseListener;
|
import java.awt.event.MouseListener;
|
||||||
import java.net.URL;
|
import java.net.URI;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
@ -18,13 +18,13 @@ import javax.swing.JLabel;
|
||||||
|
|
||||||
public class HyperlinkLabel extends JLabel {
|
public class HyperlinkLabel extends JLabel {
|
||||||
|
|
||||||
private URL url;
|
private final URI link;
|
||||||
private Color defaultColor;
|
private final Color defaultColor;
|
||||||
|
|
||||||
|
|
||||||
public HyperlinkLabel(String label, URL url) {
|
public HyperlinkLabel(String label, URI link) {
|
||||||
super(label);
|
super(label);
|
||||||
this.url = url;
|
this.link = link;
|
||||||
defaultColor = getForeground();
|
defaultColor = getForeground();
|
||||||
addMouseListener(linker);
|
addMouseListener(linker);
|
||||||
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||||
|
@ -35,7 +35,7 @@ public class HyperlinkLabel extends JLabel {
|
||||||
@Override
|
@Override
|
||||||
public void mouseClicked(MouseEvent event) {
|
public void mouseClicked(MouseEvent event) {
|
||||||
try {
|
try {
|
||||||
Desktop.getDesktop().browse(url.toURI());
|
Desktop.getDesktop().browse(link);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// should not happen
|
// should not happen
|
||||||
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
package net.sourceforge.tuned.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
|
||||||
|
public interface IconProvider<T> {
|
||||||
|
|
||||||
|
public Icon getIcon(T value);
|
||||||
|
|
||||||
|
}
|
|
@ -17,8 +17,6 @@ import javax.swing.JLabel;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.ListCellRenderer;
|
|
||||||
import javax.swing.ListModel;
|
|
||||||
import javax.swing.border.Border;
|
import javax.swing.border.Border;
|
||||||
import javax.swing.border.CompoundBorder;
|
import javax.swing.border.CompoundBorder;
|
||||||
import javax.swing.border.EmptyBorder;
|
import javax.swing.border.EmptyBorder;
|
||||||
|
@ -65,26 +63,6 @@ public class IconViewPanel extends JPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ListCellRenderer getCellRenderer() {
|
|
||||||
return list.getCellRenderer();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setCellRenderer(ListCellRenderer cellRenderer) {
|
|
||||||
list.setCellRenderer(cellRenderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public ListModel getModel() {
|
|
||||||
return list.getModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setModel(ListModel model) {
|
|
||||||
list.setModel(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void expand() {
|
public void expand() {
|
||||||
// TODO expand
|
// TODO expand
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
package net.sourceforge.tuned.ui;
|
package net.sourceforge.tuned.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
|
||||||
|
@ -17,44 +18,53 @@ public class LoadingOverlayPane extends JComponent {
|
||||||
|
|
||||||
public static final String LOADING_PROPERTY = "loading";
|
public static final String LOADING_PROPERTY = "loading";
|
||||||
|
|
||||||
private final JLabel loadingLabel;
|
private final JComponent animationComponent;
|
||||||
|
|
||||||
private boolean overlayEnabled = false;
|
private boolean overlayEnabled = false;
|
||||||
|
|
||||||
private int millisToOverlay = 500;
|
private int millisToOverlay = 500;
|
||||||
|
|
||||||
private final JComponent view;
|
|
||||||
|
|
||||||
|
|
||||||
public LoadingOverlayPane(JComponent component, Icon animation) {
|
public LoadingOverlayPane(JComponent component, Icon animation) {
|
||||||
|
this(component, new JLabel(""), getView(component));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public LoadingOverlayPane(JComponent component, JComponent animation) {
|
||||||
this(component, animation, getView(component));
|
this(component, animation, getView(component));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public LoadingOverlayPane(JComponent component, Icon animation, JComponent view) {
|
public LoadingOverlayPane(JComponent component, JComponent animation, JComponent view) {
|
||||||
this.view = view;
|
|
||||||
|
|
||||||
setLayout(new OverlayLayout(this));
|
setLayout(new OverlayLayout(this));
|
||||||
|
|
||||||
|
this.animationComponent = animation;
|
||||||
|
|
||||||
component.setAlignmentX(1.0f);
|
component.setAlignmentX(1.0f);
|
||||||
component.setAlignmentY(0.0f);
|
component.setAlignmentY(0.0f);
|
||||||
|
|
||||||
loadingLabel = new JLabel(animation);
|
animation.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 20));
|
||||||
loadingLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 20));
|
|
||||||
|
|
||||||
loadingLabel.setAlignmentX(1.0f);
|
animation.setAlignmentX(1.0f);
|
||||||
loadingLabel.setAlignmentY(0.0f);
|
animation.setAlignmentY(0.0f);
|
||||||
loadingLabel.setMaximumSize(loadingLabel.getPreferredSize());
|
animationComponent.setPreferredSize(new Dimension(48, 48));
|
||||||
|
animationComponent.setMaximumSize(animationComponent.getPreferredSize());
|
||||||
|
|
||||||
add(loadingLabel);
|
add(animation);
|
||||||
add(component);
|
add(component);
|
||||||
|
|
||||||
setOverlayVisible(false);
|
setOverlayVisible(true);
|
||||||
|
|
||||||
view.addPropertyChangeListener(LOADING_PROPERTY, loadingListener);
|
view.addPropertyChangeListener(LOADING_PROPERTY, loadingListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOptimizedDrawingEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static JComponent getView(JComponent component) {
|
private static JComponent getView(JComponent component) {
|
||||||
if (component instanceof JScrollPane) {
|
if (component instanceof JScrollPane) {
|
||||||
JScrollPane scrollPane = (JScrollPane) component;
|
JScrollPane scrollPane = (JScrollPane) component;
|
||||||
|
@ -65,11 +75,6 @@ public class LoadingOverlayPane extends JComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public JComponent getView() {
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setOverlayVisible(boolean b) {
|
public void setOverlayVisible(boolean b) {
|
||||||
overlayEnabled = b;
|
overlayEnabled = b;
|
||||||
|
|
||||||
|
@ -79,13 +84,13 @@ public class LoadingOverlayPane extends JComponent {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (overlayEnabled) {
|
if (overlayEnabled) {
|
||||||
loadingLabel.setVisible(true);
|
animationComponent.setVisible(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
loadingLabel.setVisible(false);
|
animationComponent.setVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
|
||||||
|
package net.sourceforge.tuned.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
|
||||||
|
public class NullIconProvider<T extends Object> implements IconProvider<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Icon getIcon(Object value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,124 +11,135 @@ import java.awt.Graphics2D;
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.geom.GeneralPath;
|
import java.awt.geom.GeneralPath;
|
||||||
import java.awt.geom.Path2D;
|
import java.awt.geom.Path2D;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.DefaultSingleSelectionModel;
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
|
import javax.swing.SingleSelectionModel;
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
|
|
||||||
|
|
||||||
public class SelectButton<T> extends JButton implements ActionListener {
|
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 = new ArrayList<T>(0);
|
||||||
|
private SingleSelectionModel selectionModel = new DefaultSingleSelectionModel();
|
||||||
|
|
||||||
|
private IconProvider<T> iconProvider = new NullIconProvider<T>();
|
||||||
|
|
||||||
private boolean hover = false;
|
private boolean hover = false;
|
||||||
|
|
||||||
private Color beginColor = new Color(0xF0EEE4);
|
|
||||||
private Color endColor = new Color(0xE0DED4);
|
|
||||||
|
|
||||||
private Color beginColorHover = beginColor;
|
|
||||||
private Color endColorHover = new Color(0xD8D7CD);
|
|
||||||
|
|
||||||
private Entry<T> selectedEntry = null;
|
|
||||||
|
|
||||||
private List<Entry<T>> entries = new ArrayList<Entry<T>>();
|
|
||||||
|
|
||||||
public static final String SELECTED_VALUE_PROPERTY = "SELECTED_VALUE_PROPERTY";
|
|
||||||
|
|
||||||
|
|
||||||
public SelectButton(Collection<Entry<T>> entries) {
|
|
||||||
if (entries.isEmpty())
|
|
||||||
throw new IllegalArgumentException("Entries must not be empty");
|
|
||||||
|
|
||||||
this.entries.addAll(entries);
|
|
||||||
|
|
||||||
|
public SelectButton() {
|
||||||
setContentAreaFilled(false);
|
setContentAreaFilled(false);
|
||||||
setFocusable(false);
|
setFocusable(false);
|
||||||
|
|
||||||
addActionListener(this);
|
super.setIcon(selectIcon);
|
||||||
|
|
||||||
setHorizontalAlignment(SwingConstants.CENTER);
|
setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
setVerticalAlignment(SwingConstants.CENTER);
|
setVerticalAlignment(SwingConstants.CENTER);
|
||||||
|
|
||||||
addMouseListener(new MouseInputListener());
|
|
||||||
|
|
||||||
setPreferredSize(new Dimension(32, 22));
|
setPreferredSize(new Dimension(32, 22));
|
||||||
|
|
||||||
// select first entry
|
addActionListener(new OpenPopupOnClick());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void setModel(List<T> model) {
|
||||||
|
this.model = model;
|
||||||
setSelectedIndex(0);
|
setSelectedIndex(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setSelectedValue(T value) {
|
public IconProvider<T> getIconProvider() {
|
||||||
Entry<T> entry = find(value);
|
return iconProvider;
|
||||||
|
|
||||||
if (entry == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
selectedEntry = entry;
|
|
||||||
setIcon(new SelectIcon(selectedEntry.getIcon()));
|
|
||||||
|
|
||||||
firePropertyChange(SELECTED_VALUE_PROPERTY, null, selectedEntry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public T getSelectedEntry() {
|
public void setIconProvider(IconProvider<T> iconProvider) {
|
||||||
return selectedEntry.getValue();
|
this.iconProvider = iconProvider;
|
||||||
|
|
||||||
|
// update icon
|
||||||
|
this.setIcon(iconProvider.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) {
|
public void setSelectedIndex(int i) {
|
||||||
setSelectedValue(entries.get(i).getValue());
|
if (i < 0 || i >= model.size()) {
|
||||||
|
selectionModel.clearSelection();
|
||||||
|
setIcon(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i != selectionModel.getSelectedIndex()) {
|
||||||
|
selectionModel.setSelectedIndex(i);
|
||||||
|
|
||||||
|
T value = model.get(i);
|
||||||
|
|
||||||
|
setIcon(iconProvider.getIcon(value));
|
||||||
|
|
||||||
|
firePropertyChange(SELECTED_VALUE, null, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getSelectedIndex() {
|
public int getSelectedIndex() {
|
||||||
return entries.indexOf(selectedEntry);
|
return selectionModel.getSelectedIndex();
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Entry<T> find(T value) {
|
|
||||||
for (Entry<T> entry : entries) {
|
|
||||||
if (entry.value == value)
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void spinValue(int spin) {
|
public void spinValue(int spin) {
|
||||||
spin = spin % entries.size();
|
int size = model.size();
|
||||||
|
|
||||||
|
spin = spin % size;
|
||||||
|
|
||||||
int next = getSelectedIndex() + spin;
|
int next = getSelectedIndex() + spin;
|
||||||
|
|
||||||
if (next < 0)
|
if (next < 0)
|
||||||
next += entries.size();
|
next += size;
|
||||||
else if (next >= entries.size())
|
else if (next >= size)
|
||||||
next -= entries.size();
|
next -= size;
|
||||||
|
|
||||||
setSelectedIndex(next);
|
setSelectedIndex(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
JPopupMenu popup = new JPopupMenu();
|
|
||||||
|
|
||||||
for (Entry<T> entry : entries) {
|
|
||||||
popup.add(new SelectMenuItem(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
popup.show(this, 0, getHeight() - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void paintComponent(Graphics g) {
|
protected void paintComponent(Graphics g) {
|
||||||
Graphics2D g2d = (Graphics2D) g;
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
@ -144,20 +155,61 @@ public class SelectButton<T> extends JButton implements ActionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class SelectMenuItem extends JMenuItem implements ActionListener {
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
private T value;
|
super.processMouseEvent(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public SelectMenuItem(Entry<T> entry) {
|
private class OpenPopupOnClick implements ActionListener {
|
||||||
super(entry.toString(), entry.getIcon());
|
|
||||||
this.value = entry.getValue();
|
|
||||||
|
|
||||||
this.setMargin(new Insets(3, 0, 3, 0));
|
@Override
|
||||||
this.addActionListener(this);
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
JPopupMenu popup = new JPopupMenu();
|
||||||
|
|
||||||
if (this.value == getSelectedEntry())
|
for (T value : model) {
|
||||||
this.setFont(this.getFont().deriveFont(Font.BOLD));
|
SelectPopupMenuItem item = new SelectPopupMenuItem(value, iconProvider.getIcon(value));
|
||||||
|
|
||||||
|
if (value == getSelectedValue())
|
||||||
|
item.setSelected(true);
|
||||||
|
|
||||||
|
popup.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
popup.show(SelectButton.this, -4, getHeight() - 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private class SelectPopupMenuItem extends JMenuItem implements ActionListener {
|
||||||
|
|
||||||
|
private final T value;
|
||||||
|
|
||||||
|
|
||||||
|
public SelectPopupMenuItem(T value, Icon icon) {
|
||||||
|
super(value.toString(), 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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -171,13 +223,12 @@ public class SelectButton<T> extends JButton implements ActionListener {
|
||||||
|
|
||||||
private static class SelectIcon implements Icon {
|
private static class SelectIcon implements Icon {
|
||||||
|
|
||||||
|
private final GeneralPath arrow;
|
||||||
|
|
||||||
private Icon icon;
|
private Icon icon;
|
||||||
private GeneralPath arrow;
|
|
||||||
|
|
||||||
|
|
||||||
public SelectIcon(Icon icon) {
|
public SelectIcon() {
|
||||||
this.icon = icon;
|
|
||||||
|
|
||||||
arrow = new GeneralPath(Path2D.WIND_EVEN_ODD, 3);
|
arrow = new GeneralPath(Path2D.WIND_EVEN_ODD, 3);
|
||||||
int x = 25;
|
int x = 25;
|
||||||
int y = 10;
|
int y = 10;
|
||||||
|
@ -189,10 +240,17 @@ public class SelectButton<T> extends JButton implements ActionListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
|
public void setInnerIcon(Icon icon) {
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void paintIcon(Component c, Graphics g, int x, int y) {
|
||||||
Graphics2D g2d = (Graphics2D) g;
|
Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
icon.paintIcon(c, g2d, 4, 3);
|
if (icon != null) {
|
||||||
|
icon.paintIcon(c, g2d, 4, 3);
|
||||||
|
}
|
||||||
|
|
||||||
g2d.setPaint(Color.BLACK);
|
g2d.setPaint(Color.BLACK);
|
||||||
g2d.fill(arrow);
|
g2d.fill(arrow);
|
||||||
|
@ -209,51 +267,4 @@ public class SelectButton<T> extends JButton implements ActionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class MouseInputListener extends MouseAdapter {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseEntered(MouseEvent e) {
|
|
||||||
hover = true;
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseExited(MouseEvent e) {
|
|
||||||
hover = false;
|
|
||||||
repaint();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Entry<T> {
|
|
||||||
|
|
||||||
private T value;
|
|
||||||
private Icon icon;
|
|
||||||
|
|
||||||
|
|
||||||
public Entry(T value, Icon icon) {
|
|
||||||
this.value = value;
|
|
||||||
this.icon = icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public T getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Icon getIcon() {
|
|
||||||
return icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return value.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
|
||||||
|
package net.sourceforge.tuned.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.JButton;
|
||||||
|
import javax.swing.JComboBox;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
import javax.swing.border.Border;
|
||||||
|
import javax.swing.plaf.ComboBoxUI;
|
||||||
|
import javax.swing.plaf.basic.BasicComboBoxUI;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.resources.ResourceManager;
|
||||||
|
|
||||||
|
|
||||||
|
public class SelectButtonTextField<T> extends JPanel {
|
||||||
|
|
||||||
|
private SelectButton<T> selectButton;
|
||||||
|
|
||||||
|
private ComboBoxTextField editor = new ComboBoxTextField();
|
||||||
|
|
||||||
|
private Color borderColor = new Color(0xA4A4A4);
|
||||||
|
|
||||||
|
|
||||||
|
public SelectButtonTextField() {
|
||||||
|
setLayout(new BorderLayout(0, 0));
|
||||||
|
|
||||||
|
selectButton = new SelectButton<T>();
|
||||||
|
selectButton.addActionListener(textFieldFocusOnClick);
|
||||||
|
|
||||||
|
Border lineBorder = BorderFactory.createLineBorder(borderColor, 1);
|
||||||
|
Border matteBorder = BorderFactory.createMatteBorder(1, 0, 1, 1, borderColor);
|
||||||
|
Border emptyBorder = BorderFactory.createEmptyBorder(0, 3, 0, 3);
|
||||||
|
|
||||||
|
selectButton.setBorder(lineBorder);
|
||||||
|
editor.setBorder(BorderFactory.createCompoundBorder(matteBorder, emptyBorder));
|
||||||
|
|
||||||
|
add(editor, BorderLayout.CENTER);
|
||||||
|
add(selectButton, BorderLayout.WEST);
|
||||||
|
|
||||||
|
setPreferredSize(new Dimension(280, 22));
|
||||||
|
|
||||||
|
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift UP"), new SpinClientAction(-1));
|
||||||
|
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift DOWN"), new SpinClientAction(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for <code>getEditor().getSelectedItem().toString()</code>
|
||||||
|
*/
|
||||||
|
public String getText() {
|
||||||
|
return getEditor().getSelectedItem().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method for <code>getSelectButton().getSelectedValue()</code>
|
||||||
|
*/
|
||||||
|
public T getSelected() {
|
||||||
|
return getSelectButton().getSelectedValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public JComboBox getEditor() {
|
||||||
|
return editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public SelectButton<T> getSelectButton() {
|
||||||
|
return selectButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final ActionListener textFieldFocusOnClick = new ActionListener() {
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
getEditor().requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
private class SpinClientAction extends AbstractAction {
|
||||||
|
|
||||||
|
private int spin;
|
||||||
|
|
||||||
|
|
||||||
|
public SpinClientAction(int spin) {
|
||||||
|
this.spin = spin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
selectButton.spinValue(spin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class ComboBoxTextField extends JComboBox {
|
||||||
|
|
||||||
|
public ComboBoxTextField() {
|
||||||
|
setEditable(true);
|
||||||
|
super.setUI(new TextFieldComboBoxUI());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUI(ComboBoxUI ui) {
|
||||||
|
// don't reset the UI delegate if laf is changed, or we use our custom ui
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class TextFieldComboBoxUI extends BasicComboBoxUI {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected JButton createArrowButton() {
|
||||||
|
JButton b = new JButton(ResourceManager.getIcon("action.list"));
|
||||||
|
|
||||||
|
b.setContentAreaFilled(false);
|
||||||
|
b.setFocusable(false);
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
package net.sourceforge.tuned.ui;
|
||||||
|
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
import net.sourceforge.tuned.ExceptionUtil;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <code>IconProvider</code> based on reflection.
|
||||||
|
*/
|
||||||
|
public class SimpleIconProvider<T> implements IconProvider<T> {
|
||||||
|
|
||||||
|
private final Method getIconMethod;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as <code>new SimpleIconProvider<T>(T.class)</code>.
|
||||||
|
*
|
||||||
|
* @return new <code>IconProvider</code>
|
||||||
|
*/
|
||||||
|
public static <T> SimpleIconProvider<T> forClass(Class<T> type) {
|
||||||
|
return new SimpleIconProvider<T>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new IconProvider which will use the <code>getIcon</code> method of the given
|
||||||
|
* class.
|
||||||
|
*
|
||||||
|
* @param type a class with a <code>getIcon</code> method
|
||||||
|
*/
|
||||||
|
public SimpleIconProvider(Class<T> type) {
|
||||||
|
this(type, "getIcon");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new IconProvider which will use a specified method of a given class
|
||||||
|
*
|
||||||
|
* @param type a class with the specified method
|
||||||
|
* @param getIcon a method name such as <code>getIcon</code>
|
||||||
|
*/
|
||||||
|
public SimpleIconProvider(Class<T> type, String getIcon) {
|
||||||
|
try {
|
||||||
|
getIconMethod = type.getMethod(getIcon);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Icon getIcon(T value) {
|
||||||
|
try {
|
||||||
|
return (Icon) getIconMethod.invoke(value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw ExceptionUtil.asRuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -11,7 +11,6 @@ import javax.swing.SwingWorker;
|
||||||
import javax.swing.Timer;
|
import javax.swing.Timer;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class SwingWorkerProgressMonitor {
|
public class SwingWorkerProgressMonitor {
|
||||||
|
|
||||||
public static final String PROPERTY_NOTE = "note";
|
public static final String PROPERTY_NOTE = "note";
|
||||||
|
|
|
@ -5,7 +5,7 @@ package net.sourceforge.tuned.ui;
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker.StateValue;
|
||||||
|
|
||||||
|
|
||||||
public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChangeListener {
|
public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChangeListener {
|
||||||
|
@ -13,10 +13,24 @@ public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChange
|
||||||
public void propertyChange(PropertyChangeEvent evt) {
|
public void propertyChange(PropertyChangeEvent evt) {
|
||||||
if (evt.getPropertyName().equals("progress"))
|
if (evt.getPropertyName().equals("progress"))
|
||||||
progress(evt);
|
progress(evt);
|
||||||
else if (evt.getNewValue().equals(SwingWorker.StateValue.STARTED))
|
else if (evt.getPropertyName().equals("state"))
|
||||||
started(evt);
|
state(evt);
|
||||||
else if (evt.getNewValue().equals(SwingWorker.StateValue.DONE))
|
}
|
||||||
done(evt);
|
|
||||||
|
|
||||||
|
public void state(PropertyChangeEvent evt) {
|
||||||
|
switch ((StateValue) evt.getNewValue()) {
|
||||||
|
case STARTED:
|
||||||
|
started(evt);
|
||||||
|
break;
|
||||||
|
case DONE:
|
||||||
|
done(evt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void progress(PropertyChangeEvent evt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +41,4 @@ public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChange
|
||||||
public void done(PropertyChangeEvent evt) {
|
public void done(PropertyChangeEvent evt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void progress(PropertyChangeEvent evt) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
|
|
||||||
package net.sourceforge.tuned.ui;
|
|
||||||
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
import javax.swing.KeyStroke;
|
|
||||||
import javax.swing.border.Border;
|
|
||||||
|
|
||||||
import net.sourceforge.tuned.ui.SelectButton.Entry;
|
|
||||||
|
|
||||||
|
|
||||||
public class TextFieldWithSelect<T> extends JPanel {
|
|
||||||
|
|
||||||
private SelectButton<T> selectButton;
|
|
||||||
|
|
||||||
private JTextField textfield = new JTextField();
|
|
||||||
private Color borderColor = new Color(0xA4A4A4);
|
|
||||||
|
|
||||||
|
|
||||||
public TextFieldWithSelect(Collection<Entry<T>> options) {
|
|
||||||
setLayout(new BorderLayout(0, 0));
|
|
||||||
|
|
||||||
selectButton = new SelectButton<T>(options);
|
|
||||||
selectButton.addActionListener(textFieldFocusOnClick);
|
|
||||||
|
|
||||||
Border lineBorder = BorderFactory.createLineBorder(borderColor, 1);
|
|
||||||
Border matteBorder = BorderFactory.createMatteBorder(1, 0, 1, 1, borderColor);
|
|
||||||
Border emptyBorder = BorderFactory.createEmptyBorder(0, 3, 0, 3);
|
|
||||||
|
|
||||||
selectButton.setBorder(lineBorder);
|
|
||||||
textfield.setBorder(BorderFactory.createCompoundBorder(matteBorder, emptyBorder));
|
|
||||||
|
|
||||||
textfield.setColumns(20);
|
|
||||||
add(textfield, BorderLayout.CENTER);
|
|
||||||
add(selectButton, BorderLayout.WEST);
|
|
||||||
|
|
||||||
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift UP"), new SpinClientAction(-1));
|
|
||||||
TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift DOWN"), new SpinClientAction(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public JTextField getTextField() {
|
|
||||||
return textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public SelectButton<T> getSelectButton() {
|
|
||||||
return selectButton;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public T getSelectedValue() {
|
|
||||||
return selectButton.getSelectedEntry();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void clearTextSelection() {
|
|
||||||
int length = textfield.getText().length();
|
|
||||||
textfield.select(length, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final ActionListener textFieldFocusOnClick = new ActionListener() {
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
textfield.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
private class SpinClientAction extends AbstractAction {
|
|
||||||
|
|
||||||
private int spin;
|
|
||||||
|
|
||||||
|
|
||||||
public SpinClientAction(int spin) {
|
|
||||||
this.spin = spin;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void actionPerformed(ActionEvent e) {
|
|
||||||
selectButton.spinValue(spin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -3,12 +3,16 @@ package net.sourceforge.tuned.ui;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Image;
|
||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.Window;
|
import java.awt.Window;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
|
import javax.swing.Icon;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JDialog;
|
import javax.swing.JDialog;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
@ -42,6 +46,22 @@ public class TunedUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static Image getImage(Icon icon) {
|
||||||
|
//TODO uncomment
|
||||||
|
// if (icon instanceof ImageIcon) {
|
||||||
|
// return ((ImageIcon) icon).getImage();
|
||||||
|
// }
|
||||||
|
|
||||||
|
BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
Graphics2D g2d = image.createGraphics();
|
||||||
|
icon.paintIcon(null, g2d, 0, 0);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Timer invokeLater(int delay, final Runnable runnable) {
|
public static Timer invokeLater(int delay, final Runnable runnable) {
|
||||||
Timer timer = new Timer(delay, new ActionListener() {
|
Timer timer = new Timer(delay, new ActionListener() {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
|
||||||
|
import junit.framework.JUnit4TestAdapter;
|
||||||
|
import junit.framework.Test;
|
||||||
|
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Suite;
|
||||||
|
import org.junit.runners.Suite.SuiteClasses;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(Suite.class)
|
||||||
|
@SuiteClasses( { net.sourceforge.tuned.TestSuite.class, net.sourceforge.filebot.TestSuite.class })
|
||||||
|
public class AllTests {
|
||||||
|
|
||||||
|
public static Test suite() {
|
||||||
|
return new JUnit4TestAdapter(AllTests.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot;
|
||||||
|
|
||||||
|
|
||||||
|
import junit.framework.JUnit4TestAdapter;
|
||||||
|
import junit.framework.Test;
|
||||||
|
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Suite;
|
||||||
|
import org.junit.runners.Suite.SuiteClasses;
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(Suite.class)
|
||||||
|
@SuiteClasses( {})
|
||||||
|
public class TestSuite {
|
||||||
|
|
||||||
|
public static Test suite() {
|
||||||
|
return new JUnit4TestAdapter(TestSuite.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -158,11 +158,10 @@ public class PreferencesMapTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
|
||||||
public void getWithObjectKey() throws Exception {
|
public void getWithObjectKey() throws Exception {
|
||||||
Map<String, String> map = PreferencesMap.map(strings, String.class);
|
Map<String, String> map = PreferencesMap.map(strings, String.class);
|
||||||
|
|
||||||
map.get(new Object());
|
assertEquals(null, map.get(new Object()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue