* 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:
Reinhard Pointner 2008-06-21 19:24:18 +00:00
parent 405f04b9dd
commit adb4d68055
68 changed files with 2194 additions and 1379 deletions

BIN
fw/action.list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

View File

@ -22,7 +22,7 @@ public class Main {
/**
* @param args
*/
public static void main(String[] args) {
public static void main(String... 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);
}
final FileBotWindow window = new FileBotWindow();
// publish messages from arguments to the newly created components
arguments.publishMessages();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
FileBotWindow window = new FileBotWindow();
// publish messages from arguments to the newly created components
arguments.publishMessages();
// start
window.setVisible(true);
}
});

View File

@ -12,6 +12,8 @@ import java.nio.channels.FileChannel;
import java.util.List;
import java.util.regex.Pattern;
import net.sourceforge.tuned.FileUtil;
public class FileBotUtil {
@ -42,15 +44,6 @@ public class FileBotUtil {
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[] SFV_FILE_EXTENSIONS = { "sfv" };
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) {
for (File file : files) {
if (!FileFormat.hasExtension(file, extensions))
if (!FileUtil.hasExtension(file, extensions))
return false;
}
@ -113,7 +106,7 @@ public class FileBotUtil {
@Override
public boolean accept(File dir, String name) {
return FileFormat.hasExtension(name, SUBTITLE_FILE_EXTENSIONS);
return FileUtil.hasExtension(name, SUBTITLE_FILE_EXTENSIONS);
}
};

View File

@ -2,17 +2,16 @@
package net.sourceforge.filebot;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import net.sourceforge.tuned.PreferencesList;
import net.sourceforge.tuned.PreferencesMap;
public class Settings {
@ -37,17 +36,7 @@ public class Settings {
private Settings() {
this.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);
prefs = Preferences.userRoot().node(ROOT);
}
@ -61,125 +50,13 @@ public class Settings {
}
public void putBoolean(String key, boolean value) {
prefs.putBoolean(key, value);
public List<String> asStringList(String key) {
return PreferencesList.map(prefs.node(key), String.class);
}
public boolean getBoolean(String key, boolean def) {
return prefs.getBoolean(key, def);
}
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());
public Map<String, Boolean> asBooleanMap(String key) {
return PreferencesMap.map(prefs.node(key), Boolean.class);
}
@ -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

View File

@ -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);
}
}
}

View File

@ -53,7 +53,7 @@ public class FileBotPanel extends JPanel {
public FileBotPanel(String title, Icon icon) {
super(new BorderLayout(10, 0));
super(new BorderLayout(10, 10));
this.name = title;
this.icon = icon;
}

View File

@ -1,19 +1,22 @@
package net.sourceforge.filebot.ui.panel.subtitle;
package net.sourceforge.filebot.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ImageIcon;
import javax.swing.DefaultBoundedRangeModel;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
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 {
@ -22,7 +25,7 @@ public class FileBotTab<T extends JComponent> extends JPanel {
private final T component;
private ImageIcon icon;
private Icon icon;
private boolean loading = false;
@ -33,7 +36,14 @@ public class FileBotTab<T extends JComponent> extends JPanel {
this.component = component;
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;
if (!loading) {
@ -94,7 +104,7 @@ public class FileBotTab<T extends JComponent> extends JPanel {
}
public ImageIcon getIcon() {
public Icon getIcon() {
return icon;
}

View File

@ -5,16 +5,13 @@ package net.sourceforge.filebot.ui;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.OverlayLayout;
import javax.swing.ScrollPaneConstants;
import javax.swing.border.EmptyBorder;
@ -27,7 +24,6 @@ import net.sourceforge.tuned.MessageBus;
import net.sourceforge.tuned.MessageHandler;
import net.sourceforge.tuned.ui.ShadowBorder;
import net.sourceforge.tuned.ui.SimpleListModel;
import net.sourceforge.tuned.ui.TunedUtil;
public class FileBotWindow extends JFrame implements ListSelectionListener {
@ -55,9 +51,6 @@ public class FileBotWindow extends JFrame implements ListSelectionListener {
setContentPane(contentPane);
// Shortcut ESC
TunedUtil.registerActionForKeystroke(contentPane, KeyStroke.getKeyStroke("released ESCAPE"), closeAction);
setSize(760, 615);
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);
}
};
}

View File

@ -6,7 +6,7 @@ import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.net.URL;
import java.net.URI;
import javax.swing.Icon;
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) {
JLabel label1 = (url != null) ? new HyperlinkLabel(column1, url) : new JLabel(column1);
public void add(String column1, URI link, Icon icon, String column2, String column3) {
JLabel label1 = (link != null) ? new HyperlinkLabel(column1, link) : new JLabel(column1);
JLabel label2 = new JLabel(column2);
JLabel label3 = new JLabel(column3);

View File

@ -26,10 +26,10 @@ import javax.swing.event.ChangeListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.FileBotTree;
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.ui.GradientStyle;
import net.sourceforge.tuned.ui.LoadingOverlayPane;
import net.sourceforge.tuned.ui.notification.SeparatorBorder;
@ -84,7 +84,7 @@ public class SplitPanel extends ToolPanel implements ChangeListener {
private long getSplitSize() {
return spinnerModel.getNumber().intValue() * FileFormat.MEGA;
return spinnerModel.getNumber().intValue() * FileUtil.MEGA;
}
private UpdateTask updateTask;

View File

@ -8,7 +8,7 @@ import java.util.Collection;
import javax.swing.JComponent;
import javax.swing.tree.DefaultMutableTreeNode;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.tuned.FileUtil;
public abstract class ToolPanel extends JComponent {
@ -47,7 +47,7 @@ public abstract class ToolPanel extends JComponent {
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;
}

View File

@ -19,10 +19,10 @@ import javax.swing.SwingWorker;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.FileBotTree;
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
import net.sourceforge.tuned.FileUtil;
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>>();
for (File file : files) {
String extension = FileFormat.getExtension(file);
String extension = FileUtil.getExtension(file);
SortedSet<File> set = map.get(extension);

View File

@ -10,10 +10,10 @@ import java.util.logging.Level;
import java.util.logging.Logger;
import net.sourceforge.filebot.FileBotUtil;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.filebot.torrent.Torrent;
import net.sourceforge.filebot.ui.FileBotList;
import net.sourceforge.filebot.ui.transferablepolicies.FileTransferablePolicy;
import net.sourceforge.tuned.FileUtil;
class FileListTransferablePolicy extends FileTransferablePolicy {
@ -35,7 +35,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
@Override
protected void load(List<File> files) {
if (files.size() > 1) {
list.setTitle(FileFormat.getFolderName(files.get(0).getParentFile()));
list.setTitle(FileUtil.getFolderName(files.get(0).getParentFile()));
}
if (FileBotUtil.containsOnlyFolders(files)) {
@ -50,12 +50,12 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
private void loadFolderList(List<File> folders) {
if (folders.size() == 1) {
list.setTitle(FileFormat.getFolderName(folders.get(0)));
list.setTitle(FileUtil.getFolderName(folders.get(0)));
}
for (File folder : folders) {
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) {
list.setTitle(FileFormat.getNameWithoutExtension(torrents.get(0).getName()));
list.setTitle(FileUtil.getNameWithoutExtension(torrents.get(0).getName()));
}
for (Torrent torrent : torrents) {
for (Torrent.Entry entry : torrent.getFiles()) {
list.getModel().add(FileFormat.getNameWithoutExtension(entry.getName()));
list.getModel().add(FileUtil.getNameWithoutExtension(entry.getName()));
}
}
} catch (IOException e) {
@ -87,7 +87,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
@Override
protected void load(File file) {
list.getModel().add(FileFormat.getFileName(file));
list.getModel().add(FileUtil.getFileName(file));
}

View 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.StringEntry;
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;
class NamesListTransferablePolicy extends MultiTransferablePolicy {
class NamesListTransferablePolicy extends CompositeTransferablePolicy {
private final RenameList list;

View File

@ -9,11 +9,11 @@ import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.MessageManager;
import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry;
import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry;
import net.sourceforge.tuned.FileUtil;
public class RenameAction extends AbstractAction {
@ -44,7 +44,7 @@ public class RenameAction extends AbstractAction {
FileEntry fileEntry = (FileEntry) fileEntries.get(i);
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);

View File

@ -4,7 +4,7 @@ package net.sourceforge.filebot.ui.panel.rename.entry;
import java.io.File;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.tuned.FileUtil;
public class FileEntry extends AbstractFileEntry {
@ -16,11 +16,11 @@ public class FileEntry extends AbstractFileEntry {
public FileEntry(File file) {
super(FileFormat.getFileName(file));
super(FileUtil.getFileName(file));
this.file = file;
this.length = file.length();
this.type = FileFormat.getFileType(file);
this.type = FileUtil.getFileType(file);
}

View File

@ -2,8 +2,8 @@
package net.sourceforge.filebot.ui.panel.rename.entry;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.filebot.torrent.Torrent.Entry;
import net.sourceforge.tuned.FileUtil;
public class TorrentEntry extends AbstractFileEntry {
@ -12,7 +12,7 @@ public class TorrentEntry extends AbstractFileEntry {
public TorrentEntry(Entry entry) {
super(FileFormat.getNameWithoutExtension(entry.getName()));
super(FileUtil.getNameWithoutExtension(entry.getName()));
this.entry = entry;
}

View File

@ -2,26 +2,29 @@
package net.sourceforge.filebot.ui.panel.search;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.SwingWorker;
import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.EpisodeListClient;
import net.sourceforge.filebot.web.SearchResult;
class FetchEpisodeListTask extends SwingWorker<List<Episode>, Void> {
private final String showName;
private final SearchResult searchResult;
private final EpisodeListClient searchEngine;
private final int numberOfSeason;
private long duration = -1;
public FetchEpisodeListTask(EpisodeListClient searchEngine, String showname, int numberOfSeason) {
showName = showname;
public FetchEpisodeListTask(EpisodeListClient searchEngine, SearchResult searchResult, int numberOfSeason) {
this.searchEngine = searchEngine;
this.searchResult = searchResult;
this.numberOfSeason = numberOfSeason;
}
@ -30,15 +33,25 @@ class FetchEpisodeListTask extends SwingWorker<List<Episode>, Void> {
protected List<Episode> doInBackground() throws Exception {
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;
return episodes;
return list;
}
public String getShowName() {
return showName;
public EpisodeListClient getSearchEngine() {
return searchEngine;
}
public SearchResult getSearchResult() {
return searchResult;
}
@ -51,9 +64,4 @@ class FetchEpisodeListTask extends SwingWorker<List<Episode>, Void> {
return duration;
}
public EpisodeListClient getSearchEngine() {
return searchEngine;
}
}

View File

@ -8,9 +8,8 @@ import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.net.URI;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Level;
@ -32,8 +31,6 @@ 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.resources.ResourceManager;
import net.sourceforge.filebot.ui.FileBotPanel;
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.web.Episode;
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.SelectButtonTextField;
import net.sourceforge.tuned.ui.SimpleIconProvider;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import net.sourceforge.tuned.ui.TextCompletion;
import net.sourceforge.tuned.ui.TextFieldWithSelect;
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 TextFieldWithSelect<EpisodeListClient> searchField;
private SelectButtonTextField<EpisodeListClient> searchField;
private TextCompletion searchFieldCompletion;
@ -66,19 +66,12 @@ public class SearchPanel extends FileBotPanel {
public SearchPanel() {
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()) {
episodeListClients.add(new SelectButton.Entry<EpisodeListClient>(client, client.getIcon()));
}
searchField.getSelectButton().setModel(EpisodeListClient.getAvailableEpisodeListClients());
searchField.getSelectButton().setIconProvider(SimpleIconProvider.forClass(EpisodeListClient.class));
searchField = new TextFieldWithSelect<EpisodeListClient>(episodeListClients);
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();
searchField.getSelectButton().addPropertyChangeListener(SelectButton.SELECTED_VALUE, selectButtonListener);
historyPanel.setColumnHeader1("Show");
historyPanel.setColumnHeader2("Number of Episodes");
@ -135,14 +128,14 @@ public class SearchPanel extends FileBotPanel {
seasonSpinnerModel.setValue(value);
}
private final PropertyChangeListener searchFieldListener = new PropertyChangeListener() {
private final PropertyChangeListener selectButtonListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
EpisodeListClient client = searchField.getSelectedValue();
EpisodeListClient client = searchField.getSelected();
if (!client.isSingleSeasonSupported()) {
seasonSpinnerModel.setMaximum(SeasonSpinnerEditor.ALL_SEASONS);
seasonSpinnerModel.setValue(SeasonSpinnerEditor.ALL_SEASONS);
seasonSpinnerModel.setMaximum(SeasonSpinnerEditor.ALL_SEASONS);
} else {
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")) {
public void actionPerformed(ActionEvent e) {
searchField.clearTextSelection();
EpisodeListClient searchEngine = searchField.getSelected();
EpisodeListClient searchEngine = searchField.getSelectedValue();
SearchTask task = new SearchTask(searchEngine, searchField.getTextField().getText(), seasonSpinnerModel.getNumber().intValue());
SearchTask task = new SearchTask(searchEngine, searchField.getText(), seasonSpinnerModel.getNumber().intValue());
task.addPropertyChangeListener(new SearchTaskListener());
task.execute();
@ -182,10 +174,10 @@ public class SearchPanel extends FileBotPanel {
@Override
public void actionPerformed(ActionEvent e) {
Component c = tabbedPane.getSelectedComponent();
Component comp = tabbedPane.getSelectedComponent();
if (c instanceof Saveable) {
setSaveable((Saveable) c);
if (comp instanceof Saveable) {
setSaveable((Saveable) comp);
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 EpisodeListClient client;
private int numberOfSeason;
private final String query;
private final EpisodeListClient client;
private final int numberOfSeason;
public SearchTask(EpisodeListClient client, String query, int numberOfSeason) {
@ -208,7 +200,7 @@ public class SearchPanel extends FileBotPanel {
@Override
protected List<String> doInBackground() throws Exception {
protected List<SearchResult> doInBackground() throws Exception {
return client.search(query);
}
@ -250,14 +242,14 @@ public class SearchPanel extends FileBotPanel {
SearchTask task = (SearchTask) evt.getSource();
List<String> shows = null;
List<SearchResult> searchResults;
try {
shows = task.get();
searchResults = task.get();
} catch (Exception e) {
tabbedPane.remove(episodeList);
Throwable cause = FileBotUtil.getRootCause(e);
Throwable cause = ExceptionUtil.getRootCause(e);
MessageManager.showWarning(cause.getMessage());
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, cause.toString());
@ -265,38 +257,43 @@ public class SearchPanel extends FileBotPanel {
return;
}
String showname = null;
SearchResult selectedResult = null;
/*
* NEEDED??? exact find without cache???
/// TODO: ??????
if (task.client.getFoundName(task.query) != null) {
// a show matching the search term exactly has already been found
showname = task.client.getFoundName(task.query);
} else if (shows.size() == 1) {
}*/
if (searchResults.size() == 1) {
// only one show found, select this one
showname = shows.get(0);
} else if (shows.size() > 1) {
selectedResult = searchResults.get(0);
} else if (searchResults.size() > 1) {
// multiple shows found, let user selected one
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.setIconImage(episodeList.getIcon().getImage());
select.setVisible(true);
showname = select.getSelectedValue();
selectedResult = select.getSelectedValue();
} else {
MessageManager.showWarning("\"" + task.query + "\" has not been found.");
}
if (showname == null) {
if (selectedResult == null) {
tabbedPane.remove(episodeList);
return;
}
searchFieldCompletion.addTerm(showname);
Settings.getSettings().putStringList(Settings.SEARCH_HISTORY, searchFieldCompletion.getTerms());
String title = selectedResult.getName();
String title = showname;
searchFieldCompletion.addTerm(title);
//TODO fix
// Settings.getSettings().putStringList(Settings.SEARCH_HISTORY, searchFieldCompletion.getTerms());
if (task.numberOfSeason != SeasonSpinnerEditor.ALL_SEASONS) {
title += String.format(" - Season %d", task.numberOfSeason);
@ -304,7 +301,7 @@ public class SearchPanel extends FileBotPanel {
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.execute();
@ -331,13 +328,13 @@ public class SearchPanel extends FileBotPanel {
FetchEpisodeListTask task = (FetchEpisodeListTask) evt.getSource();
try {
URL url = task.getSearchEngine().getEpisodeListUrl(task.getShowName(), task.getNumberOfSeason());
URI link = task.getSearchEngine().getEpisodeListLink(task.getSearchResult(), task.getNumberOfSeason());
Collection<Episode> episodes = task.get();
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)
tabbedPane.remove(episodeList);
@ -348,8 +345,10 @@ public class SearchPanel extends FileBotPanel {
} catch (Exception e) {
tabbedPane.remove(episodeList);
MessageManager.showWarning(FileBotUtil.getRootCause(e).getMessage());
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
Throwable cause = ExceptionUtil.getRootCause(e);
MessageManager.showWarning(cause.getMessage());
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, cause.getMessage(), cause);
}
}
}

View File

@ -15,7 +15,7 @@ import javax.swing.event.TableModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableModel;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.tuned.FileUtil;
class ChecksumTableModel extends AbstractTableModel {
@ -38,7 +38,7 @@ class ChecksumTableModel extends AbstractTableModel {
if (columnIndex >= checksumColumnsOffset) {
File columnRoot = checksumColumnRoots.get(columnIndex - checksumColumnsOffset);
return FileFormat.getFolderName(columnRoot);
return FileUtil.getFolderName(columnRoot);
}
return null;

View File

@ -17,13 +17,13 @@ import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import net.sourceforge.filebot.FileFormat;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.ui.FileBotPanel;
import net.sourceforge.filebot.ui.FileTransferableMessageHandler;
import net.sourceforge.filebot.ui.SelectDialog;
import net.sourceforge.filebot.ui.transfer.LoadAction;
import net.sourceforge.filebot.ui.transfer.SaveAction;
import net.sourceforge.tuned.FileUtil;
import net.sourceforge.tuned.MessageBus;
import net.sourceforge.tuned.ui.TunedUtil;
@ -110,7 +110,7 @@ public class SfvPanel extends FileBotPanel {
@Override
protected String convertValueToString(Object value) {
File columnRoot = (File) value;
return FileFormat.getFolderName(columnRoot);
return FileUtil.getFolderName(columnRoot);
}
};
@ -125,7 +125,7 @@ public class SfvPanel extends FileBotPanel {
return;
index = options.indexOf(selected);
name = FileFormat.getFileName(selected);
name = FileUtil.getFileName(selected);
if (name.isEmpty())
name = "name";

View File

@ -16,7 +16,6 @@ import javax.swing.event.TableModelEvent;
import javax.swing.table.TableColumn;
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.renderer.ChecksumTableCellRenderer;
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.TransferablePolicyImportHandler;
import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy;
import net.sourceforge.tuned.FileUtil;
class SfvTable extends JTable implements Saveable {
@ -103,7 +103,7 @@ class SfvTable extends JTable implements Saveable {
String name = "";
if (columnRoot != null)
name = FileFormat.getFileName(columnRoot);
name = FileUtil.getFileName(columnRoot);
if (name.isEmpty())
name = "name";

View File

@ -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;
}
}

View File

@ -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());
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}
}
}
}

View File

@ -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();
}
}
}

View File

@ -46,9 +46,9 @@ public class SubtitleCellRenderer extends IconViewCellRenderer {
setText(subtitle.getName());
info1.setText(subtitle.getLanguageName());
info1.setText(subtitle.getLanguage().getName());
icon = subtitle.getLanguageIcon();
icon = subtitle.getLanguage().getIcon();
info1.setIcon(icon);

View File

@ -2,14 +2,10 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.util.List;
import java.awt.BorderLayout;
import javax.swing.BoxLayout;
import javax.swing.JPanel;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.tuned.ui.SimpleListModel;
public class SubtitleDownloadPanel extends JPanel {
@ -17,9 +13,9 @@ public class SubtitleDownloadPanel extends JPanel {
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;
}
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);
}
}

View File

@ -3,34 +3,42 @@ package net.sourceforge.filebot.ui.panel.subtitle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.ImageIcon;
import javax.swing.SwingWorker.StateValue;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.tuned.DownloadTask;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import org.jdesktop.beans.AbstractBean;
public class SubtitlePackage {
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public class SubtitlePackage extends AbstractBean {
private final SubtitleDescriptor subtitleDescriptor;
private final ArchiveType archiveType;
private final ImageIcon archiveIcon;
private final ImageIcon languageIcon;
private final Language language;
private DownloadTask downloadTask;
private StateValue downloadState = StateValue.PENDING;
private float downloadProgress = 0;
public SubtitlePackage(SubtitleDescriptor subtitleDescriptor) {
this.subtitleDescriptor = subtitleDescriptor;
archiveIcon = ResourceManager.getArchiveIcon(subtitleDescriptor.getArchiveType());
languageIcon = ResourceManager.getFlagIcon(LanguageResolver.getDefault().getLanguageCode(subtitleDescriptor.getLanguageName()));
this.language = new Language(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() {
return ArchiveType.forName(subtitleDescriptor.getArchiveType());
return archiveType;
}
@ -49,17 +62,13 @@ public class SubtitlePackage {
}
public String getLanguageName() {
return subtitleDescriptor.getLanguageName();
@Override
public String toString() {
return getName();
}
public ImageIcon getLanguageIcon() {
return languageIcon;
}
public synchronized void startDownload() {
public synchronized void download() {
if (downloadTask != null)
throw new IllegalStateException("Download has been started already");
@ -70,31 +79,46 @@ public class SubtitlePackage {
}
public DownloadTask getDownloadTask() {
if (downloadTask == null)
throw new IllegalStateException("Download has not been started");
return downloadTask;
public StateValue getDownloadState() {
return downloadState;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
private void setDownloadState(StateValue downloadState) {
this.downloadState = downloadState;
firePropertyChange("downloadState", null, downloadState);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
public float getDownloadProgress() {
return downloadProgress;
}
private void setDownloadProgress(float downloadProgress) {
this.downloadProgress = downloadProgress;
firePropertyChange("downloadProgress", null, downloadProgress);
}
private class DownloadTaskPropertyChangeAdapter implements PropertyChangeListener {
private class DownloadTaskPropertyChangeAdapter extends SwingWorkerPropertyChangeAdapter {
@Override
public void propertyChange(PropertyChangeEvent evt) {
propertyChangeSupport.firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
public void started(PropertyChangeEvent evt) {
setDownloadState(StateValue.STARTED);
}
@Override
public void done(PropertyChangeEvent evt) {
setDownloadState(StateValue.DONE);
}
@Override
public void progress(PropertyChangeEvent evt) {
setDownloadProgress((Float) evt.getNewValue() / 100);
}
};
}

View File

@ -2,186 +2,168 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.awt.AlphaComposite;
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.JToggleButton;
import javax.swing.ListModel;
import javax.swing.JScrollPane;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.tuned.ui.IconViewPanel;
import net.sourceforge.tuned.ui.SimpleListModel;
import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
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 Map<String, Boolean> languageFilterSelection = new TreeMap<String, Boolean>(String.CASE_INSENSITIVE_ORDER);
private final LanguageSelectionPanel languageSelection = new LanguageSelectionPanel(model);
public SubtitlePackagePanel() {
setCellRenderer(new SubtitleCellRenderer());
languageFilterPanel.setOpaque(false);
getHeaderPanel().add(languageFilterPanel, BorderLayout.EAST);
languageFilterSelection.putAll(Settings.getSettings().getBooleanMap(Settings.SUBTITLE_LANGUAGE));
super(new BorderLayout());
add(languageSelection, BorderLayout.NORTH);
add(new JScrollPane(createList()), BorderLayout.CENTER);
}
@Override
public void setModel(ListModel model) {
unfilteredModel = model;
updateLanguageFilterButtonPanel();
updateFilteredModel();
public EventList<SubtitlePackage> getModel() {
return model;
}
@Override
public ListModel getModel() {
return unfilteredModel;
protected JList createList() {
FilterList<SubtitlePackage> filterList = new FilterList<SubtitlePackage>(model, new LanguageMatcherEditor(languageSelection));
ObservableElementList<SubtitlePackage> observableList = new ObservableElementList<SubtitlePackage>(filterList, GlazedLists.beanConnector(SubtitlePackage.class));
JList list = new JList(new EventListModel<SubtitlePackage>(observableList));
return list;
}
private void updateLanguageFilterButtonPanel() {
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());
}
languageFilterPanel.removeAll();
for (String language : languages) {
LanguageFilterButton languageFilterButton = createLanguageFilterButton(language);
languageFilterButton.addItemListener(new LanguageFilterItemListener(language));
/*
private void updateLanguageFilterButtonPanel() {
languageFilterPanel.add(languageFilterButton);
}
}
private void updateFilteredModel() {
SimpleListModel model = new SimpleListModel();
for (int i = 0; i < unfilteredModel.getSize(); i++) {
SubtitlePackage subtitle = (SubtitlePackage) unfilteredModel.getElementAt(i);
SortedSet<String> languages = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
if (isLanguageSelected(subtitle.getLanguageName())) {
model.add(subtitle);
for (int i = 0; i < unfilteredModel.getSize(); i++) {
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));
languageFilterPanel.add(languageFilterButton);
}
}
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().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);
private void updateFilteredModel() {
SimpleListModel model = new SimpleListModel();
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);
for (int i = 0; i < unfilteredModel.getSize(); i++) {
SubtitlePackage subtitle = (SubtitlePackage) unfilteredModel.getElementAt(i);
if (isLanguageSelected(subtitle.getLanguageName())) {
model.add(subtitle);
}
}
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);
}
}
*/
}

View File

@ -2,285 +2,122 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.awt.BorderLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.text.NumberFormat;
import java.util.ArrayList;
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.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.resources.ResourceManager;
import net.sourceforge.filebot.ui.FileBotPanel;
import net.sourceforge.filebot.ui.HistoryPanel;
import net.sourceforge.filebot.ui.MessageManager;
import net.sourceforge.filebot.ui.AbstractSearchPanel;
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.SubtitleDescriptor;
import net.sourceforge.tuned.ui.SelectButton;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import net.sourceforge.tuned.ui.TextCompletion;
import net.sourceforge.tuned.ui.TextFieldWithSelect;
import net.sourceforge.tuned.ui.TunedUtil;
import net.sourceforge.tuned.BasicCachingList;
import net.sourceforge.tuned.FunctionIterator;
import net.sourceforge.tuned.ProgressIterator;
import net.sourceforge.tuned.FunctionIterator.Function;
import net.sourceforge.tuned.ui.SimpleIconProvider;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
public class SubtitlePanel extends FileBotPanel {
private JTabbedPane tabbedPane = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT);
private HistoryPanel historyPanel = new HistoryPanel();
private TextFieldWithSelect<SubtitleClient> searchField;
private TextCompletion searchFieldCompletion;
public class SubtitlePanel extends AbstractSearchPanel<SubtitleClient, SubtitlePackage, SubtitleDownloadPanel> {
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()) {
clients.add(new SelectButton.Entry<SubtitleClient>(client, client.getIcon()));
}
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);
getSearchField().getSelectButton().setModel(SubtitleClient.getAvailableSubtitleClients());
getSearchField().getSelectButton().setIconProvider(SimpleIconProvider.forClass(SubtitleClient.class));
}
private final AbstractAction searchAction = new AbstractAction("Find", ResourceManager.getIcon("action.find")) {
public void actionPerformed(ActionEvent e) {
searchField.clearTextSelection();
SearchTask searchTask = new SearchTask(searchField.getSelectedValue(), searchField.getTextField().getText());
searchTask.addPropertyChangeListener(new SearchTaskListener());
searchTask.execute();
}
};
private static EventList<String> globalSearchHistory() {
return GlazedLists.eventList(new BasicCachingList<String>(Settings.getSettings().asStringList(Settings.SUBTITLE_HISTORY)));
}
private final AbstractAction saveAction = new AbstractAction("Down") {
@Override
protected SearchTask createSearchTask() {
SubtitleDownloadPanel panel = new SubtitleDownloadPanel();
public void actionPerformed(ActionEvent e) {
//TODO save action
}
};
return new SubtitleSearchTask(getSearchField().getSelected(), getSearchField().getText(), panel);
}
@Override
protected void configureSelectDialog(SelectDialog<SearchResult> selectDialog) {
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 SearchTask extends SwingWorker<List<MovieDescriptor>, Void> {
private class SubtitleSearchTask extends SearchTask {
private final String query;
private final SubtitleClient client;
public SearchTask(SubtitleClient client, String query) {
this.client = client;
this.query = query;
public SubtitleSearchTask(SubtitleClient client, String searchText, SubtitleDownloadPanel panel) {
super(client, searchText, panel);
}
@Override
protected List<MovieDescriptor> doInBackground() throws Exception {
return client.search(query);
protected List<SearchResult> doInBackground() throws Exception {
return getClient().search(getSearchText());
}
}
private class SearchTaskListener extends SwingWorkerPropertyChangeAdapter {
private class SubtitleFetchTask extends FetchTask {
private FileBotTab<SubtitleDownloadPanel> downloadPanel;
@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);
public SubtitleFetchTask(SubtitleClient client, SearchResult searchResult, SubtitleDownloadPanel tabPanel) {
super(client, searchResult, tabPanel);
}
@Override
public void done(PropertyChangeEvent evt) {
// tab might have been closed
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());
}
protected ProgressIterator<SubtitlePackage> fetch() throws Exception {
ProgressIterator<SubtitleDescriptor> descriptors = getClient().getSubtitleList(getSearchResult());
return new FunctionIterator<SubtitleDescriptor, SubtitlePackage>(descriptors, new SubtitlePackageFunction());
}
private MovieDescriptor selectDescriptor(List<MovieDescriptor> descriptors, SubtitleClient client) {
switch (descriptors.size()) {
case 0:
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
protected void process(List<SubtitlePackage> elements) {
getTabPanel().getPackagePanel().getModel().addAll(elements);
}
@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
public void done(PropertyChangeEvent evt) {
// tab might have been closed
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);
}
public SubtitlePackage evaluate(SubtitleDescriptor sourceValue) {
return new SubtitlePackage(sourceValue);
}
}
}

View File

@ -7,7 +7,7 @@ import java.io.File;
import javax.swing.filechooser.FileFilter;
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;
@ -32,8 +32,8 @@ public class TransferablePolicyFileFilter extends FileFilter {
@Override
public String getDescription() {
if (transferablePolicy instanceof MultiTransferablePolicy) {
MultiTransferablePolicy multi = (MultiTransferablePolicy) transferablePolicy;
if (transferablePolicy instanceof CompositeTransferablePolicy) {
CompositeTransferablePolicy multi = (CompositeTransferablePolicy) transferablePolicy;
return multi.getDescription(FileTransferablePolicy.class);
}

View File

@ -9,12 +9,12 @@ import java.util.Iterator;
import java.util.List;
public class MultiTransferablePolicy implements TransferablePolicy {
public class CompositeTransferablePolicy implements TransferablePolicy {
private List<TransferablePolicy> policies = Collections.synchronizedList(new ArrayList<TransferablePolicy>());
public MultiTransferablePolicy() {
public CompositeTransferablePolicy() {
}

View File

@ -5,20 +5,22 @@ package net.sourceforge.filebot.web;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Collections;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
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.FunctionIterator.Function;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@ -27,9 +29,9 @@ import org.xml.sax.SAXException;
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() {
@ -38,39 +40,32 @@ public class AnidbClient extends EpisodeListClient {
@Override
public List<String> search(String searchterm) throws IOException, SAXException {
synchronized (cache) {
if (getFoundName(searchterm) != null) {
return Arrays.asList(getFoundName(searchterm));
}
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));
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())
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
if (type.equalsIgnoreCase("tv series")) {
Node titleNode = XPathUtil.selectNode("./TD[@class='name']/A", node);
String title = XPathUtil.selectString(".", titleNode);
String href = XPathUtil.selectString("@href", titleNode);
String file = "/perl-bin/" + href;
try {
URL url = new URL("http", host, file);
String title = XPathUtil.selectString(".", titleNode);
String href = XPathUtil.selectString("@href", titleNode);
String file = "/perl-bin/" + href;
try {
URL url = new URL("http", host, file);
searchResults.put(title, url);
} catch (MalformedURLException e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href);
}
searchResults.add(new HyperLink(title, url));
} catch (MalformedURLException e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href);
}
}
else {
@ -82,32 +77,44 @@ public class AnidbClient extends EpisodeListClient {
String header = XPathUtil.selectString("id('layout-content')//H1[1]", dom);
String title = header.replaceFirst("Anime:\\s*", "");
searchResults.put(title, getSearchUrl(searchterm));
searchResults.add(new HyperLink(title, getSearchUrl(searchterm)));
}
}
synchronized (cache) {
cache.putAll(searchResults);
}
cache.addAll(searchResults);
return new ArrayList<String>(searchResults.keySet());
return searchResults;
}
@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);
ArrayList<Episode> list = new ArrayList<Episode>(nodes.size());
return new FunctionIterator<Node, Episode>(nodes, new EpisodeFunction(searchResult, nodes.size()));
}
private static class EpisodeFunction implements Function<Node, Episode> {
NumberFormat f = NumberFormat.getInstance();
f.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
f.setGroupingUsed(false);
private final SearchResult searchResult;
private final NumberFormat numberFormat;
for (Node node : nodes) {
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 title = XPathUtil.selectString("./TD[@class='title']/LABEL/text()", node);
@ -116,41 +123,29 @@ public class AnidbClient extends EpisodeListClient {
try {
// try to format number of episode
number = f.format(Integer.parseInt(number));
number = numberFormat.format(Integer.parseInt(number));
} catch (NumberFormatException ex) {
// 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
public URL getEpisodeListUrl(String showname, int season) {
synchronized (cache) {
return cache.get(showname);
}
}
@Override
public String getFoundName(String searchterm) {
synchronized (cache) {
if (cache.containsKey(searchterm)) {
return cache.floorKey(searchterm);
}
}
return null;
public URI getEpisodeListLink(SearchResult searchResult, int season) {
return ((HyperLink) searchResult).getUri();
}
private URL getSearchUrl(String searchterm) throws UnsupportedEncodingException, MalformedURLException {
String qs = URLEncoder.encode(searchterm, "UTF-8");
String file = "/perl-bin/animedb.pl?show=animelist&orderby=name&orderdir=0&adb.search=" + qs + "&noalias=1&notinml=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);
}

View File

@ -2,13 +2,15 @@
package net.sourceforge.filebot.web;
import java.net.URL;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.ImageIcon;
import net.sourceforge.tuned.ProgressIterator;
public abstract class EpisodeListClient {
@ -27,43 +29,9 @@ public abstract class EpisodeListClient {
return Collections.unmodifiableList(registry);
}
public static EpisodeListClient forName(String name) {
for (EpisodeListClient client : registry) {
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;
private final String name;
private final boolean singleSeasonSupported;
private final ImageIcon icon;
public EpisodeListClient(String name, ImageIcon icon, boolean singleSeasonSupported) {
@ -73,8 +41,17 @@ public abstract class EpisodeListClient {
}
public String getName() {
return name;
public abstract List<SearchResult> search(String searchterm) throws Exception;
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
public String toString() {
return getName();
return name;
}
}

View File

@ -6,6 +6,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
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 {
URLConnection connection = url.openConnection();

View File

@ -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;
}
}

View File

@ -2,36 +2,19 @@
package net.sourceforge.filebot.web;
public class MovieDescriptor {
public class MovieDescriptor extends SearchResult {
private final String title;
private final Integer imdbId;
private final int imdbId;
public MovieDescriptor(String title) {
this(title, null);
}
public MovieDescriptor(String title, Integer imdbId) {
this.title = title;
public MovieDescriptor(String name, int imdbId) {
super(name);
this.imdbId = imdbId;
}
public String getTitle() {
return title;
}
public Integer getImdbId() {
public int getImdbId() {
return imdbId;
}
@Override
public String toString() {
return title;
}
}

View File

@ -2,6 +2,7 @@
package net.sourceforge.filebot.web;
import java.net.URI;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
@ -10,6 +11,9 @@ import java.util.logging.Logger;
import net.sourceforge.filebot.Settings;
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
public List<MovieDescriptor> search(String query) throws Exception {
public List<SearchResult> search(String searchterm) throws Exception {
login();
return client.searchMoviesOnIMDB(query);
List result = client.searchMoviesOnIMDB(searchterm);
return result;
}
@Override
public List<OpenSubtitlesSubtitleDescriptor> getSubtitleList(MovieDescriptor descriptor) throws Exception {
public ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception {
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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -5,21 +5,23 @@ package net.sourceforge.filebot.web;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.filebot.FileFormat;
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.FunctionIterator.Function;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@ -28,7 +30,7 @@ import org.xml.sax.SAXException;
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";
@ -39,13 +41,16 @@ public class SubsceneSubtitleClient extends SubtitleClient {
@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));
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) {
String title = XPathUtil.selectString("text()", node);
@ -54,66 +59,80 @@ public class SubsceneSubtitleClient extends SubtitleClient {
try {
URL url = new URL("http", host, href);
MovieDescriptor descriptor = new MovieDescriptor(title);
cache.put(descriptor, url);
results.add(descriptor);
searchResults.add(new HyperLink(title, url));
} catch (MalformedURLException e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e);
}
}
return results;
cache.addAll(searchResults);
return searchResults;
}
@Override
public List<SubsceneSubtitleDescriptor> getSubtitleList(MovieDescriptor descriptor) throws IOException, SAXException {
URL url = cache.get(descriptor);
public ProgressIterator<SubtitleDescriptor> getSubtitleList(SearchResult searchResult) throws Exception {
URL url = getSubtitleListLink(searchResult).toURL();
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<SubsceneSubtitleDescriptor> list = new ArrayList<SubsceneSubtitleDescriptor>();
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;
return new FunctionIterator<Node, SubtitleDescriptor>(nodes, new SubtitleFunction(url));
}
private URL getDownloadUrl(URL referer, String subtitleId, String typeId) throws MalformedURLException {
String basePath = FileFormat.getNameWithoutExtension(referer.getFile());
String path = String.format("%s-dlpath-%s/%s.zipx", basePath, subtitleId, typeId);
private static class SubtitleFunction implements Function<Node, SubtitleDescriptor> {
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();
}

View File

@ -2,12 +2,15 @@
package net.sourceforge.filebot.web;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.ImageIcon;
import net.sourceforge.tuned.ProgressIterator;
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() {
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
public String toString() {
return name;

View File

@ -5,21 +5,23 @@ package net.sourceforge.filebot.web;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Collections;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.FunctionIterator.Function;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@ -28,9 +30,9 @@ import org.xml.sax.SAXException;
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() {
@ -39,18 +41,16 @@ public class TVRageClient extends EpisodeListClient {
@Override
public List<String> search(String searchterm) throws IOException, SAXException {
synchronized (cache) {
if (getFoundName(searchterm) != null) {
return Arrays.asList(getFoundName(searchterm));
}
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));
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) {
String href = XPathUtil.selectString("@href", node);
@ -58,30 +58,44 @@ public class TVRageClient extends EpisodeListClient {
try {
URL url = new URL(href);
searchResults.put(title, url);
searchResults.add(new HyperLink(title, url));
} catch (MalformedURLException e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e);
}
}
synchronized (cache) {
cache.putAll(searchResults);
}
cache.addAll(searchResults);
return new ArrayList<String>(searchResults.keySet());
return searchResults;
}
@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);
ArrayList<Episode> episodes = new ArrayList<Episode>();
return new FunctionIterator<Node, Episode>(nodes, new EpisodeFunction(searchResult, season));
}
private static class EpisodeFunction implements Function<Node, Episode> {
for (Node node : nodes) {
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 title = XPathUtil.selectString("./TD[5]", node);
@ -105,49 +119,33 @@ public class TVRageClient extends EpisodeListClient {
} else {
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;
}
}
@Override
public String getFoundName(String searchterm) {
synchronized (cache) {
if (cache.containsKey(searchterm)) {
return cache.floorKey(searchterm);
}
public URI getEpisodeListLink(SearchResult searchResult, int season) {
URI baseUri = ((HyperLink) searchResult).getUri();
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);
}
}

View File

@ -5,20 +5,23 @@ package net.sourceforge.filebot.web;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Collections;
import java.util.List;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
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.FunctionIterator.Function;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
@ -27,9 +30,9 @@ import org.xml.sax.SAXException;
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() {
@ -38,18 +41,16 @@ public class TvdotcomClient extends EpisodeListClient {
@Override
public List<String> search(String searchterm) throws IOException, SAXException {
synchronized (cache) {
if (getFoundName(searchterm) != null) {
return Arrays.asList(getFoundName(searchterm));
}
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));
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) {
String category = node.getParentNode().getTextContent();
@ -62,45 +63,53 @@ public class TvdotcomClient extends EpisodeListClient {
try {
URL url = new URL(href);
searchResults.put(title, url);
searchResults.add(new HyperLink(title, url));
} catch (MalformedURLException e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid href: " + href, e);
}
}
}
synchronized (cache) {
cache.putAll(searchResults);
}
cache.addAll(searchResults);
return new ArrayList<String>(searchResults.keySet());
return searchResults;
}
@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);
String seasonString = null;
return new FunctionIterator<Node, Episode>(nodes, new EpisodeFunction(searchResult, season, nodes.size()));
}
private static class EpisodeFunction implements Function<Node, Episode> {
if (season >= 1)
seasonString = Integer.toString(season);
private final SearchResult searchResult;
private final NumberFormat numberFormat;
ArrayList<Episode> episodes = new ArrayList<Episode>(nodes.size());
private Integer episodeOffset = null;
private String seasonString = null;
NumberFormat numberFormat = NumberFormat.getInstance();
numberFormat.setMinimumIntegerDigits(Math.max(Integer.toString(nodes.size()).length(), 2));
numberFormat.setGroupingUsed(false);
Integer episodeOffset = null;
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);
}
if (season == 1)
episodeOffset = 0;
for (Node node : nodes) {
@Override
public Episode evaluate(Node node) {
String episodeNumber = XPathUtil.selectString("./TD[1]", node);
String title = XPathUtil.selectString("./TD[2]/A", node);
@ -113,47 +122,32 @@ public class TvdotcomClient extends EpisodeListClient {
episodeNumber = numberFormat.format(n - episodeOffset);
} 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
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 {
String summaryFile = null;
synchronized (cache) {
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;
return new URI("http", host, file, query, null);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
}
@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 {
String qs = URLEncoder.encode(searchterm, "UTF-8");
String file = "/search.php?qs=" + qs + "&type=11&stype=all";

View File

@ -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();
}
}

View File

@ -1,14 +1,11 @@
package net.sourceforge.filebot;
package net.sourceforge.tuned;
import java.io.File;
import java.text.NumberFormat;
public class FileFormat {
private static final NumberFormat numberFormat = NumberFormat.getNumberInstance();
public class FileUtil {
public final static long KILO = 1024;
@ -16,18 +13,14 @@ public class FileFormat {
public final static long GIGA = MEGA * 1024;
static {
numberFormat.setMaximumFractionDigits(0);
}
public static String formatSize(long size) {
if (size >= MEGA)
return numberFormat.format((double) size / MEGA) + " MB";
return String.format("%d MB", (double) size / MEGA);
else if (size >= KILO)
return numberFormat.format((double) size / KILO) + " KB";
return String.format("%d KB", (double) size / KILO);
else
return numberFormat.format(size) + " Byte";
return String.format("%d Byte", size);
}

View File

@ -2,11 +2,12 @@
package net.sourceforge.tuned;
import java.util.Collection;
import java.util.Iterator;
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.
@ -22,20 +23,30 @@ public class FunctionIterator<S, T> implements Iterator<T> {
* @param sourceValue - the Object to transform
* @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 Function<S, T> function;
private final int length;
private int position = 0;
public FunctionIterator(Iterable<S> source, Function<S, T> function) {
this(source.iterator(), function);
public FunctionIterator(Collection<S> source, Function<S, T> 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.length = length;
this.function = function;
}
@ -74,20 +85,38 @@ public class FunctionIterator<S, T> implements Iterator<T> {
try {
cache = transform(sourceIterator.next());
} catch (RuntimeException e) {
currentException = e;
} catch (Exception e) {
currentException = ExceptionUtil.asRuntimeException(e);
}
position++;
}
return cache;
}
private T transform(S sourceValue) {
private T transform(S sourceValue) throws Exception {
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>.
*
@ -98,4 +127,5 @@ public class FunctionIterator<S, T> implements Iterator<T> {
public void remove() {
throw new UnsupportedOperationException();
}
}

View File

@ -13,7 +13,6 @@ import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@ -40,7 +39,7 @@ public class PreferencesMap<T> implements Map<String, T> {
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
public T remove(Object key) {
if (key instanceof String) {
prefs.remove((String) key);
adapter.remove(prefs, (String) key);
}
return null;
@ -67,7 +66,7 @@ public class PreferencesMap<T> implements Map<String, T> {
public String[] keys() {
try {
return prefs.keys();
return adapter.keys(prefs);
} catch (BackingStoreException e) {
throw new RuntimeException(e);
}
@ -76,10 +75,8 @@ public class PreferencesMap<T> implements Map<String, T> {
@Override
public void clear() {
try {
prefs.clear();
} catch (BackingStoreException e) {
throw new RuntimeException(e);
for (String key : keys()) {
adapter.remove(prefs, key);
}
}
@ -131,7 +128,7 @@ public class PreferencesMap<T> implements Map<String, T> {
@Override
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 String[] keys(Preferences prefs) throws BackingStoreException;
public T get(Preferences prefs, String key);
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
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;
@ -260,6 +287,7 @@ public class PreferencesMap<T> implements Map<String, T> {
try {
return constructor.newInstance(stringValue);
} catch (InvocationTargetException e) {
// try to throw the cause directly, e.g. NumberFormatException
throw ExceptionUtil.asRuntimeException(e.getCause());
} catch (Exception 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")
@Override

View File

@ -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();
}

View File

@ -9,7 +9,7 @@ import java.awt.SystemColor;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.net.URL;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -18,13 +18,13 @@ import javax.swing.JLabel;
public class HyperlinkLabel extends JLabel {
private URL url;
private Color defaultColor;
private final URI link;
private final Color defaultColor;
public HyperlinkLabel(String label, URL url) {
public HyperlinkLabel(String label, URI link) {
super(label);
this.url = url;
this.link = link;
defaultColor = getForeground();
addMouseListener(linker);
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
@ -35,7 +35,7 @@ public class HyperlinkLabel extends JLabel {
@Override
public void mouseClicked(MouseEvent event) {
try {
Desktop.getDesktop().browse(url.toURI());
Desktop.getDesktop().browse(link);
} catch (Exception e) {
// should not happen
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);

View File

@ -0,0 +1,12 @@
package net.sourceforge.tuned.ui;
import javax.swing.Icon;
public interface IconProvider<T> {
public Icon getIcon(T value);
}

View File

@ -17,8 +17,6 @@ import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListModel;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
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() {
// TODO expand
}

View File

@ -2,6 +2,7 @@
package net.sourceforge.tuned.ui;
import java.awt.Dimension;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
@ -17,44 +18,53 @@ public class LoadingOverlayPane extends JComponent {
public static final String LOADING_PROPERTY = "loading";
private final JLabel loadingLabel;
private final JComponent animationComponent;
private boolean overlayEnabled = false;
private int millisToOverlay = 500;
private final JComponent view;
public LoadingOverlayPane(JComponent component, Icon animation) {
this(component, new JLabel(""), getView(component));
}
public LoadingOverlayPane(JComponent component, JComponent animation) {
this(component, animation, getView(component));
}
public LoadingOverlayPane(JComponent component, Icon animation, JComponent view) {
this.view = view;
public LoadingOverlayPane(JComponent component, JComponent animation, JComponent view) {
setLayout(new OverlayLayout(this));
this.animationComponent = animation;
component.setAlignmentX(1.0f);
component.setAlignmentY(0.0f);
loadingLabel = new JLabel(animation);
loadingLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 20));
animation.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 20));
loadingLabel.setAlignmentX(1.0f);
loadingLabel.setAlignmentY(0.0f);
loadingLabel.setMaximumSize(loadingLabel.getPreferredSize());
animation.setAlignmentX(1.0f);
animation.setAlignmentY(0.0f);
animationComponent.setPreferredSize(new Dimension(48, 48));
animationComponent.setMaximumSize(animationComponent.getPreferredSize());
add(loadingLabel);
add(animation);
add(component);
setOverlayVisible(false);
setOverlayVisible(true);
view.addPropertyChangeListener(LOADING_PROPERTY, loadingListener);
}
@Override
public boolean isOptimizedDrawingEnabled() {
return false;
}
private static JComponent getView(JComponent component) {
if (component instanceof JScrollPane) {
JScrollPane scrollPane = (JScrollPane) component;
@ -65,11 +75,6 @@ public class LoadingOverlayPane extends JComponent {
}
public JComponent getView() {
return view;
}
public void setOverlayVisible(boolean b) {
overlayEnabled = b;
@ -79,13 +84,13 @@ public class LoadingOverlayPane extends JComponent {
@Override
public void run() {
if (overlayEnabled) {
loadingLabel.setVisible(true);
animationComponent.setVisible(true);
}
}
});
} else {
loadingLabel.setVisible(false);
animationComponent.setVisible(false);
}
}

View File

@ -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;
}
}

View File

@ -11,124 +11,135 @@ import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.DefaultSingleSelectionModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.SingleSelectionModel;
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 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);
setFocusable(false);
addActionListener(this);
super.setIcon(selectIcon);
setHorizontalAlignment(SwingConstants.CENTER);
setVerticalAlignment(SwingConstants.CENTER);
addMouseListener(new MouseInputListener());
setPreferredSize(new Dimension(32, 22));
// select first entry
addActionListener(new OpenPopupOnClick());
}
public void setModel(List<T> model) {
this.model = model;
setSelectedIndex(0);
}
public void setSelectedValue(T value) {
Entry<T> entry = find(value);
if (entry == null)
return;
selectedEntry = entry;
setIcon(new SelectIcon(selectedEntry.getIcon()));
firePropertyChange(SELECTED_VALUE_PROPERTY, null, selectedEntry);
public IconProvider<T> getIconProvider() {
return iconProvider;
}
public T getSelectedEntry() {
return selectedEntry.getValue();
public void setIconProvider(IconProvider<T> iconProvider) {
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) {
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() {
return entries.indexOf(selectedEntry);
}
private Entry<T> find(T value) {
for (Entry<T> entry : entries) {
if (entry.value == value)
return entry;
}
return null;
return selectionModel.getSelectedIndex();
}
public void spinValue(int spin) {
spin = spin % entries.size();
int size = model.size();
spin = spin % size;
int next = getSelectedIndex() + spin;
if (next < 0)
next += entries.size();
else if (next >= entries.size())
next -= entries.size();
next += size;
else if (next >= size)
next -= size;
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
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
@ -143,21 +154,62 @@ public class SelectButton<T> extends JButton implements ActionListener {
super.paintComponent(g);
}
@Override
protected void processMouseEvent(MouseEvent e) {
switch (e.getID()) {
case MouseEvent.MOUSE_ENTERED:
hover = true;
repaint();
break;
case MouseEvent.MOUSE_EXITED:
hover = false;
repaint();
break;
}
super.processMouseEvent(e);
}
private class SelectMenuItem extends JMenuItem implements ActionListener {
private class OpenPopupOnClick implements ActionListener {
private T value;
public SelectMenuItem(Entry<T> entry) {
super(entry.toString(), entry.getIcon());
this.value = entry.getValue();
@Override
public void actionPerformed(ActionEvent e) {
JPopupMenu popup = new JPopupMenu();
this.setMargin(new Insets(3, 0, 3, 0));
this.addActionListener(this);
for (T value : model) {
SelectPopupMenuItem item = new SelectPopupMenuItem(value, iconProvider.getIcon(value));
if (value == getSelectedValue())
item.setSelected(true);
popup.add(item);
}
if (this.value == getSelectedEntry())
this.setFont(this.getFont().deriveFont(Font.BOLD));
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 final GeneralPath arrow;
private Icon icon;
private GeneralPath arrow;
public SelectIcon(Icon icon) {
this.icon = icon;
public SelectIcon() {
arrow = new GeneralPath(Path2D.WIND_EVEN_ODD, 3);
int x = 25;
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;
icon.paintIcon(c, g2d, 4, 3);
if (icon != null) {
icon.paintIcon(c, g2d, 4, 3);
}
g2d.setPaint(Color.BLACK);
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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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&lt;T&gt;(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);
}
}
}

View File

@ -11,7 +11,6 @@ import javax.swing.SwingWorker;
import javax.swing.Timer;
public class SwingWorkerProgressMonitor {
public static final String PROPERTY_NOTE = "note";

View File

@ -5,7 +5,7 @@ package net.sourceforge.tuned.ui;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.SwingWorker;
import javax.swing.SwingWorker.StateValue;
public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChangeListener {
@ -13,10 +13,24 @@ public abstract class SwingWorkerPropertyChangeAdapter implements PropertyChange
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("progress"))
progress(evt);
else if (evt.getNewValue().equals(SwingWorker.StateValue.STARTED))
started(evt);
else if (evt.getNewValue().equals(SwingWorker.StateValue.DONE))
done(evt);
else if (evt.getPropertyName().equals("state"))
state(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 progress(PropertyChangeEvent evt) {
}
}

View File

@ -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);
}
}
}

View File

@ -3,12 +3,16 @@ package net.sourceforge.tuned.ui;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JDialog;
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) {
Timer timer = new Timer(delay, new ActionListener() {

18
test/AllTests.java Normal file
View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -158,11 +158,10 @@ public class PreferencesMapTest {
}
@Test(expected = IllegalArgumentException.class)
public void getWithObjectKey() throws Exception {
Map<String, String> map = PreferencesMap.map(strings, String.class);
map.get(new Object());
assertEquals(null, map.get(new Object()));
}