* lots of stuff for subtitle support

This commit is contained in:
Reinhard Pointner 2008-05-03 20:43:15 +00:00
parent b59e5dc714
commit 12d453eff4
21 changed files with 1390 additions and 39 deletions

View File

@ -22,9 +22,9 @@ public class Settings {
public static final String ROOT = NAME.toLowerCase(); public static final String ROOT = NAME.toLowerCase();
public static final String SELECTED_PANEL = "panel"; public static final String SELECTED_PANEL = "panel";
public static final String SEARCH_HISTORY = "history/search"; public static final String SEARCH_HISTORY = "search/history";
public static final String SUBTITLE_HISTORY = "history/subtitle"; public static final String SUBTITLE_HISTORY = "subtitle/history";
public static final String LANGUAGE_HISTORY = "history/language"; public static final String SUBTITLE_LANGUAGE = "subtitle/language";
private static Settings settings = new Settings(); private static Settings settings = new Settings();
@ -133,7 +133,7 @@ public class Settings {
for (Entry<String, String> entry : entries.entrySet()) { for (Entry<String, String> entry : entries.entrySet()) {
try { try {
map.put(entry.getKey(), new Integer(entry.getValue())); map.put(entry.getKey(), Integer.valueOf(entry.getValue()));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
} }
@ -154,6 +154,35 @@ public class Settings {
} }
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 void clear() { public void clear() {
try { try {
prefs.removeNode(); prefs.removeNode();

View File

@ -5,7 +5,10 @@ package net.sourceforge.filebot.resources;
import java.awt.Image; import java.awt.Image;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -15,49 +18,71 @@ import javax.swing.ImageIcon;
public class ResourceManager { public class ResourceManager {
private static HashMap<String, String> aliasMap = new HashMap<String, String>(); private ResourceManager() {
// hide constructor
}
private static final Map<String, String> aliasMap = new HashMap<String, String>();
static { static {
aliasMap.put("tab.loading", "tab.loading.gif"); aliasMap.put("tab.loading", "tab.loading.gif");
aliasMap.put("tab.history", "action.find.png"); aliasMap.put("tab.history", "action.find.png");
aliasMap.put("loading", "loading.gif"); aliasMap.put("loading", "loading.gif");
} }
private static final Map<String, Image> cache = Collections.synchronizedMap(new WeakHashMap<String, Image>());
public static Image getImage(String name) {
try {
return ImageIO.read(getResource(name));
} catch (IOException e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
}
return null;
}
public static ImageIcon getIcon(String name) { public static ImageIcon getIcon(String name) {
return new ImageIcon(getResource(name)); return new ImageIcon(getImage(name));
} }
public static ImageIcon getFlagIcon(String languageCode) { public static ImageIcon getFlagIcon(String languageCode) {
URL url = ResourceManager.class.getResource(String.format("flags/%s.png", languageCode)); if (languageCode == null)
languageCode = "default";
if (url == null) return new ImageIcon(getImage(String.format("flags/%s", languageCode.toLowerCase()), "flags/default"));
url = ResourceManager.class.getResource(String.format("flags/default.png", languageCode));
return new ImageIcon(url);
} }
public static ImageIcon getArchiveIcon(String type) { public static ImageIcon getArchiveIcon(String type) {
URL url = ResourceManager.class.getResource(String.format("archives/%s.png", type.toLowerCase())); if (type == null)
type = "default";
if (url == null) return new ImageIcon(getImage(String.format("archives/%s", type.toLowerCase()), "archives/default"));
url = ResourceManager.class.getResource(String.format("archives/default.png")); }
public static Image getImage(String name) {
Image image = cache.get(name);
return new ImageIcon(url); if (image == null) {
try {
// load image if not in cache
URL resource = getResource(name);
if (resource != null) {
image = ImageIO.read(resource);
cache.put(name, image);
}
} catch (IOException e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e);
}
}
return image;
}
private static Image getImage(String name, String def) {
Image image = getImage(name);
// image not found, use default
if (image == null)
image = getImage(def);
return image;
} }
@ -71,4 +96,5 @@ public class ResourceManager {
return ResourceManager.class.getResource(resource); return ResourceManager.class.getResource(resource);
} }
} }

View File

@ -13,10 +13,10 @@ import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
public class Checksum { public class Checksum {
private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public static final String STATE_PROPERTY = "STATE_PROPERTY"; public static final String STATE_PROPERTY = "DOWNLOAD_STATE";
public static final String PROGRESS_PROPERTY = "PROGRESS_PROPERTY"; public static final String PROGRESS_PROPERTY = "DOWNLOAD_PROGRESS";
private Long checksum = null; private Long checksum = null;
private State state = State.PENDING; private State state = State.PENDING;

View File

@ -0,0 +1,23 @@
package net.sourceforge.filebot.ui.panel.subtitle;
public enum ArchiveType {
ZIP,
RAR,
UNKNOWN;
public static ArchiveType forName(String name) {
if (name == null)
return UNKNOWN;
if (name.equalsIgnoreCase("zip"))
return ZIP;
if (name.equalsIgnoreCase("rar"))
return RAR;
return UNKNOWN;
}
}

View File

@ -0,0 +1,52 @@
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>, Object> {
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,67 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class LanguageResolver {
private static final LanguageResolver defaultInstance = new LanguageResolver();
public static LanguageResolver getDefault() {
return defaultInstance;
}
private final Map<String, Locale> localeMap = new HashMap<String, Locale>();
/**
* Get the locale for a language.
*
* @param languageName english name of the language
* @return the locale for this language or null if no locale for this language exists
*/
public synchronized Locale getLocale(String languageName) {
languageName = languageName.toLowerCase();
Locale locale = localeMap.get(languageName);
if (locale == null) {
locale = findLocale(languageName);
localeMap.put(languageName, locale);
}
return locale;
}
/**
* Get the ISO 639 language code for a language.
*
* @param languageName english name of the language
* @return lowercase ISO 639 language code
* @see Locale#getLanguage()
*/
public String getLanguageCode(String languageName) {
Locale locale = getLocale(languageName);
if (locale != null)
return locale.getLanguage();
return null;
}
private Locale findLocale(String languageName) {
for (Locale locale : Locale.getAvailableLocales()) {
if (locale.getDisplayLanguage(Locale.ENGLISH).toLowerCase().equals(languageName))
return locale;
}
return null;
}
}

View File

@ -0,0 +1,83 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.image.FilteredImageSource;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.SwingConstants;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.tuned.ui.ColorTintImageFilter;
import net.sourceforge.tuned.ui.IconViewCellRenderer;
public class SubtitleCellRenderer extends IconViewCellRenderer {
//TODO rename info to e.g. language
private final JLabel info1 = new JLabel();
private final JLabel info2 = new JLabel();
private Color infoForegroundDeselected = new Color(0x808080);
// TODO gscheid machn
private Icon icon;
public SubtitleCellRenderer() {
info1.setBorder(null);
info2.setBorder(null);
info1.setHorizontalTextPosition(SwingConstants.LEFT);
getContentPane().add(info1);
}
@Override
public void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
SubtitleDescriptor subtitle = (SubtitleDescriptor) value;
setText(subtitle.getName());
info1.setText(subtitle.getLanguageName());
info2.setText(subtitle.getAuthor());
icon = (ResourceManager.getFlagIcon(LanguageResolver.getDefault().getLocale(subtitle.getLanguageName()).getLanguage()));
info1.setIcon(icon);
ImageIcon icon = ResourceManager.getArchiveIcon(subtitle.getArchiveType());
if (isSelected) {
setIcon(new ImageIcon(createImage(new FilteredImageSource(icon.getImage().getSource(), new ColorTintImageFilter(list.getSelectionBackground(), 0.5f)))));
info1.setForeground(list.getSelectionForeground());
info2.setForeground(list.getSelectionForeground());
} else {
setIcon(icon);
info1.setForeground(infoForegroundDeselected);
info2.setForeground(infoForegroundDeselected);
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
//TODO gscheid machn
g.translate(36, 43);
// icon.paintIcon(this, g, 0, 0);
}
}

View File

@ -0,0 +1,93 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import javax.swing.Icon;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.tuned.DownloadTask;
public class SubtitlePackage {
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
private DownloadTask downloadTask = null;
private final SubtitleDescriptor subtitleDescriptor;
public SubtitlePackage(SubtitleDescriptor subtitleDescriptor) {
this.subtitleDescriptor = subtitleDescriptor;
}
public String getName() {
return subtitleDescriptor.getName();
}
public ArchiveType getArchiveType() {
return ArchiveType.forName(subtitleDescriptor.getArchiveType());
}
public Icon getArchiveIcon() {
return ResourceManager.getArchiveIcon(getArchiveType().toString());
}
public String getLanguageName() {
return subtitleDescriptor.getLanguageName();
}
public Icon getLanguageIcon() {
return ResourceManager.getFlagIcon(LanguageResolver.getDefault().getLanguageCode(getLanguageName()));
}
public synchronized void startDownload() {
if (downloadTask != null)
throw new IllegalStateException("Download has been started already");
downloadTask = subtitleDescriptor.createDownloadTask();
downloadTask.addPropertyChangeListener(new DownloadTaskPropertyChangeRelay());
downloadTask.execute();
}
public DownloadTask getDownloadTask() {
if (downloadTask == null)
throw new IllegalStateException("Download has not been started");
return downloadTask;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
private class DownloadTaskPropertyChangeRelay implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
propertyChangeSupport.firePropertyChange(evt.getPropertyName(), evt.getOldValue(), evt.getNewValue());
}
};
}

View File

@ -155,7 +155,7 @@ public class SubtitlePanel extends FileBotPanel {
private class SearchTaskListener extends SwingWorkerPropertyChangeAdapter { private class SearchTaskListener extends SwingWorkerPropertyChangeAdapter {
private SubtitleListPanel subtitleSearchResultPanel; private SubtitleViewPanel subtitleSearchResultPanel;
private FileBotTabComponent tabComponent; private FileBotTabComponent tabComponent;
@ -163,7 +163,7 @@ public class SubtitlePanel extends FileBotPanel {
public void started(PropertyChangeEvent evt) { public void started(PropertyChangeEvent evt) {
SearchTask task = (SearchTask) evt.getSource(); SearchTask task = (SearchTask) evt.getSource();
subtitleSearchResultPanel = new SubtitleListPanel(); subtitleSearchResultPanel = new SubtitleViewPanel();
tabComponent = new FileBotTabComponent(task.query, ResourceManager.getIcon("tab.loading")); tabComponent = new FileBotTabComponent(task.query, ResourceManager.getIcon("tab.loading"));
tabbedPane.addTab(task.query, subtitleSearchResultPanel); tabbedPane.addTab(task.query, subtitleSearchResultPanel);
@ -246,11 +246,11 @@ public class SubtitlePanel extends FileBotPanel {
private class FetchSubtitleListTaskListener extends SwingWorkerPropertyChangeAdapter { private class FetchSubtitleListTaskListener extends SwingWorkerPropertyChangeAdapter {
private final SubtitleListPanel subtitleSearchResultPanel; private final SubtitleViewPanel subtitleSearchResultPanel;
private final FileBotTabComponent tabComponent; private final FileBotTabComponent tabComponent;
public FetchSubtitleListTaskListener(SubtitleListPanel subtitleSearchResultPanel, FileBotTabComponent tabComponent) { public FetchSubtitleListTaskListener(SubtitleViewPanel subtitleSearchResultPanel, FileBotTabComponent tabComponent) {
this.subtitleSearchResultPanel = subtitleSearchResultPanel; this.subtitleSearchResultPanel = subtitleSearchResultPanel;
this.tabComponent = tabComponent; this.tabComponent = tabComponent;
} }

View File

@ -0,0 +1,188 @@
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.JPanel;
import javax.swing.JToggleButton;
import javax.swing.JToolTip;
import javax.swing.ListModel;
import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.resources.ResourceManager;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.tuned.ui.IconViewPanel;
import net.sourceforge.tuned.ui.SimpleListModel;
public class SubtitleViewPanel extends IconViewPanel {
private ListModel unfilteredModel = new SimpleListModel();
private JPanel languageFilterPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 1));
private Map<String, Boolean> languageFilterSelection = new TreeMap<String, Boolean>(String.CASE_INSENSITIVE_ORDER);
public SubtitleViewPanel() {
setCellRenderer(new SubtitleCellRenderer());
languageFilterPanel.setOpaque(false);
getHeaderPanel().add(languageFilterPanel, BorderLayout.EAST);
languageFilterSelection.putAll(Settings.getSettings().getBooleanMap(Settings.SUBTITLE_LANGUAGE));
}
@Override
public void setModel(ListModel model) {
unfilteredModel = model;
updateLanguageFilterButtonPanel();
updateFilteredModel();
}
@Override
public ListModel getModel() {
return unfilteredModel;
}
private void updateLanguageFilterButtonPanel() {
SortedSet<String> languages = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER);
for (int i = 0; i < unfilteredModel.getSize(); i++) {
SubtitleDescriptor subtitle = (SubtitleDescriptor) 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);
}
}
private void updateFilteredModel() {
SimpleListModel model = new SimpleListModel();
for (int i = 0; i < unfilteredModel.getSize(); i++) {
SubtitleDescriptor subtitle = (SubtitleDescriptor) unfilteredModel.getElementAt(i);
if (isLanguageSelected(subtitle.getLanguageName())) {
model.add(subtitle);
}
}
super.setModel(model);
}
public boolean isLanguageSelected(String language) {
return !languageFilterSelection.containsKey(language) || languageFilterSelection.get(language);
}
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);
languageFilterSelection.put(language, selected);
Settings.getSettings().putBooleanMapEntry(Settings.SUBTITLE_LANGUAGE, 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;
AlphaComposite composite = AlphaComposite.SrcOver.derive(isSelected() ? 1.0f : 0.2f);
g2d.setComposite(composite);
super.paintComponent(g2d);
}
}
@Override
public JToolTip createToolTip() {
System.out.println("SubtitleViewPanel.createToolTip()");
return super.createToolTip();
}
}

View File

@ -0,0 +1,129 @@
package net.sourceforge.filebot.ui.panel.subtitle;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sourceforge.filebot.FileBotUtil;
public class Unrar {
private static final Command command = getCommand();
private static final int timeout = 5000;
private static final int sleepInterval = 50;
public static void extractFiles(File archive, File destination) throws Exception {
if (command == null) {
throw new IllegalStateException("Unrar could not be initialized");
}
Process process = command.execute(archive, destination);
int counter = 0;
while (isRunning(process)) {
Thread.sleep(sleepInterval);
counter += sleepInterval;
if (counter > timeout) {
process.destroy();
throw new TimeoutException(String.format("%s timed out", command.getName()));
}
}
if (process.exitValue() != 0) {
throw new Exception(String.format("%s returned with exit value %d", command.getName(), process.exitValue()));
}
}
private static boolean isRunning(Process process) {
try {
// will throw exception if process is still running
process.exitValue();
// process has terminated
return false;
} catch (IllegalThreadStateException e) {
// process is still running
return true;
}
}
private static Command getCommand() {
try {
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
File programFiles = new File(System.getenv("PROGRAMFILES"));
for (File folder : programFiles.listFiles(FileBotUtil.FOLDERS_ONLY)) {
String name = folder.getName().toLowerCase();
if (name.contains("rar") || name.contains("zip")) {
for (File file : folder.listFiles(FileBotUtil.FILES_ONLY)) {
String filename = file.getName();
if (filename.equalsIgnoreCase("unrar.exe") || filename.equalsIgnoreCase("7z.exe")) {
return new Command(filename, file);
}
}
}
}
throw new FileNotFoundException("External program not found");
} else {
String command = "unrar";
// will throw an exception if command cannot be executed
Runtime.getRuntime().exec(command);
return new Command(command);
}
} catch (Exception e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Cannot initialize unrar facility: " + e.getMessage());
}
return null;
}
private static class Command {
private final String name;
private final String executable;
public Command(String command) {
this.name = command;
this.executable = command;
}
public Command(String name, File executable) {
this.name = name;
this.executable = executable.getAbsolutePath();
}
public Process execute(File archive, File workingDirectory) throws IOException {
ProcessBuilder builder = new ProcessBuilder(executable, "x", "-y", archive.getAbsolutePath());
builder.directory(workingDirectory);
return builder.start();
}
public String getName() {
return name;
}
}
}

View File

@ -73,11 +73,18 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor {
} }
@Override
public String getLanguageName() { public String getLanguageName() {
return getProperty(Properties.LanguageName); return getProperty(Properties.LanguageName);
} }
@Override
public String getAuthor() {
return getProperty(Properties.UserNickName);
}
public long getSize() { public long getSize() {
return Long.parseLong(getProperty(Properties.SubSize)); return Long.parseLong(getProperty(Properties.SubSize));
} }

View File

@ -84,10 +84,11 @@ public class SubsceneSubtitleClient extends SubtitleClient {
String href = XPathUtil.selectString("@href", linkNode); String href = XPathUtil.selectString("@href", linkNode);
String name = XPathUtil.selectString("./SPAN[2]", linkNode);
String lang = XPathUtil.selectString("./SPAN[1]", linkNode); String lang = XPathUtil.selectString("./SPAN[1]", linkNode);
String name = XPathUtil.selectString("./SPAN[2]", linkNode);
int numberOfCDs = Integer.parseInt(XPathUtil.selectString("./TD[2]", node)); int numberOfCDs = Integer.parseInt(XPathUtil.selectString("./TD[2]", node));
boolean hearingImpaired = XPathUtil.selectFirstNode("./TD[3]/*[@id='imgEar']", node) != null;
String author = XPathUtil.selectString("./TD[4]", node); String author = XPathUtil.selectString("./TD[4]", node);
URL downloadUrl = new URL("http", host, downloadPath); URL downloadUrl = new URL("http", host, downloadPath);
@ -95,7 +96,7 @@ public class SubsceneSubtitleClient extends SubtitleClient {
Map<String, String> downloadParameters = parseParameters(href); Map<String, String> downloadParameters = parseParameters(href);
downloadParameters.put("__VIEWSTATE", viewstate); downloadParameters.put("__VIEWSTATE", viewstate);
list.add(new SubsceneSubtitleDescriptor(name, lang, numberOfCDs, author, downloadUrl, downloadParameters)); list.add(new SubsceneSubtitleDescriptor(name, lang, numberOfCDs, author, hearingImpaired, downloadUrl, downloadParameters));
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Cannot parse subtitle node", e); Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Cannot parse subtitle node", e);
} }

View File

@ -14,22 +14,25 @@ public class SubsceneSubtitleDescriptor implements SubtitleDescriptor {
private final String language; private final String language;
private final int numberOfCDs; private final int numberOfCDs;
private final String author; private final String author;
private final boolean hearingImpaired;
private final Map<String, String> downloadParameters; private final Map<String, String> downloadParameters;
private final URL downloadUrl; private final URL downloadUrl;
public SubsceneSubtitleDescriptor(String title, String language, int numberOfCDs, String author, URL downloadUrl, Map<String, String> downloadParameters) { public SubsceneSubtitleDescriptor(String title, String language, int numberOfCDs, String author, boolean hearingImpaired, URL downloadUrl, Map<String, String> downloadParameters) {
this.title = title; this.title = title;
this.language = language; this.language = language;
this.numberOfCDs = numberOfCDs; this.numberOfCDs = numberOfCDs;
this.author = author; this.author = author;
this.hearingImpaired = hearingImpaired;
this.downloadUrl = downloadUrl; this.downloadUrl = downloadUrl;
this.downloadParameters = downloadParameters; this.downloadParameters = downloadParameters;
} }
@Override
public String getName() { public String getName() {
return title; return title;
} }
@ -50,11 +53,18 @@ public class SubsceneSubtitleDescriptor implements SubtitleDescriptor {
} }
public boolean getHearingImpaired() {
return hearingImpaired;
}
@Override
public DownloadTask createDownloadTask() { public DownloadTask createDownloadTask() {
return new DownloadTask(downloadUrl, downloadParameters); return new DownloadTask(downloadUrl, downloadParameters);
} }
@Override
public String getArchiveType() { public String getArchiveType() {
return downloadParameters.get("typeId"); return downloadParameters.get("typeId");
} }

View File

@ -13,6 +13,9 @@ public interface SubtitleDescriptor {
public String getLanguageName(); public String getLanguageName();
public String getAuthor();
public String getArchiveType(); public String getArchiveType();

View File

@ -23,7 +23,7 @@ import javax.swing.SwingWorker;
public class DownloadTask extends SwingWorker<ByteBuffer, Object> { public class DownloadTask extends SwingWorker<ByteBuffer, Object> {
public static final String DOWNLOAD_STATE = "download state"; public static final String DOWNLOAD_STATE = "download state";
public static final String BYTES_READ = "bytes read"; public static final String DOWNLOAD_PROGRESS = "download progress";
public static enum DownloadState { public static enum DownloadState {
@ -84,11 +84,11 @@ public class DownloadTask extends SwingWorker<ByteBuffer, Object> {
int len = 0; int len = 0;
try { try {
while ((len = in.read(buffer)) > 0) { while (((len = in.read(buffer)) > 0) && !isCancelled()) {
out.write(buffer, 0, len); out.write(buffer, 0, len);
bytesRead += len; bytesRead += len;
getPropertyChangeSupport().firePropertyChange(BYTES_READ, null, bytesRead); getPropertyChangeSupport().firePropertyChange(DOWNLOAD_PROGRESS, null, bytesRead);
} }
} catch (IOException e) { } catch (IOException e) {
// IOException (Premature EOF) is always thrown when the size of // IOException (Premature EOF) is always thrown when the size of

View File

@ -0,0 +1,34 @@
package net.sourceforge.tuned.ui;
import java.awt.Color;
import java.awt.image.RGBImageFilter;
public class ColorTintImageFilter extends RGBImageFilter {
private Color color;
private float intensity;
public ColorTintImageFilter(Color color, float intensity) {
this.color = color;
this.intensity = intensity;
canFilterIndexColorModel = true;
}
@Override
public int filterRGB(int x, int y, int rgb) {
Color c = new Color(rgb, true);
int red = (int) ((c.getRed() * (1 - intensity)) + color.getRed() * intensity);
int green = (int) ((c.getGreen() * (1 - intensity)) + color.getGreen() * intensity);
int blue = (int) ((c.getBlue() * (1 - intensity)) + color.getBlue() * intensity);
return new Color(red, green, blue, c.getAlpha()).getRGB();
}
}

View File

@ -0,0 +1,245 @@
package net.sourceforge.tuned.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.RectangularShape;
import java.awt.geom.RoundRectangle2D;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;
public class IconViewCellRenderer extends AbstractFancyListCellRenderer {
private final JLabel iconLabel = new JLabel();
private final JLabel titleLabel = new JLabel();
private final ContentPane contentPane = new ContentPane();
public IconViewCellRenderer() {
super(new Insets(3, 3, 3, 3), new Insets(3, 3, 3, 3));
setHighlightingEnabled(false);
contentPane.add(titleLabel);
contentPane.setBorder(new EmptyBorder(4, 4, 4, 4));
iconLabel.setHorizontalAlignment(SwingConstants.CENTER);
iconLabel.setVerticalAlignment(SwingConstants.CENTER);
JPanel contentPanel = new JPanel(new BorderLayout());
contentPanel.setOpaque(false);
Box contentPaneContainer = new Box(BoxLayout.X_AXIS);
contentPaneContainer.add(contentPane);
contentPanel.add(contentPaneContainer, BorderLayout.WEST);
add(iconLabel, BorderLayout.WEST);
add(contentPanel, BorderLayout.CENTER);
}
@Override
protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
setGradientPainted(false);
setText(value.toString());
contentPane.setGradientPainted(isSelected);
}
@Override
public void setForeground(Color fg) {
super.setForeground(fg);
// label is null while in super constructor
if (titleLabel != null) {
titleLabel.setForeground(fg);
}
}
@Override
public void setBackground(Color bg) {
super.setBackground(bg);
// label is null while in super constructor
if (titleLabel != null) {
titleLabel.setBackground(bg);
}
}
public void setIcon(Icon icon) {
iconLabel.setIcon(icon);
}
public void setText(String title) {
titleLabel.setText(title);
}
protected JComponent getContentPane() {
return contentPane;
}
private class ContentPane extends Box {
private boolean gradientPainted;
public ContentPane() {
super(BoxLayout.Y_AXIS);
setOpaque(false);
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
RectangularShape shape = new RoundRectangle2D.Float(0, 0, getWidth(), getHeight(), 16, 16);
if (gradientPainted) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(getGradientStyle().getGradientPaint(shape, getGradientBeginColor(), getGradientEndColor()));
g2d.fill(shape);
}
super.paintComponent(g);
}
public void setGradientPainted(boolean gradientPainted) {
this.gradientPainted = gradientPainted;
}
}
/**
* Overridden for performance reasons.
*/
@Override
public void validate() {
// validate children, yet avoid flickering of the mouse cursor
validateTree();
}
/**
* Overridden for performance reasons.
*/
@Override
public void repaint() {
}
/**
* Overridden for performance reasons.
*/
@Override
public void repaint(long tm, int x, int y, int width, int height) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void repaint(Rectangle r) {
}
/**
* Overridden for performance reasons.
*/
@Override
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, byte oldValue, byte newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, char oldValue, char newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, short oldValue, short newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, int oldValue, int newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, long oldValue, long newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, float oldValue, float newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, double oldValue, double newValue) {
}
/**
* Overridden for performance reasons.
*/
@Override
public void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
}
}

View File

@ -0,0 +1,138 @@
package net.sourceforge.tuned.ui;
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Paint;
import java.awt.geom.Rectangle2D;
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;
public class IconViewPanel extends JPanel {
private final JList list = new JList(new SimpleListModel());
private final JLabel title = new JLabel();
private final JPanel headerPanel = new JPanel(new BorderLayout());
public IconViewPanel() {
super(new BorderLayout());
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(-1);
title.setFont(title.getFont().deriveFont(Font.BOLD));
headerPanel.setOpaque(false);
headerPanel.add(title, BorderLayout.WEST);
setBackground(list.getBackground());
headerPanel.setBorder(new CompoundBorder(new GradientLineBorder(290), new EmptyBorder(2, 10, 1, 5)));
list.setBorder(new EmptyBorder(5, 5, 5, 5));
JScrollPane listScrollPane = new JScrollPane(list);
listScrollPane.setBorder(null);
add(headerPanel, BorderLayout.NORTH);
add(listScrollPane, BorderLayout.CENTER);
}
public JPanel getHeaderPanel() {
return headerPanel;
}
public void setTitle(String text) {
title.setText(text);
}
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
}
public void collapse() {
// TODO collapse
}
private class GradientLineBorder implements Border {
private int gradientLineWidth;
public GradientLineBorder(int width) {
this.gradientLineWidth = width;
}
@Override
public Insets getBorderInsets(Component c) {
return new Insets(0, 0, 1, 0);
}
@Override
public boolean isBorderOpaque() {
return false;
}
@Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2d = (Graphics2D) g;
Color beginColor = list.getSelectionBackground().brighter();
Color endColor = list.getBackground();
Rectangle2D shape = new Rectangle2D.Float(x, y + height - 1, gradientLineWidth, 1);
Paint paint = GradientStyle.LEFT_TO_RIGHT.getGradientPaint(shape, beginColor, endColor);
g2d.setPaint(paint);
g2d.setComposite(AlphaComposite.SrcOver.derive(0.9f));
g2d.fill(shape);
}
}
}

View File

@ -0,0 +1,217 @@
package net.sourceforge.tuned.ui;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Calendar;
import javax.swing.BoundedRangeModel;
import javax.swing.JComponent;
public class ProgressIndicator extends JComponent {
private BoundedRangeModel model = null;
private boolean indeterminate = false;
private float indeterminateRadius = 4.0f;
private int indeterminateShapeCount = 1;
private float progressStrokeWidth = 4.5f;
private float remainingStrokeWidth = 2.5f;
private Stroke progressStroke = new BasicStroke(progressStrokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
private Stroke remainingStroke = new BasicStroke(remainingStrokeWidth, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND);
private Color progressColor = Color.orange;
private Color remainingColor = new Color(0f, 0f, 0f, 0.25f);
private Color textColor = new Color(42, 42, 42);
private boolean paintText = true;
private boolean paintBackground = false;
private final Rectangle2D frame = new Rectangle2D.Double();
private final Arc2D arc = new Arc2D.Double();
private final Ellipse2D circle = new Ellipse2D.Double();
public ProgressIndicator() {
this(null);
}
public ProgressIndicator(BoundedRangeModel model) {
this.model = model;
indeterminate = (model == null);
setFont(new Font(Font.DIALOG, Font.PLAIN, 8));
}
public double getProgress() {
if (model == null)
return 0;
double total = model.getMaximum() - model.getMinimum();
double current = model.getValue() - model.getMinimum();
return current / total;
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
if (paintBackground) {
frame.setFrame(0, 0, getWidth(), getHeight());
g2d.setPaint(getBackground());
circle.setFrame(frame);
g2d.fill(circle);
}
frame.setFrame(progressStrokeWidth, progressStrokeWidth, getWidth() - progressStrokeWidth * 2 - 1, getHeight() - progressStrokeWidth * 2 - 1);
if (!indeterminate) {
paintProgress(g2d);
} else {
paintIndeterminate(g2d);
}
}
protected void paintProgress(Graphics2D g2d) {
double progress = getProgress();
// remaining circle
circle.setFrame(frame);
g2d.setStroke(remainingStroke);
g2d.setPaint(remainingColor);
g2d.draw(circle);
// progress circle
arc.setArc(frame, 90, progress * 360 * -1, Arc2D.OPEN);
g2d.setStroke(progressStroke);
g2d.setPaint(progressColor);
g2d.draw(arc);
if (paintText) {
// text
g2d.setFont(getFont());
g2d.setPaint(textColor);
String text = String.format("%d%%", (int) (100 * progress));
Rectangle2D textBounds = g2d.getFontMetrics().getStringBounds(text, g2d);
g2d.drawString(text, (float) (frame.getCenterX() - textBounds.getX() - (textBounds.getWidth() / 2f) + 0.5f), (float) (frame.getCenterY() - textBounds.getY() - (textBounds.getHeight() / 2)));
}
}
protected void paintIndeterminate(Graphics2D g2d) {
circle.setFrame(frame);
g2d.setStroke(remainingStroke);
g2d.setPaint(remainingColor);
g2d.draw(circle);
Point2D center = new Point2D.Double(frame.getCenterX(), frame.getMinY());
circle.setFrameFromCenter(center, new Point2D.Double(center.getX() + indeterminateRadius, center.getY() + indeterminateRadius));
g2d.setStroke(progressStroke);
g2d.setPaint(progressColor);
Calendar now = Calendar.getInstance();
double theta = getTheta(now.get(Calendar.MILLISECOND), now.getMaximum(Calendar.MILLISECOND));
g2d.rotate(theta, frame.getCenterX(), frame.getCenterY());
theta = getTheta(1, indeterminateShapeCount);
for (int i = 0; i < indeterminateShapeCount; i++) {
g2d.rotate(theta, frame.getCenterX(), frame.getCenterY());
g2d.fill(circle);
}
}
private double getTheta(int value, int max) {
return ((double) value / max) * 2 * Math.PI;
}
public BoundedRangeModel getModel() {
return model;
}
public void setModel(BoundedRangeModel model) {
this.model = model;
}
public boolean isIndeterminate() {
return indeterminate;
}
public void setIndeterminate(boolean indeterminate) {
this.indeterminate = indeterminate;
}
public void setIndeterminateRadius(float indeterminateRadius) {
this.indeterminateRadius = indeterminateRadius;
}
public void setIndeterminateShapeCount(int indeterminateShapeCount) {
this.indeterminateShapeCount = indeterminateShapeCount;
}
public void setProgressColor(Color progressColor) {
this.progressColor = progressColor;
}
public void setRemainingColor(Color remainingColor) {
this.remainingColor = remainingColor;
}
public void setTextColor(Color textColor) {
this.textColor = textColor;
}
public void setPaintBackground(boolean paintBackground) {
this.paintBackground = paintBackground;
}
public void setPaintText(boolean paintString) {
this.paintText = paintString;
}
}

View File

@ -2,6 +2,7 @@
package net.sourceforge.tuned.ui; package net.sourceforge.tuned.ui;
import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Point; import java.awt.Point;
import java.awt.Window; import java.awt.Window;
@ -17,6 +18,11 @@ import javax.swing.Timer;
public class TunedUtil { public class TunedUtil {
private TunedUtil() {
// hide constructor
}
public static void registerActionForKeystroke(JComponent component, KeyStroke keystroke, Action action) { public static void registerActionForKeystroke(JComponent component, KeyStroke keystroke, Action action) {
Integer key = action.hashCode(); Integer key = action.hashCode();
component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keystroke, key); component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keystroke, key);