diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/DashedSeparator.java b/source/net/sourceforge/filebot/ui/panel/subtitle/DashedSeparator.java new file mode 100644 index 00000000..30bf7a23 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/DashedSeparator.java @@ -0,0 +1,64 @@ + +package net.sourceforge.filebot.ui.panel.subtitle; + + +import static java.awt.BasicStroke.*; +import static java.awt.RenderingHints.*; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; + +import javax.swing.border.Border; + + +class DashedSeparator implements Border { + + private final int height; + private final int dash; + + private final Color foreground; + private final Color background; + + + public DashedSeparator(int height, int dash, Color foreground, Color background) { + this.height = height; + this.dash = dash; + this.foreground = foreground; + this.background = background; + } + + + @Override + public Insets getBorderInsets(Component c) { + return new Insets(0, 0, height, 0); + } + + + @Override + public boolean isBorderOpaque() { + return true; + } + + + @Override + public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) { + Graphics2D g2d = (Graphics2D) g.create(x, h - this.height, w, h); + + // fill background + g2d.setPaint(background); + g2d.fillRect(0, 0, w, h); + + // draw dashed line + g2d.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); + g2d.setColor(foreground); + g2d.setStroke(new BasicStroke(1, CAP_ROUND, JOIN_ROUND, 1, new float[] { dash }, 0)); + + g2d.drawLine(dash, this.height / 2, w - dash, this.height / 2); + + g2d.dispose(); + } +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageComboBoxCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageComboBoxCellRenderer.java new file mode 100644 index 00000000..08cc0b66 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageComboBoxCellRenderer.java @@ -0,0 +1,50 @@ + +package net.sourceforge.filebot.ui.panel.subtitle; + + +import java.awt.Color; +import java.awt.Component; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.JList; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; + +import net.sourceforge.filebot.ResourceManager; + + +class LanguageComboBoxCellRenderer extends DefaultListCellRenderer { + + private final Border padding = new EmptyBorder(2, 2, 2, 2); + + private final Border favoritePadding = new EmptyBorder(0, 6, 0, 6); + + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + Language language = (Language) value; + setText(language.getName()); + setIcon(ResourceManager.getFlagIcon(language.getCode())); + + // default padding + setBorder(padding); + + LanguageComboBoxModel model = (LanguageComboBoxModel) list.getModel(); + + if ((index > 0 && index <= model.favorites().size())) { + // add favorite padding + setBorder(new CompoundBorder(favoritePadding, getBorder())); + } + + if (index == 0 || index == model.favorites().size()) { + // add separator border + setBorder(new CompoundBorder(new DashedSeparator(10, 4, Color.lightGray, list.getBackground()), getBorder())); + } + + return this; + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageComboBoxModel.java b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageComboBoxModel.java new file mode 100644 index 00000000..87c7cd03 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageComboBoxModel.java @@ -0,0 +1,188 @@ + +package net.sourceforge.filebot.ui.panel.subtitle; + + +import static net.sourceforge.filebot.ui.panel.subtitle.Language.*; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import javax.swing.AbstractListModel; +import javax.swing.ComboBoxModel; + + +class LanguageComboBoxModel extends AbstractListModel implements ComboBoxModel { + + public static final Language ALL_LANGUAGES = new Language("undefined", "All Languages"); + + private Language selection = ALL_LANGUAGES; + + private List favorites = new Favorites(2); + + private List values; + + + public LanguageComboBoxModel() { + values = Arrays.asList(availableLanguages()); + + // sort languages by name + Collections.sort(values, ALPHABETIC_ORDER); + } + + + @Override + public Language getElementAt(int index) { + // "All Languages" + if (index == 0) { + return ALL_LANGUAGES; + } + + // "All Languages" offset + index -= 1; + + if (index < favorites.size()) { + return favorites.get(index); + } + + // "Favorites" offset + index -= favorites.size(); + + return values.get(index); + } + + + @Override + public int getSize() { + // "All Languages" : favorites[] : values[] + return 1 + favorites.size() + values.size(); + } + + + public List favorites() { + return favorites; + } + + + @Override + public Language getSelectedItem() { + return selection; + } + + + @Override + public void setSelectedItem(Object value) { + if (value instanceof Language) { + selection = (Language) value; + } + } + + + protected int convertFavoriteIndexToModel(int favoriteIndex) { + return 1 + favoriteIndex; + } + + + protected void fireFavoritesAdded(int from, int to) { + fireIntervalAdded(this, convertFavoriteIndexToModel(from), convertFavoriteIndexToModel(to)); + } + + + protected void fireFavoritesRemoved(int from, int to) { + fireIntervalRemoved(this, convertFavoriteIndexToModel(from), convertFavoriteIndexToModel(to)); + } + + + private class Favorites extends AbstractList implements Set { + + private final List data; + + private final int capacity; + + + public Favorites(int capacity) { + this.data = new ArrayList(capacity); + this.capacity = capacity; + } + + + @Override + public Language get(int index) { + return data.get(index); + } + + + public boolean add(Language element) { + // add first + return addIfAbsent(0, element); + } + + + public void add(int index, Language element) { + addIfAbsent(index, element); + } + + + public boolean addIfAbsent(int index, Language element) { + // 1. ignore null values + // 2. ignore ALL_LANGUAGES + // 3. make sure there are no duplicates + // 4. limit size to capacity + if (element == null || element == ALL_LANGUAGES || contains(element) || index >= capacity) { + return false; + } + + // make sure there is free space + if (data.size() >= capacity) { + // remove last + remove(data.size() - 1); + } + + // add clone of language, because KeySelection behaviour will + // get weird if the same object is in the model multiple times + data.add(index, element.clone()); + + // update view + fireFavoritesAdded(index, index); + + return true; + } + + + @Override + public boolean contains(Object obj) { + // check via language code, because data consists of clones + if (obj instanceof Language) { + Language language = (Language) obj; + + for (Language element : data) { + if (language.getCode().equals(element.getCode())) + return true; + } + } + + return false; + } + + + @Override + public Language remove(int index) { + Language old = data.remove(index); + + // update view + fireFavoritesRemoved(index, index); + + return old; + } + + + @Override + public int size() { + return data.size(); + } + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/PopupSelectionListener.java b/source/net/sourceforge/filebot/ui/panel/subtitle/PopupSelectionListener.java new file mode 100644 index 00000000..ec0a050a --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/PopupSelectionListener.java @@ -0,0 +1,52 @@ + +package net.sourceforge.filebot.ui.panel.subtitle; + + +import static java.awt.event.ItemEvent.*; + +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.JComboBox; +import javax.swing.event.PopupMenuEvent; +import javax.swing.event.PopupMenuListener; + + +class PopupSelectionListener implements PopupMenuListener, ItemListener { + + private Object selected = null; + + + @Override + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + JComboBox comboBox = (JComboBox) e.getSource(); + + // selected item before popup + selected = comboBox.getSelectedItem(); + } + + + @Override + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + JComboBox comboBox = (JComboBox) e.getSource(); + + // check selected item after popup + if (selected != comboBox.getSelectedItem()) { + itemStateChanged(new ItemEvent(comboBox, ITEM_STATE_CHANGED, comboBox.getSelectedItem(), SELECTED)); + } + + selected = null; + } + + + @Override + public void popupMenuCanceled(PopupMenuEvent e) { + selected = null; + } + + + @Override + public void itemStateChanged(ItemEvent e) { + + } +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleListCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleListCellRenderer.java new file mode 100644 index 00000000..848d6971 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleListCellRenderer.java @@ -0,0 +1,95 @@ + +package net.sourceforge.filebot.ui.panel.subtitle; + + +import java.awt.Color; +import java.awt.Dimension; +import java.util.HashMap; +import java.util.Map; + +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JProgressBar; +import javax.swing.border.CompoundBorder; + +import net.miginfocom.swing.MigLayout; +import net.sourceforge.filebot.ResourceManager; +import net.sourceforge.tuned.ui.AbstractFancyListCellRenderer; + + +public class SubtitleListCellRenderer extends AbstractFancyListCellRenderer { + + private final JLabel titleLabel = new JLabel(); + private final JLabel languageLabel = new JLabel(); + + private final JProgressBar progressBar = new JProgressBar(0, 100); + + private final Map languageCodeMap = mapLanguageCodeByName(Language.availableLanguages()); + + + public SubtitleListCellRenderer() { + setHighlightingEnabled(false); + + progressBar.setStringPainted(true); + progressBar.setOpaque(false); + progressBar.setPreferredSize(new Dimension(80, 18)); + + setLayout(new MigLayout("fill, nogrid, insets 0")); + + add(languageLabel, "hidemode 3, w 85px!"); + add(titleLabel, ""); + add(progressBar, "gap indent:push"); + + setBorder(new CompoundBorder(new DashedSeparator(2, 4, Color.lightGray, Color.white), getBorder())); + } + + + @Override + public void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + SubtitlePackage subtitle = (SubtitlePackage) value; + + titleLabel.setIcon(ResourceManager.getIcon("status.archive")); + titleLabel.setText(subtitle.getName()); + + if (languageLabel.isVisible()) { + languageLabel.setText(subtitle.getLanguageName()); + languageLabel.setIcon(ResourceManager.getFlagIcon(languageCodeMap.get(subtitle.getLanguageName()))); + } + + //TODO download + progress + progressBar.setVisible(false); + progressBar.setString(subtitle.getDownloadTask().getState().toString().toLowerCase()); + + titleLabel.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); + languageLabel.setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); + + // don't paint border on last element + setBorderPainted(index < list.getModel().getSize() - 1); + } + + + private Map mapLanguageCodeByName(Language[] languages) { + Map map = new HashMap(); + + for (Language language : languages) { + map.put(language.getName(), language.getCode()); + } + + return map; + } + + + public JLabel getLanguageLabel() { + return languageLabel; + } + + + @Override + public void validate() { + // validate children, yet avoid flickering of the mouse cursor + validateTree(); + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleListComponent.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleListComponent.java new file mode 100644 index 00000000..ff552876 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleListComponent.java @@ -0,0 +1,79 @@ + +package net.sourceforge.filebot.ui.panel.subtitle; + + +import java.awt.event.ActionEvent; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.JTextField; + +import net.miginfocom.swing.MigLayout; +import net.sourceforge.filebot.ResourceManager; +import ca.odell.glazedlists.BasicEventList; +import ca.odell.glazedlists.EventList; +import ca.odell.glazedlists.FilterList; +import ca.odell.glazedlists.GlazedLists; +import ca.odell.glazedlists.ListSelection; +import ca.odell.glazedlists.TextFilterator; +import ca.odell.glazedlists.swing.EventListModel; +import ca.odell.glazedlists.swing.EventSelectionModel; +import ca.odell.glazedlists.swing.TextComponentMatcherEditor; + + +public class SubtitleListComponent extends JComponent { + + private EventList model = new BasicEventList(); + + private SubtitleListCellRenderer renderer = new SubtitleListCellRenderer(); + + private JTextField filterEditor = new JTextField(); + + + public SubtitleListComponent() { + TextFilterator filterator = GlazedLists.toStringTextFilterator(); + TextComponentMatcherEditor matcherEditor = new TextComponentMatcherEditor(filterEditor, filterator); + + JList list = new JList(new EventListModel(new FilterList(model, matcherEditor))); + list.setCellRenderer(renderer); + list.setFixedCellHeight(35); + + EventSelectionModel selectionModel = new EventSelectionModel(model); + selectionModel.setSelectionMode(ListSelection.MULTIPLE_INTERVAL_SELECTION_DEFENSIVE); + list.setSelectionModel(selectionModel); + + JButton clearButton = new JButton(clearFilterAction); + clearButton.setOpaque(false); + + setLayout(new MigLayout("fill, nogrid", "[fill]", "[pref!][fill]")); + + add(new JLabel("Filter:"), "gap indent:push"); + add(filterEditor, "wmin 120px, gap rel"); + add(clearButton, "w 24px!, h 24px!"); + add(new JScrollPane(list), "newline"); + } + + + public EventList getModel() { + return model; + } + + + public void setLanguageVisible(boolean visible) { + renderer.getLanguageLabel().setVisible(visible); + } + + private final Action clearFilterAction = new AbstractAction(null, ResourceManager.getIcon("edit.clear")) { + + @Override + public void actionPerformed(ActionEvent e) { + filterEditor.setText(""); + } + }; + +} diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/languages.xml b/source/net/sourceforge/filebot/ui/panel/subtitle/languages.xml new file mode 100644 index 00000000..0f77c4ce --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/languages.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +