diff --git a/source/net/sourceforge/filebot/Settings.java b/source/net/sourceforge/filebot/Settings.java index 07faf743..798b4f6f 100644 --- a/source/net/sourceforge/filebot/Settings.java +++ b/source/net/sourceforge/filebot/Settings.java @@ -22,9 +22,9 @@ public class Settings { public static final String ROOT = NAME.toLowerCase(); public static final String SELECTED_PANEL = "panel"; - public static final String SEARCH_HISTORY = "history/search"; - public static final String SUBTITLE_HISTORY = "history/subtitle"; - public static final String LANGUAGE_HISTORY = "history/language"; + public static final String SEARCH_HISTORY = "search/history"; + public static final String SUBTITLE_HISTORY = "subtitle/history"; + public static final String SUBTITLE_LANGUAGE = "subtitle/language"; private static Settings settings = new Settings(); @@ -133,7 +133,7 @@ public class Settings { for (Entry entry : entries.entrySet()) { try { - map.put(entry.getKey(), new Integer(entry.getValue())); + map.put(entry.getKey(), Integer.valueOf(entry.getValue())); } catch (NumberFormatException e) { Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); } @@ -154,6 +154,35 @@ public class Settings { } + public Map getBooleanMap(String key) { + Map entries = getStringMap(key); + + Map map = new HashMap(entries.size()); + + for (Entry entry : entries.entrySet()) { + map.put(entry.getKey(), Boolean.valueOf(entry.getValue())); + } + + return map; + } + + + public void putBooleanMap(String key, Map map) { + Map entries = new HashMap(); + + for (Entry 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() { try { prefs.removeNode(); diff --git a/source/net/sourceforge/filebot/resources/ResourceManager.java b/source/net/sourceforge/filebot/resources/ResourceManager.java index d17d6ae3..06f02233 100644 --- a/source/net/sourceforge/filebot/resources/ResourceManager.java +++ b/source/net/sourceforge/filebot/resources/ResourceManager.java @@ -5,7 +5,10 @@ package net.sourceforge.filebot.resources; import java.awt.Image; import java.io.IOException; import java.net.URL; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -15,49 +18,71 @@ import javax.swing.ImageIcon; public class ResourceManager { - private static HashMap aliasMap = new HashMap(); + private ResourceManager() { + // hide constructor + } + + private static final Map aliasMap = new HashMap(); static { aliasMap.put("tab.loading", "tab.loading.gif"); aliasMap.put("tab.history", "action.find.png"); - aliasMap.put("loading", "loading.gif"); } + private static final Map cache = Collections.synchronizedMap(new WeakHashMap()); - 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) { - return new ImageIcon(getResource(name)); + return new ImageIcon(getImage(name)); } 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) - url = ResourceManager.class.getResource(String.format("flags/default.png", languageCode)); - - return new ImageIcon(url); + return new ImageIcon(getImage(String.format("flags/%s", languageCode.toLowerCase()), "flags/default")); } 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) - url = ResourceManager.class.getResource(String.format("archives/default.png")); + return new ImageIcon(getImage(String.format("archives/%s", type.toLowerCase()), "archives/default")); + } + + + 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); } + } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/Checksum.java b/source/net/sourceforge/filebot/ui/panel/sfv/Checksum.java index cec63685..1b3b2a04 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/Checksum.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/Checksum.java @@ -13,10 +13,10 @@ import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter; 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 PROGRESS_PROPERTY = "PROGRESS_PROPERTY"; + public static final String STATE_PROPERTY = "DOWNLOAD_STATE"; + public static final String PROGRESS_PROPERTY = "DOWNLOAD_PROGRESS"; private Long checksum = null; private State state = State.PENDING; diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/ArchiveType.java b/source/net/sourceforge/filebot/ui/panel/subtitle/ArchiveType.java new file mode 100644 index 00000000..635e4f23 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/ArchiveType.java @@ -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; + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/FetchSubtitleListTask.java b/source/net/sourceforge/filebot/ui/panel/subtitle/FetchSubtitleListTask.java new file mode 100644 index 00000000..d3ac34ed --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/FetchSubtitleListTask.java @@ -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, 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 doInBackground() throws Exception { + long start = System.currentTimeMillis(); + + List 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; + } +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageResolver.java b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageResolver.java new file mode 100644 index 00000000..4acc6aa1 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageResolver.java @@ -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 localeMap = new HashMap(); + + + /** + * 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; + } +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleCellRenderer.java new file mode 100644 index 00000000..0b46a0ac --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleCellRenderer.java @@ -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); + + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java new file mode 100644 index 00000000..cefb28a2 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java @@ -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()); + } + + }; + +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java index 9aa9e7a4..0484c500 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java @@ -155,7 +155,7 @@ public class SubtitlePanel extends FileBotPanel { private class SearchTaskListener extends SwingWorkerPropertyChangeAdapter { - private SubtitleListPanel subtitleSearchResultPanel; + private SubtitleViewPanel subtitleSearchResultPanel; private FileBotTabComponent tabComponent; @@ -163,7 +163,7 @@ public class SubtitlePanel extends FileBotPanel { public void started(PropertyChangeEvent evt) { SearchTask task = (SearchTask) evt.getSource(); - subtitleSearchResultPanel = new SubtitleListPanel(); + subtitleSearchResultPanel = new SubtitleViewPanel(); tabComponent = new FileBotTabComponent(task.query, ResourceManager.getIcon("tab.loading")); tabbedPane.addTab(task.query, subtitleSearchResultPanel); @@ -246,11 +246,11 @@ public class SubtitlePanel extends FileBotPanel { private class FetchSubtitleListTaskListener extends SwingWorkerPropertyChangeAdapter { - private final SubtitleListPanel subtitleSearchResultPanel; + private final SubtitleViewPanel subtitleSearchResultPanel; private final FileBotTabComponent tabComponent; - public FetchSubtitleListTaskListener(SubtitleListPanel subtitleSearchResultPanel, FileBotTabComponent tabComponent) { + public FetchSubtitleListTaskListener(SubtitleViewPanel subtitleSearchResultPanel, FileBotTabComponent tabComponent) { this.subtitleSearchResultPanel = subtitleSearchResultPanel; this.tabComponent = tabComponent; } diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleViewPanel.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleViewPanel.java new file mode 100644 index 00000000..f243e729 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleViewPanel.java @@ -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 languageFilterSelection = new TreeMap(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 languages = new TreeSet(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(); + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/Unrar.java b/source/net/sourceforge/filebot/ui/panel/subtitle/Unrar.java new file mode 100644 index 00000000..d139e8d7 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/Unrar.java @@ -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; + } + + } +} diff --git a/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleDescriptor.java b/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleDescriptor.java index d1cd823f..ff7b48cd 100644 --- a/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleDescriptor.java +++ b/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleDescriptor.java @@ -73,11 +73,18 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor { } + @Override public String getLanguageName() { return getProperty(Properties.LanguageName); } + @Override + public String getAuthor() { + return getProperty(Properties.UserNickName); + } + + public long getSize() { return Long.parseLong(getProperty(Properties.SubSize)); } diff --git a/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java b/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java index 7fe3f291..c6b2802f 100644 --- a/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java +++ b/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java @@ -84,10 +84,11 @@ public class SubsceneSubtitleClient extends SubtitleClient { String href = XPathUtil.selectString("@href", linkNode); - String name = XPathUtil.selectString("./SPAN[2]", linkNode); String lang = XPathUtil.selectString("./SPAN[1]", linkNode); + String name = XPathUtil.selectString("./SPAN[2]", linkNode); 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); URL downloadUrl = new URL("http", host, downloadPath); @@ -95,7 +96,7 @@ public class SubsceneSubtitleClient extends SubtitleClient { Map downloadParameters = parseParameters(href); 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) { Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Cannot parse subtitle node", e); } diff --git a/source/net/sourceforge/filebot/web/SubsceneSubtitleDescriptor.java b/source/net/sourceforge/filebot/web/SubsceneSubtitleDescriptor.java index dbca74a8..a3e7f2ee 100644 --- a/source/net/sourceforge/filebot/web/SubsceneSubtitleDescriptor.java +++ b/source/net/sourceforge/filebot/web/SubsceneSubtitleDescriptor.java @@ -14,22 +14,25 @@ public class SubsceneSubtitleDescriptor implements SubtitleDescriptor { private final String language; private final int numberOfCDs; private final String author; + private final boolean hearingImpaired; private final Map downloadParameters; private final URL downloadUrl; - public SubsceneSubtitleDescriptor(String title, String language, int numberOfCDs, String author, URL downloadUrl, Map downloadParameters) { + public SubsceneSubtitleDescriptor(String title, String language, int numberOfCDs, String author, boolean hearingImpaired, URL downloadUrl, Map downloadParameters) { this.title = title; this.language = language; this.numberOfCDs = numberOfCDs; this.author = author; + this.hearingImpaired = hearingImpaired; this.downloadUrl = downloadUrl; this.downloadParameters = downloadParameters; } + @Override public String getName() { return title; } @@ -50,11 +53,18 @@ public class SubsceneSubtitleDescriptor implements SubtitleDescriptor { } + public boolean getHearingImpaired() { + return hearingImpaired; + } + + + @Override public DownloadTask createDownloadTask() { return new DownloadTask(downloadUrl, downloadParameters); } + @Override public String getArchiveType() { return downloadParameters.get("typeId"); } diff --git a/source/net/sourceforge/filebot/web/SubtitleDescriptor.java b/source/net/sourceforge/filebot/web/SubtitleDescriptor.java index f7ae5e01..a53bd477 100644 --- a/source/net/sourceforge/filebot/web/SubtitleDescriptor.java +++ b/source/net/sourceforge/filebot/web/SubtitleDescriptor.java @@ -13,6 +13,9 @@ public interface SubtitleDescriptor { public String getLanguageName(); + public String getAuthor(); + + public String getArchiveType(); diff --git a/source/net/sourceforge/tuned/DownloadTask.java b/source/net/sourceforge/tuned/DownloadTask.java index 7e7eda18..8d4b9df3 100644 --- a/source/net/sourceforge/tuned/DownloadTask.java +++ b/source/net/sourceforge/tuned/DownloadTask.java @@ -23,7 +23,7 @@ import javax.swing.SwingWorker; public class DownloadTask extends SwingWorker { 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 { @@ -84,11 +84,11 @@ public class DownloadTask extends SwingWorker { int len = 0; try { - while ((len = in.read(buffer)) > 0) { + while (((len = in.read(buffer)) > 0) && !isCancelled()) { out.write(buffer, 0, len); bytesRead += len; - getPropertyChangeSupport().firePropertyChange(BYTES_READ, null, bytesRead); + getPropertyChangeSupport().firePropertyChange(DOWNLOAD_PROGRESS, null, bytesRead); } } catch (IOException e) { // IOException (Premature EOF) is always thrown when the size of diff --git a/source/net/sourceforge/tuned/ui/ColorTintImageFilter.java b/source/net/sourceforge/tuned/ui/ColorTintImageFilter.java new file mode 100644 index 00000000..91684985 --- /dev/null +++ b/source/net/sourceforge/tuned/ui/ColorTintImageFilter.java @@ -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(); + } + +} diff --git a/source/net/sourceforge/tuned/ui/IconViewCellRenderer.java b/source/net/sourceforge/tuned/ui/IconViewCellRenderer.java new file mode 100644 index 00000000..f1c1cbc8 --- /dev/null +++ b/source/net/sourceforge/tuned/ui/IconViewCellRenderer.java @@ -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) { + } + +} diff --git a/source/net/sourceforge/tuned/ui/IconViewPanel.java b/source/net/sourceforge/tuned/ui/IconViewPanel.java new file mode 100644 index 00000000..29bc3ce3 --- /dev/null +++ b/source/net/sourceforge/tuned/ui/IconViewPanel.java @@ -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); + } + } + +} diff --git a/source/net/sourceforge/tuned/ui/ProgressIndicator.java b/source/net/sourceforge/tuned/ui/ProgressIndicator.java new file mode 100644 index 00000000..fb327c9d --- /dev/null +++ b/source/net/sourceforge/tuned/ui/ProgressIndicator.java @@ -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; + } + +} diff --git a/source/net/sourceforge/tuned/ui/TunedUtil.java b/source/net/sourceforge/tuned/ui/TunedUtil.java index f82e90a3..271a1063 100644 --- a/source/net/sourceforge/tuned/ui/TunedUtil.java +++ b/source/net/sourceforge/tuned/ui/TunedUtil.java @@ -2,6 +2,7 @@ package net.sourceforge.tuned.ui; +import java.awt.Color; import java.awt.Dimension; import java.awt.Point; import java.awt.Window; @@ -17,6 +18,11 @@ import javax.swing.Timer; public class TunedUtil { + private TunedUtil() { + // hide constructor + } + + public static void registerActionForKeystroke(JComponent component, KeyStroke keystroke, Action action) { Integer key = action.hashCode(); component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keystroke, key);