1
0
mirror of https://github.com/mitb-archive/filebot synced 2024-12-24 16:58:51 -05:00

+ usability enhancements regarding FormatEditor

This commit is contained in:
Reinhard Pointner 2013-12-18 05:53:59 +00:00
parent f81e2fa9ea
commit 0d6ae94ae9
4 changed files with 161 additions and 164 deletions

View File

@ -1,21 +1,17 @@
package net.sourceforge.filebot.format; package net.sourceforge.filebot.format;
public class BindingException extends RuntimeException { public class BindingException extends RuntimeException {
public BindingException(String message, Throwable cause) { public BindingException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
public BindingException(String binding, String innerMessage) { public BindingException(String binding, String innerMessage) {
this(binding, innerMessage, null); this(binding, innerMessage, null);
} }
public BindingException(String binding, String innerMessage, Throwable cause) { public BindingException(String binding, String innerMessage, Throwable cause) {
this(String.format("BindingError: \"%s\": %s", binding, innerMessage), cause); this(String.format("BindingException: \"%s\": %s", binding, innerMessage), cause);
} }
} }

View File

@ -141,7 +141,7 @@ public class FormatDialog extends JDialog {
} }
} }
public FormatDialog(Window owner) { public FormatDialog(Window owner, Mode initMode, MediaBindingBean lockOnBinding) {
super(owner, ModalityType.DOCUMENT_MODAL); super(owner, ModalityType.DOCUMENT_MODAL);
// initialize hidden // initialize hidden
@ -223,26 +223,29 @@ public class FormatDialog extends JDialog {
// install editor suggestions popup // install editor suggestions popup
editor.setComponentPopupMenu(createRecentFormatPopup()); editor.setComponentPopupMenu(createRecentFormatPopup());
// episode mode by default
setMode(Mode.Episode);
// initialize window properties // initialize window properties
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
setSize(610, 430); setSize(610, 430);
// initialize data
setState(initMode, lockOnBinding != null ? lockOnBinding : restoreSample(initMode), lockOnBinding != null);
} }
public void setMode(Mode mode) { public void setState(Mode mode, MediaBindingBean bindings, boolean locked) {
this.mode = mode; this.mode = mode;
this.setTitle(String.format("%s Format", mode)); this.setTitle(String.format(locked ? "%s Format - %s ⇔ %s" : "%s Format", mode, bindings.getInfoObject(), bindings.getMediaFile().getName()));
title.setText(this.getTitle()); title.setText(this.getTitle());
status.setVisible(false); status.setVisible(false);
switchEditModeAction.putValue(Action.NAME, String.format("Switch to %s Format", mode.next())); switchEditModeAction.putValue(Action.NAME, String.format("Switch to %s Format", mode.next()));
switchEditModeAction.setEnabled(!locked);
changeSampleAction.setEnabled(!locked);
updateHelpPanel(mode); updateHelpPanel(mode);
// update preview to current format // update preview to current format
sample = restoreSample(mode); sample = bindings;
// restore editor state // restore editor state
setFormatCode(mode.persistentFormatHistory().isEmpty() ? "" : mode.persistentFormatHistory().get(0)); setFormatCode(mode.persistentFormatHistory().isEmpty() ? "" : mode.persistentFormatHistory().get(0));
@ -388,7 +391,7 @@ public class FormatDialog extends JDialog {
return panel; return panel;
} }
private MediaBindingBean restoreSample(Mode mode) { protected MediaBindingBean restoreSample(Mode mode) {
Object info = null; Object info = null;
File media = null; File media = null;
@ -646,7 +649,8 @@ public class FormatDialog extends JDialog {
@Override @Override
public void actionPerformed(ActionEvent e) { public void actionPerformed(ActionEvent e) {
setMode(mode.next()); Mode next = mode.next();
setState(next, restoreSample(next), false);
} }
}; };

View File

@ -1,7 +1,5 @@
package net.sourceforge.filebot.ui.rename; package net.sourceforge.filebot.ui.rename;
import static net.sourceforge.tuned.FileUtilities.*; import static net.sourceforge.tuned.FileUtilities.*;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
@ -31,7 +29,6 @@ import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.TransformedList; import ca.odell.glazedlists.TransformedList;
import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEvent;
public class RenameModel extends MatchModel<Object, File> { public class RenameModel extends MatchModel<Object, File> {
private final FormattedFutureEventList names = new FormattedFutureEventList(this.values()); private final FormattedFutureEventList names = new FormattedFutureEventList(this.values());
@ -45,13 +42,11 @@ public class RenameModel extends MatchModel<Object, File> {
return true; return true;
} }
@Override @Override
public String preview(Match<?, ?> match) { public String preview(Match<?, ?> match) {
return format(match, null); return format(match, null);
} }
@Override @Override
public String format(Match<?, ?> match, Map<?, ?> context) { public String format(Match<?, ?> match, Map<?, ?> context) {
// clean up path separators like / or \ // clean up path separators like / or \
@ -61,27 +56,22 @@ public class RenameModel extends MatchModel<Object, File> {
private boolean preserveExtension = true; private boolean preserveExtension = true;
public EventList<FormattedFuture> names() { public EventList<FormattedFuture> names() {
return names; return names;
} }
public EventList<File> files() { public EventList<File> files() {
return candidates(); return candidates();
} }
public boolean preserveExtension() { public boolean preserveExtension() {
return preserveExtension; return preserveExtension;
} }
public void setPreserveExtension(boolean preserveExtension) { public void setPreserveExtension(boolean preserveExtension) {
this.preserveExtension = preserveExtension; this.preserveExtension = preserveExtension;
} }
public Map<File, String> getRenameMap() { public Map<File, String> getRenameMap() {
Map<File, String> map = new LinkedHashMap<File, String>(); Map<File, String> map = new LinkedHashMap<File, String>();
@ -122,7 +112,6 @@ public class RenameModel extends MatchModel<Object, File> {
return map; return map;
} }
public void useFormatter(Object key, MatchFormatter formatter) { public void useFormatter(Object key, MatchFormatter formatter) {
if (formatter != null) { if (formatter != null) {
formatters.put(key, formatter); formatters.put(key, formatter);
@ -134,7 +123,6 @@ public class RenameModel extends MatchModel<Object, File> {
names.refresh(); names.refresh();
} }
private MatchFormatter getFormatter(Match<Object, File> match) { private MatchFormatter getFormatter(Match<Object, File> match) {
for (MatchFormatter formatter : formatters.values()) { for (MatchFormatter formatter : formatters.values()) {
if (formatter.canFormat(match)) { if (formatter.canFormat(match)) {
@ -145,6 +133,21 @@ public class RenameModel extends MatchModel<Object, File> {
return defaultFormatter; return defaultFormatter;
} }
public Map<File, Object> getMatchContext() {
return new AbstractMap<File, Object>() {
@Override
public Set<Entry<File, Object>> entrySet() {
Set<Entry<File, Object>> context = new LinkedHashSet<Entry<File, Object>>();
for (Match<Object, File> it : matches()) {
if (it.getValue() != null && it.getCandidate() != null) {
context.add(new SimpleImmutableEntry<File, Object>(it.getCandidate(), it.getValue()));
}
}
return context;
}
};
}
private class FormattedFutureEventList extends TransformedList<Object, FormattedFuture> { private class FormattedFutureEventList extends TransformedList<Object, FormattedFuture> {
@ -152,32 +155,27 @@ public class RenameModel extends MatchModel<Object, File> {
private final Executor backgroundFormatter = new ThreadPoolExecutor(0, 1, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); private final Executor backgroundFormatter = new ThreadPoolExecutor(0, 1, 5L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
public FormattedFutureEventList(EventList<Object> source) { public FormattedFutureEventList(EventList<Object> source) {
super(source); super(source);
this.source.addListEventListener(this); this.source.addListEventListener(this);
} }
@Override @Override
public FormattedFuture get(int index) { public FormattedFuture get(int index) {
return futures.get(index); return futures.get(index);
} }
@Override @Override
protected boolean isWritable() { protected boolean isWritable() {
// can't write to source directly // can't write to source directly
return false; return false;
} }
@Override @Override
public void add(int index, FormattedFuture value) { public void add(int index, FormattedFuture value) {
source.add(index, value.getMatch().getValue()); source.add(index, value.getMatch().getValue());
} }
@Override @Override
public FormattedFuture set(int index, FormattedFuture value) { public FormattedFuture set(int index, FormattedFuture value) {
FormattedFuture obsolete = get(index); FormattedFuture obsolete = get(index);
@ -187,7 +185,6 @@ public class RenameModel extends MatchModel<Object, File> {
return obsolete; return obsolete;
} }
@Override @Override
public FormattedFuture remove(int index) { public FormattedFuture remove(int index) {
FormattedFuture obsolete = get(index); FormattedFuture obsolete = get(index);
@ -197,7 +194,6 @@ public class RenameModel extends MatchModel<Object, File> {
return obsolete; return obsolete;
} }
@Override @Override
public void listChanged(ListEvent<Object> listChanges) { public void listChanged(ListEvent<Object> listChanges) {
updates.beginEvent(true); updates.beginEvent(true);
@ -210,7 +206,7 @@ public class RenameModel extends MatchModel<Object, File> {
Match<Object, File> match = getMatch(index); Match<Object, File> match = getMatch(index);
// create new future // create new future
final FormattedFuture future = new FormattedFuture(match, getFormatter(match), getContext()); final FormattedFuture future = new FormattedFuture(match, getFormatter(match), getMatchContext());
// update data // update data
if (type == ListEvent.INSERT) { if (type == ListEvent.INSERT) {
@ -252,13 +248,12 @@ public class RenameModel extends MatchModel<Object, File> {
updates.commitEvent(); updates.commitEvent();
} }
public void refresh() { public void refresh() {
updates.beginEvent(true); updates.beginEvent(true);
for (int i = 0; i < size(); i++) { for (int i = 0; i < size(); i++) {
FormattedFuture obsolete = futures.get(i); FormattedFuture obsolete = futures.get(i);
FormattedFuture future = new FormattedFuture(obsolete.getMatch(), getFormatter(obsolete.getMatch()), getContext()); FormattedFuture future = new FormattedFuture(obsolete.getMatch(), getFormatter(obsolete.getMatch()), getMatchContext());
// replace and cancel old future // replace and cancel old future
cancel(futures.set(i, future)); cancel(futures.set(i, future));
@ -272,31 +267,12 @@ public class RenameModel extends MatchModel<Object, File> {
updates.commitEvent(); updates.commitEvent();
} }
private Map<File, Object> getContext() {
return new AbstractMap<File, Object>() {
@Override
public Set<Entry<File, Object>> entrySet() {
Set<Entry<File, Object>> context = new LinkedHashSet<Entry<File, Object>>();
for (Match<Object, File> it : matches()) {
if (it.getValue() != null && it.getCandidate() != null) {
context.add(new SimpleImmutableEntry<File, Object>(it.getCandidate(), it.getValue()));
}
}
return context;
}
};
}
private void submit(FormattedFuture future) { private void submit(FormattedFuture future) {
// observe and enqueue worker task // observe and enqueue worker task
future.addPropertyChangeListener(futureListener); future.addPropertyChangeListener(futureListener);
backgroundFormatter.execute(future); backgroundFormatter.execute(future);
} }
private void cancel(FormattedFuture future) { private void cancel(FormattedFuture future) {
// remove listener and cancel worker task // remove listener and cancel worker task
future.removePropertyChangeListener(futureListener); future.removePropertyChangeListener(futureListener);
@ -321,7 +297,6 @@ public class RenameModel extends MatchModel<Object, File> {
}; };
} }
public static class FormattedFuture extends SwingWorker<String, Void> { public static class FormattedFuture extends SwingWorker<String, Void> {
private final Match<Object, File> match; private final Match<Object, File> match;
@ -329,35 +304,29 @@ public class RenameModel extends MatchModel<Object, File> {
private final MatchFormatter formatter; private final MatchFormatter formatter;
private FormattedFuture(Match<Object, File> match, MatchFormatter formatter, Map<File, Object> context) { private FormattedFuture(Match<Object, File> match, MatchFormatter formatter, Map<File, Object> context) {
this.match = match; this.match = match;
this.formatter = formatter; this.formatter = formatter;
this.context = context; this.context = context;
} }
public boolean isComplexFormat() { public boolean isComplexFormat() {
return formatter instanceof ExpressionFormatter; return formatter instanceof ExpressionFormatter;
} }
public Match<Object, File> getMatch() { public Match<Object, File> getMatch() {
return match; return match;
} }
public String preview() { public String preview() {
return formatter.preview(match).trim(); return formatter.preview(match).trim();
} }
@Override @Override
protected String doInBackground() throws Exception { protected String doInBackground() throws Exception {
return formatter.format(match, context).trim(); return formatter.format(match, context).trim();
} }
@Override @Override
public String toString() { public String toString() {
if (isDone()) { if (isDone()) {

View File

@ -23,6 +23,7 @@ import java.util.EnumSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -50,8 +51,10 @@ import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.Settings;
import net.sourceforge.filebot.StandardRenameAction; import net.sourceforge.filebot.StandardRenameAction;
import net.sourceforge.filebot.WebServices; import net.sourceforge.filebot.WebServices;
import net.sourceforge.filebot.format.MediaBindingBean;
import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.ui.Language; import net.sourceforge.filebot.ui.Language;
import net.sourceforge.filebot.ui.rename.FormatDialog.Mode;
import net.sourceforge.filebot.ui.rename.RenameModel.FormattedFuture; import net.sourceforge.filebot.ui.rename.RenameModel.FormattedFuture;
import net.sourceforge.filebot.web.AudioTrack; import net.sourceforge.filebot.web.AudioTrack;
import net.sourceforge.filebot.web.AudioTrackFormat; import net.sourceforge.filebot.web.AudioTrackFormat;
@ -85,6 +88,8 @@ public class RenamePanel extends JComponent {
private static final PreferencesEntry<String> persistentEpisodeFormat = Settings.forPackage(RenamePanel.class).entry("rename.format.episode"); private static final PreferencesEntry<String> persistentEpisodeFormat = Settings.forPackage(RenamePanel.class).entry("rename.format.episode");
private static final PreferencesEntry<String> persistentMovieFormat = Settings.forPackage(RenamePanel.class).entry("rename.format.movie"); private static final PreferencesEntry<String> persistentMovieFormat = Settings.forPackage(RenamePanel.class).entry("rename.format.movie");
private static final PreferencesEntry<String> persistentMusicFormat = Settings.forPackage(RenamePanel.class).entry("rename.format.music"); private static final PreferencesEntry<String> persistentMusicFormat = Settings.forPackage(RenamePanel.class).entry("rename.format.music");
private static final PreferencesEntry<String> persistentLastFormatState = Settings.forPackage(RenamePanel.class).entry("rename.last.format.state");
private static final PreferencesEntry<String> persistentPreferredLanguage = Settings.forPackage(RenamePanel.class).entry("rename.language").defaultValue("en"); private static final PreferencesEntry<String> persistentPreferredLanguage = Settings.forPackage(RenamePanel.class).entry("rename.language").defaultValue("en");
private static final PreferencesEntry<String> persistentPreferredEpisodeOrder = Settings.forPackage(RenamePanel.class).entry("rename.episode.order").defaultValue("Airdate"); private static final PreferencesEntry<String> persistentPreferredEpisodeOrder = Settings.forPackage(RenamePanel.class).entry("rename.episode.order").defaultValue("Airdate");
@ -245,19 +250,15 @@ public class RenamePanel extends JComponent {
@Override @Override
public void mouseClicked(MouseEvent evt) { public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 2) { if (evt.getClickCount() == 2) {
getWindow(evt.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try { try {
JList list = (JList) evt.getSource(); JList list = (JList) evt.getSource();
if (list.getSelectedIndex() >= 0) { if (list.getSelectedIndex() >= 0) {
Object item = ((FormattedFuture) list.getSelectedValue()).getMatch().getValue(); Match<Object, File> match = renameModel.getMatch(list.getSelectedIndex());
if (item instanceof Movie) { Map<File, Object> context = renameModel.getMatchContext();
getWindow(evt.getSource()).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
Movie m = (Movie) item; MediaBindingBean sample = new MediaBindingBean(match.getValue(), match.getCandidate(), context);
if (m.getTmdbId() > 0) { showFormatEditor(sample);
Desktop.getDesktop().browse(WebServices.TMDb.getMoviePageLink(m.getTmdbId()));
} else if (m.getImdbId() > 0) {
Desktop.getDesktop().browse(WebServices.IMDb.getMoviePageLink(m.getImdbId()));
}
}
} }
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger(RenamePanel.class.getName()).log(Level.WARNING, e.getMessage()); Logger.getLogger(RenamePanel.class.getName()).log(Level.WARNING, e.getMessage());
@ -312,26 +313,7 @@ public class RenamePanel extends JComponent {
@Override @Override
public void actionPerformed(ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
FormatDialog dialog = new FormatDialog(getWindowAncestor(RenamePanel.this)); showFormatEditor(null);
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
dialog.setVisible(true);
if (dialog.submit()) {
switch (dialog.getMode()) {
case Episode:
renameModel.useFormatter(Episode.class, new ExpressionFormatter(dialog.getFormat().getExpression(), EpisodeFormat.SeasonEpisode, Episode.class));
persistentEpisodeFormat.setValue(dialog.getFormat().getExpression());
break;
case Movie:
renameModel.useFormatter(Movie.class, new ExpressionFormatter(dialog.getFormat().getExpression(), MovieFormat.NameYear, Movie.class));
persistentMovieFormat.setValue(dialog.getFormat().getExpression());
break;
case Music:
renameModel.useFormatter(AudioTrack.class, new ExpressionFormatter(dialog.getFormat().getExpression(), new AudioTrackFormat(), AudioTrack.class));
persistentMusicFormat.setValue(dialog.getFormat().getExpression());
break;
}
}
} }
}); });
@ -408,6 +390,52 @@ public class RenamePanel extends JComponent {
return actionPopup; return actionPopup;
} }
protected void showFormatEditor(MediaBindingBean lockOnBinding) {
// default to Episode mode
Mode initMode = null;
if (lockOnBinding == null || lockOnBinding.getInfoObject() instanceof Episode) {
initMode = Mode.Episode;
} else if (lockOnBinding.getInfoObject() instanceof Movie) {
initMode = Mode.Movie;
} else if (lockOnBinding.getInfoObject() instanceof AudioTrack) {
initMode = Mode.Music;
}
// restore previous mode
if (lockOnBinding == null) {
try {
initMode = Mode.valueOf(persistentLastFormatState.getValue());
} catch (Exception e) {
Logger.getLogger(RenamePanel.class.getName()).log(Level.WARNING, e.getMessage());
}
}
FormatDialog dialog = new FormatDialog(getWindowAncestor(RenamePanel.this), initMode, lockOnBinding);
dialog.setLocation(getOffsetLocation(dialog.getOwner()));
dialog.setVisible(true);
if (dialog.submit()) {
switch (dialog.getMode()) {
case Episode:
renameModel.useFormatter(Episode.class, new ExpressionFormatter(dialog.getFormat().getExpression(), EpisodeFormat.SeasonEpisode, Episode.class));
persistentEpisodeFormat.setValue(dialog.getFormat().getExpression());
break;
case Movie:
renameModel.useFormatter(Movie.class, new ExpressionFormatter(dialog.getFormat().getExpression(), MovieFormat.NameYear, Movie.class));
persistentMovieFormat.setValue(dialog.getFormat().getExpression());
break;
case Music:
renameModel.useFormatter(AudioTrack.class, new ExpressionFormatter(dialog.getFormat().getExpression(), new AudioTrackFormat(), AudioTrack.class));
persistentMusicFormat.setValue(dialog.getFormat().getExpression());
break;
}
if (lockOnBinding == null) {
persistentLastFormatState.setValue(dialog.getMode().name());
}
}
}
protected final Action clearFilesAction = new AbstractAction("Clear", ResourceManager.getIcon("action.clear")) { protected final Action clearFilesAction = new AbstractAction("Clear", ResourceManager.getIcon("action.clear")) {
@Override @Override