From a5d987dc08ef74ccd8b08660fe68468dba175053 Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Sat, 25 Jul 2015 22:46:45 +0000 Subject: [PATCH] + Preset Editor UI --- source/net/filebot/WebServices.java | 17 + source/net/filebot/ui/rename/Preset.java | 36 +- .../net/filebot/ui/rename/PresetEditor.java | 356 ++++++++++++++++++ source/net/filebot/ui/rename/RenamePanel.java | 33 +- 4 files changed, 414 insertions(+), 28 deletions(-) create mode 100644 source/net/filebot/ui/rename/PresetEditor.java diff --git a/source/net/filebot/WebServices.java b/source/net/filebot/WebServices.java index a4b6bc87..b33d47d6 100644 --- a/source/net/filebot/WebServices.java +++ b/source/net/filebot/WebServices.java @@ -25,6 +25,7 @@ import net.filebot.similarity.MetricAvg; import net.filebot.web.AcoustIDClient; import net.filebot.web.AnidbClient; import net.filebot.web.AnidbSearchResult; +import net.filebot.web.Datasource; import net.filebot.web.EpisodeListProvider; import net.filebot.web.FanartTVClient; import net.filebot.web.ID3Lookup; @@ -110,6 +111,22 @@ public final class WebServices { return null; // default } + public static Datasource getDatasourceByName(String name) { + EpisodeListProvider sdb = WebServices.getEpisodeListProvider(name); + if (sdb != null) { + return sdb; + } + MovieIdentificationService mdb = WebServices.getMovieIdentificationService(name); + if (mdb != null) { + return mdb; + } + MusicIdentificationService adb = WebServices.getMusicIdentificationService(name); + if (adb != null) { + return adb; + } + return null; + } + public static final ExecutorService requestThreadPool = Executors.newCachedThreadPool(); public static class TheTVDBClientWithLocalSearch extends TheTVDBClient { diff --git a/source/net/filebot/ui/rename/Preset.java b/source/net/filebot/ui/rename/Preset.java index 8530f235..95cca7bc 100644 --- a/source/net/filebot/ui/rename/Preset.java +++ b/source/net/filebot/ui/rename/Preset.java @@ -2,12 +2,14 @@ package net.filebot.ui.rename; import java.io.File; import java.util.Locale; -import java.util.regex.Pattern; +import net.filebot.Language; import net.filebot.StandardRenameAction; import net.filebot.WebServices; +import net.filebot.format.ExpressionFilter; import net.filebot.format.ExpressionFormat; import net.filebot.ui.rename.FormatDialog.Mode; +import net.filebot.web.Datasource; import net.filebot.web.EpisodeListProvider; import net.filebot.web.MovieIdentificationService; import net.filebot.web.MusicIdentificationService; @@ -25,16 +27,16 @@ public class Preset { public String language; public String action; - public Preset(String name, String path, String includes, String format, String database, String sortOrder, String matchMode, String language, String action) { + public Preset(String name, File path, ExpressionFilter includes, ExpressionFormat format, Datasource database, SortOrder sortOrder, String matchMode, Language language, StandardRenameAction action) { this.name = name; - this.path = path; - this.includes = includes; - this.format = format; - this.database = database; - this.sortOrder = sortOrder; - this.matchMode = matchMode; - this.language = language; - this.action = action; + this.path = path == null ? null : path.getPath(); + this.includes = includes == null ? null : includes.getExpression(); + this.format = format == null ? null : format.getExpression(); + this.database = database == null ? null : database.getName(); + this.sortOrder = sortOrder == null ? null : sortOrder.name(); + this.matchMode = matchMode == null ? null : matchMode; + this.language = language == null ? null : language.getCode(); + this.action = action == null ? null : action.name(); } public String getName() { @@ -45,18 +47,26 @@ public class Preset { return new File(path); } - public Pattern getIncludePattern() { - return includes == null || includes.isEmpty() ? null : Pattern.compile(includes, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + public ExpressionFilter getIncludeFilter() { + try { + return includes == null || includes.isEmpty() ? null : new ExpressionFilter(includes); + } catch (Exception e) { + return null; + } } public ExpressionFormat getFormat() { try { - return new ExpressionFormat(format); + return format == null || format.isEmpty() ? null : new ExpressionFormat(format); } catch (Exception e) { return null; } } + public Datasource getDatabase() { + return WebServices.getDatasourceByName(database); + } + public AutoCompleteMatcher getAutoCompleteMatcher() { EpisodeListProvider sdb = WebServices.getEpisodeListProvider(database); if (sdb != null) { diff --git a/source/net/filebot/ui/rename/PresetEditor.java b/source/net/filebot/ui/rename/PresetEditor.java new file mode 100644 index 00000000..6c6e973d --- /dev/null +++ b/source/net/filebot/ui/rename/PresetEditor.java @@ -0,0 +1,356 @@ +package net.filebot.ui.rename; + +import static net.filebot.ui.NotificationLogging.*; +import static java.awt.Font.*; +import static javax.swing.BorderFactory.*; +import static net.filebot.util.ui.SwingUI.*; + +import java.awt.Component; +import java.awt.Font; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.EnumSet; + +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.ButtonGroup; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JTextField; +import javax.swing.ListCellRenderer; +import javax.swing.SwingUtilities; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; + +import net.filebot.Language; +import net.filebot.RenameAction; +import net.filebot.ResourceManager; +import net.filebot.StandardRenameAction; +import net.filebot.UserFiles; +import net.filebot.WebServices; +import net.filebot.format.ExpressionFilter; +import net.filebot.format.ExpressionFormat; +import net.filebot.ui.HeaderPanel; +import net.filebot.web.Datasource; +import net.filebot.web.EpisodeListProvider; +import net.filebot.web.SortOrder; +import net.miginfocom.swing.MigLayout; + +import org.fife.ui.rsyntaxtextarea.RSyntaxDocument; +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; +import org.fife.ui.rsyntaxtextarea.SyntaxConstants; +import org.fife.ui.rtextarea.RTextScrollPane; + +public class PresetEditor extends JDialog { + + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + new PresetEditor(null).setVisible(true); + }); + } + + enum Result { + SET, DELETE, CANCEL; + } + + private Result result = Result.CANCEL; + + private HeaderPanel presetNameHeader; + private JTextField pathInput; + private RSyntaxTextArea filterEditor; + private RSyntaxTextArea formatEditor; + private JComboBox actionCombo; + private JComboBox providerCombo; + private JComboBox sortOrderCombo; + private JComboBox matchModeCombo; + private JComboBox languageCombo; + + private JRadioButton selectRadio; + private JRadioButton inheritRadio; + + public PresetEditor(Window owner) { + super(owner, "Preset Editor", ModalityType.APPLICATION_MODAL); + JComponent c = (JComponent) getContentPane(); + + presetNameHeader = new HeaderPanel(); + + inheritRadio = new JRadioButton("Use Original Files"); + selectRadio = new JRadioButton("Do Select"); + pathInput = new JTextField(40); + + filterEditor = createEditor(); + formatEditor = createEditor(); + + actionCombo = createRenameActionCombo(); + providerCombo = createDataProviderCombo(); + sortOrderCombo = new JComboBox(SortOrder.values()); + matchModeCombo = createMatchModeCombo(); + languageCombo = createLanguageCombo(); + + JButton pathButton = createImageButton(selectInputFolder); + + JPanel inputPanel = new JPanel(new MigLayout("insets 0, fill")); + inputPanel.add(new JLabel("Input Folder:"), "gap indent"); + inputPanel.add(pathInput, "growx, gap rel"); + inputPanel.add(pathButton, "gap rel, wrap"); + inputPanel.add(new JLabel("Includes:"), "gap indent, skip 1, split 2"); + inputPanel.add(wrapEditor(filterEditor), "growx, gap rel, gap after indent"); + + JPanel inputGroup = createGroupPanel("Files"); + inputGroup.add(selectRadio); + inputGroup.add(inheritRadio, "wrap"); + inputGroup.add(inputPanel); + + JPanel formatGroup = createGroupPanel("Format"); + formatGroup.add(new JLabel("Expression:")); + formatGroup.add(wrapEditor(formatEditor), "growx, gap rel"); + + JPanel searchGroup = createGroupPanel("Options"); + searchGroup.add(new JLabel("Datasource:"), "sg label"); + searchGroup.add(providerCombo, "sg combo"); + searchGroup.add(new JLabel("Episode Order:"), "sg label, gap indent"); + searchGroup.add(sortOrderCombo, "sg combo, wrap"); + searchGroup.add(new JLabel("Language:"), "sg label"); + searchGroup.add(languageCombo, "sg combo"); + searchGroup.add(new JLabel("Match Mode:"), "sg label, gap indent"); + searchGroup.add(matchModeCombo, "sg combo, wrap"); + searchGroup.add(new JLabel("Rename Action:"), "sg label"); + searchGroup.add(actionCombo, "sg combo, wrap"); + + c.setLayout(new MigLayout("insets dialog, hidemode 3, nogrid, fill")); + c.add(presetNameHeader, "wmin 150px, hmin 75px, growx, dock north"); + c.add(inputGroup, "growx, wrap"); + c.add(formatGroup, "growx, wrap"); + c.add(searchGroup, "growx, wrap push"); + c.add(new JButton(set), "tag apply"); + c.add(new JButton(delete), "tag cancel"); + setSize(650, 550); + + ButtonGroup inputButtonGroup = new ButtonGroup(); + inputButtonGroup.add(inheritRadio); + inputButtonGroup.add(selectRadio); + selectRadio.addItemListener((evt) -> { + inputPanel.setVisible(selectRadio.isSelected()); + }); + providerCombo.addItemListener((evt) -> { + sortOrderCombo.setEnabled(evt.getItem() instanceof EpisodeListProvider); + }); + inheritRadio.setSelected(true); + inputPanel.setVisible(false); + + presetNameHeader.getTitleLabel().setText("Preset"); + } + + public void setPreset(Preset p) { + presetNameHeader.getTitleLabel().setText(p.getName()); + pathInput.setText(p.getInputFolder().getPath()); + filterEditor.setText(p.getIncludeFilter().getExpression()); + formatEditor.setText(p.getFormat().getExpression()); + providerCombo.setSelectedItem(p.getDatabase()); + sortOrderCombo.setSelectedItem(p.getSortOrder()); + matchModeCombo.setSelectedItem(p.getMatchMode()); + actionCombo.setSelectedItem(p.getRenameAction()); + } + + public Preset getPreset() throws Exception { + String name = presetNameHeader.getTitleLabel().getText(); + File path = inheritRadio.isSelected() ? null : new File(pathInput.getText()); + ExpressionFilter includes = inheritRadio.isSelected() ? null : new ExpressionFilter(filterEditor.getText()); + ExpressionFormat format = formatEditor.getText().trim().isEmpty() ? null : new ExpressionFormat(formatEditor.getText()); + Datasource database = ((Datasource) providerCombo.getSelectedItem()); + SortOrder sortOrder = ((SortOrder) sortOrderCombo.getSelectedItem()); + String matchMode = (String) matchModeCombo.getSelectedItem(); + Language language = ((Language) languageCombo.getSelectedItem()); + StandardRenameAction action = (StandardRenameAction) actionCombo.getSelectedItem(); + + return new Preset(name, path, includes, format, database, sortOrder, matchMode, language, action); + } + + private final Action selectInputFolder = new AbstractAction("Select Input Folder", ResourceManager.getIcon("action.load")) { + + @Override + public void actionPerformed(ActionEvent evt) { + File f = UserFiles.showOpenDialogSelectFolder(null, "Select Input Folder", evt); + if (f != null) { + pathInput.setText(f.getAbsolutePath()); + } + } + }; + + private JPanel createGroupPanel(String title) { + JPanel inputGroup = new JPanel(new MigLayout("insets dialog, hidemode 3, nogrid, fill")); + inputGroup.setBorder(createTitledBorder(title)); + return inputGroup; + } + + private RSyntaxTextArea createEditor() { + final RSyntaxTextArea editor = new RSyntaxTextArea(new RSyntaxDocument(SyntaxConstants.SYNTAX_STYLE_GROOVY) { + @Override + public void insertString(int offs, String str, AttributeSet a) throws BadLocationException { + super.insertString(offs, str.replaceAll("\\s", " "), a); // FORCE SINGLE LINE + } + }, null, 1, 80); + + editor.setAntiAliasingEnabled(true); + editor.setAnimateBracketMatching(false); + editor.setAutoIndentEnabled(false); + editor.setClearWhitespaceLinesEnabled(false); + editor.setBracketMatchingEnabled(true); + editor.setCloseCurlyBraces(false); + editor.setCodeFoldingEnabled(false); + editor.setHyperlinksEnabled(false); + editor.setUseFocusableTips(false); + editor.setHighlightCurrentLine(false); + editor.setLineWrap(false); + editor.setFont(new Font(MONOSPACED, PLAIN, 14)); + + return editor; + } + + private RTextScrollPane wrapEditor(RSyntaxTextArea editor) { + RTextScrollPane scroll = new RTextScrollPane(editor, false); + scroll.setLineNumbersEnabled(false); + scroll.setFoldIndicatorEnabled(false); + scroll.setIconRowHeaderEnabled(false); + scroll.setVerticalScrollBarPolicy(RTextScrollPane.VERTICAL_SCROLLBAR_NEVER); + scroll.setHorizontalScrollBarPolicy(RTextScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scroll.setBackground(editor.getBackground()); + scroll.setOpaque(true); + scroll.setBorder(pathInput.getBorder()); + return scroll; + } + + private JComboBox createDataProviderCombo() { + DefaultComboBoxModel providers = new DefaultComboBoxModel<>(); + for (Datasource[] seq : new Datasource[][] { WebServices.getEpisodeListProviders(), WebServices.getMovieIdentificationServices(), WebServices.getMusicIdentificationServices() }) { + for (Datasource it : seq) { + providers.addElement(it); + } + } + + JComboBox combo = new JComboBox(providers); + combo.setRenderer(new ListCellRenderer() { + + private final ListCellRenderer parent = (ListCellRenderer) combo.getRenderer(); + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) parent.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + if (value instanceof Datasource) { + Datasource provider = (Datasource) value; + label.setText(provider.getName()); + label.setIcon(provider.getIcon()); + } + + return label; + } + }); + + return combo; + } + + private JComboBox createMatchModeCombo() { + String[] modes = new String[] { RenamePanel.MATCH_MODE_OPPORTUNISTIC, RenamePanel.MATCH_MODE_STRICT }; + JComboBox combo = new JComboBox<>(modes); + return combo; + } + + private JComboBox createLanguageCombo() { + DefaultComboBoxModel languages = new DefaultComboBoxModel<>(); + for (Language it : Language.preferredLanguages()) { + languages.addElement(it); + } + for (Language it : Language.availableLanguages()) { + languages.addElement(it); + } + + JComboBox combo = new JComboBox(languages); + combo.setRenderer(new ListCellRenderer() { + + private final ListCellRenderer parent = (ListCellRenderer) combo.getRenderer(); + + @Override + public Component getListCellRendererComponent(JList list, Language value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) parent.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + if (value instanceof Language) { + Language it = (Language) value; + label.setText(it.getName()); + label.setIcon(ResourceManager.getFlagIcon(it.getCode())); + } + + return label; + } + + }); + + return combo; + } + + private JComboBox createRenameActionCombo() { + DefaultComboBoxModel actions = new DefaultComboBoxModel<>(); + for (StandardRenameAction it : EnumSet.of(StandardRenameAction.MOVE, StandardRenameAction.COPY, StandardRenameAction.KEEPLINK, StandardRenameAction.SYMLINK, StandardRenameAction.HARDLINK)) { + actions.addElement(it); + } + + JComboBox combo = new JComboBox(actions); + combo.setRenderer(new ListCellRenderer() { + + private final ListCellRenderer parent = (ListCellRenderer) combo.getRenderer(); + + @Override + public Component getListCellRendererComponent(JList list, RenameAction value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel label = (JLabel) parent.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + + if (value instanceof StandardRenameAction) { + StandardRenameAction it = (StandardRenameAction) value; + label.setText(it.getDisplayName()); + label.setIcon(ResourceManager.getIcon("rename.action." + it.toString().toLowerCase())); + } + + return label; + } + + }); + + return combo; + } + + public Result getResult() { + return result; + } + + private final Action set = new AbstractAction("Set", ResourceManager.getIcon("dialog.continue")) { + + @Override + public void actionPerformed(ActionEvent evt) { + try { + Preset preset = getPreset(); + if (preset != null) { + result = Result.SET; + setVisible(false); + } + } catch (Exception e) { + UILogger.severe("Invalid preset settings: " + e.getMessage()); + } + } + }; + + private final Action delete = new AbstractAction("Delete", ResourceManager.getIcon("dialog.cancel")) { + + @Override + public void actionPerformed(ActionEvent evt) { + result = Result.DELETE; + setVisible(false); + } + }; + +} diff --git a/source/net/filebot/ui/rename/RenamePanel.java b/source/net/filebot/ui/rename/RenamePanel.java index 1db1a159..5cdc6f26 100644 --- a/source/net/filebot/ui/rename/RenamePanel.java +++ b/source/net/filebot/ui/rename/RenamePanel.java @@ -20,6 +20,7 @@ import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.io.FileFilter; import java.util.ArrayList; import java.util.EnumSet; import java.util.LinkedList; @@ -55,6 +56,7 @@ import net.filebot.Settings; import net.filebot.StandardRenameAction; import net.filebot.UserFiles; import net.filebot.WebServices; +import net.filebot.format.ExpressionFilter; import net.filebot.format.MediaBindingBean; import net.filebot.mac.MacAppUtilities; import net.filebot.media.MediaDetection; @@ -84,8 +86,8 @@ import ca.odell.glazedlists.swing.EventSelectionModel; public class RenamePanel extends JComponent { - private static final String MATCH_MODE_OPPORTUNISTIC = "Opportunistic"; - private static final String MATCH_MODE_STRICT = "Strict"; + public static final String MATCH_MODE_OPPORTUNISTIC = "Opportunistic"; + public static final String MATCH_MODE_STRICT = "Strict"; protected final RenameModel renameModel = new RenameModel(); @@ -391,15 +393,7 @@ public class RenamePanel extends JComponent { } actionPopup.addDescription(new JLabel("Options:")); - actionPopup.add(new AbstractAction("New Preset", ResourceManager.getIcon("script.add")) { - - @Override - public void actionPerformed(ActionEvent e) { - // TODO Auto-generated method stub - - } - }); - actionPopup.add(new AbstractAction("Delete Preset", ResourceManager.getIcon("script.remove")) { + actionPopup.add(new AbstractAction("Edit Presets", ResourceManager.getIcon("script.add")) { @Override public void actionPerformed(ActionEvent e) { @@ -663,11 +657,20 @@ public class RenamePanel extends JComponent { public List getFiles(ActionEvent evt) { List input = new ArrayList(); if (preset.getInputFolder() != null) { - for (File f : FileUtilities.listFiles(preset.getInputFolder())) { - if (preset.getIncludePattern() == null || preset.getIncludePattern().matcher(f.getPath()).find()) { - input.add(f); - } + List selection = FileUtilities.listFiles(preset.getInputFolder()); + ExpressionFilter filter = preset.getIncludeFilter(); + if (filter != null) { + selection = FileUtilities.filter(selection, (File f) -> { + try { + return filter.matches(new MediaBindingBean(null, f)); + } catch (Exception e) { + Logger.getLogger(RenamePanel.class.getName()).log(Level.WARNING, e.toString()); + return false; + } + }); } + input.addAll(selection); + renameModel.clear(); renameModel.files().addAll(input); } else {