Rewrite ListPanel for parallel editing and testing of format expressions

This commit is contained in:
Reinhard Pointner 2016-03-20 18:33:31 +00:00
parent 56e13f072f
commit ef71e2fff8
17 changed files with 474 additions and 253 deletions

View File

@ -202,7 +202,7 @@ public class CmdlineOperations implements CmdlineInterface {
}
// fetch episode data
Collection<Episode> episodes = fetchEpisodeSet(db, seriesNames, sortOrder, locale, strict);
List<Episode> episodes = fetchEpisodeSet(db, seriesNames, sortOrder, locale, strict);
if (episodes.size() == 0) {
log.warning("Failed to fetch episode data: " + seriesNames);
continue;
@ -277,7 +277,7 @@ public class CmdlineOperations implements CmdlineInterface {
return validMatches;
}
private Set<Episode> fetchEpisodeSet(final EpisodeListProvider db, final Collection<String> names, final SortOrder sortOrder, final Locale locale, final boolean strict) throws Exception {
private List<Episode> fetchEpisodeSet(final EpisodeListProvider db, final Collection<String> names, final SortOrder sortOrder, final Locale locale, final boolean strict) throws Exception {
Set<SearchResult> shows = new LinkedHashSet<SearchResult>();
Set<Episode> episodes = new LinkedHashSet<Episode>();
@ -304,7 +304,7 @@ public class CmdlineOperations implements CmdlineInterface {
}
}
return episodes;
return new ArrayList<Episode>(episodes);
}
public List<File> renameMovie(Collection<File> files, RenameAction renameAction, ConflictAction conflictAction, File outputDir, ExpressionFormat format, MovieIdentificationService service, String query, ExpressionFilter filter, Locale locale, boolean strict) throws Exception {
@ -434,7 +434,7 @@ public class CmdlineOperations implements CmdlineInterface {
// unknown hash, try via imdb id from nfo file
if (movie == null) {
log.fine(format("Auto-detect movie from context: [%s]", file));
Collection<Movie> options = detectMovie(file, service, locale, strict);
List<Movie> options = detectMovie(file, service, locale, strict);
// apply filter if defined
options = applyExpressionFilter(options, filter);
@ -871,13 +871,13 @@ public class CmdlineOperations implements CmdlineInterface {
return destination;
}
private <T> List<T> applyExpressionFilter(Collection<T> input, ExpressionFilter filter) throws Exception {
private <T> List<T> applyExpressionFilter(List<T> input, ExpressionFilter filter) throws Exception {
if (filter == null) {
return new ArrayList<T>(input);
}
log.fine(format("Apply Filter: {%s}", filter.getExpression()));
Map<File, Object> context = new EntryList<File, Object>(null, input);
Map<File, T> context = new EntryList<File, T>(null, input);
List<T> output = new ArrayList<T>(input.size());
for (T it : input) {
if (filter.matches(new MediaBindingBean(it, null, context))) {

View File

@ -67,7 +67,7 @@ public class MediaBindingBean {
private final Object infoObject;
private final File mediaFile;
private final Map<File, Object> context;
private final Map<File, ?> context;
private MediaInfo mediaInfo;
private Object metaInfo;
@ -76,7 +76,7 @@ public class MediaBindingBean {
this(infoObject, mediaFile, singletonMap(mediaFile, infoObject));
}
public MediaBindingBean(Object infoObject, File mediaFile, Map<File, Object> context) {
public MediaBindingBean(Object infoObject, File mediaFile, Map<File, ?> context) {
this.infoObject = infoObject;
this.mediaFile = mediaFile;
this.context = context;
@ -903,14 +903,14 @@ public class MediaBindingBean {
@Define("model")
public List<AssociativeScriptObject> getModel() {
List<AssociativeScriptObject> result = new ArrayList<AssociativeScriptObject>();
for (Entry<File, Object> it : context.entrySet()) {
for (Entry<File, ?> it : context.entrySet()) {
result.add(createBindingObject(it.getKey(), it.getValue(), context));
}
return result;
}
@Define("json")
public String getInfoObjectDump() throws Exception {
public String getInfoObjectDump() {
return JsonWriter.objectToJson(infoObject);
}
@ -924,7 +924,7 @@ public class MediaBindingBean {
} else if (SUBTITLE_FILES.accept(getMediaFile()) || ((infoObject instanceof Episode || infoObject instanceof Movie) && !VIDEO_FILES.accept(getMediaFile()))) {
// prefer equal match from current context if possible
if (context != null) {
for (Entry<File, Object> it : context.entrySet()) {
for (Entry<File, ?> it : context.entrySet()) {
if (infoObject.equals(it.getValue()) && VIDEO_FILES.accept(it.getKey())) {
return it.getKey();
}
@ -994,7 +994,7 @@ public class MediaBindingBean {
return undefined(String.format("%s[%d][%s]", streamKind, streamNumber, join(keys, ", ")));
}
private AssociativeScriptObject createBindingObject(File file, Object info, Map<File, Object> context) {
private AssociativeScriptObject createBindingObject(File file, Object info, Map<File, ?> context) {
MediaBindingBean mediaBindingBean = new MediaBindingBean(info, file, context) {
@Override
@Define(undefined)

View File

@ -1,6 +1,7 @@
package net.filebot.torrent;
import static java.nio.charset.StandardCharsets.*;
import static java.util.Collections.*;
import java.io.BufferedInputStream;
import java.io.File;
@ -9,11 +10,13 @@ import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.filebot.vfs.FileInfo;
import net.filebot.vfs.SimpleFileInfo;
public class Torrent {
private String name;
@ -24,7 +27,7 @@ public class Torrent {
private Long creationDate;
private Long pieceLength;
private List<Entry> files;
private List<FileInfo> files;
private boolean singleFileTorrent;
protected Torrent() {
@ -59,7 +62,7 @@ public class Torrent {
// torrent contains multiple entries
singleFileTorrent = false;
List<Entry> entries = new ArrayList<Entry>();
List<FileInfo> entries = new ArrayList<FileInfo>();
for (Object fileMapObject : (List<?>) infoMap.get("files")) {
Map<?, ?> fileMap = (Map<?, ?>) fileMapObject;
@ -81,17 +84,17 @@ public class Torrent {
Long length = decodeLong(fileMap.get("length"));
entries.add(new Entry(path.toString(), length));
entries.add(new SimpleFileInfo(path.toString(), length));
}
files = Collections.unmodifiableList(entries);
files = unmodifiableList(entries);
} else {
// single file torrent
singleFileTorrent = true;
Long length = decodeLong(infoMap.get("length"));
files = Collections.singletonList(new Entry(name, length));
files = singletonList(new SimpleFileInfo(name, length));
}
}
@ -139,7 +142,7 @@ public class Torrent {
return encoding;
}
public List<Entry> getFiles() {
public List<FileInfo> getFiles() {
return files;
}
@ -155,35 +158,4 @@ public class Torrent {
return singleFileTorrent;
}
public static class Entry {
private final String path;
private final long length;
public Entry(String path, long length) {
this.path = path;
this.length = length;
}
public String getPath() {
return path;
}
public String getName() {
// the last element in the path is the filename
// torrents don't contain directory entries, so there is always a non-empty name
return path.substring(path.lastIndexOf("/") + 1);
}
public long getLength() {
return length;
}
@Override
public String toString() {
return getPath();
}
}
}

View File

@ -1,35 +1,39 @@
package net.filebot.ui;
import java.awt.Cursor;
import java.io.PrintWriter;
import net.filebot.ui.transfer.TextFileExportHandler;
public class FileBotListExportHandler<T> extends TextFileExportHandler {
public class FileBotListExportHandler extends TextFileExportHandler {
protected final FileBotList<T> list;
protected final FileBotList<?> list;
public FileBotListExportHandler(FileBotList<?> list) {
public FileBotListExportHandler(FileBotList<T> list) {
this.list = list;
}
@Override
public boolean canExport() {
return list.getModel().size() > 0;
}
@Override
public void export(PrintWriter out) {
for (Object entry : list.getModel()) {
out.println(entry);
try {
list.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
for (T item : list.getModel()) {
export(item, out);
}
} finally {
list.setCursor(Cursor.getDefaultCursor());
}
}
public void export(T item, PrintWriter out) {
out.println(item);
}
@Override
public String getDefaultFileName() {

View File

@ -278,7 +278,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListProvider, E
}
protected static class EpisodeListExportHandler extends FileBotListExportHandler implements ClipboardHandler {
protected static class EpisodeListExportHandler extends FileBotListExportHandler<Episode> implements ClipboardHandler {
public EpisodeListExportHandler(FileBotList<Episode> list) {
super(list);

View File

@ -1,25 +1,60 @@
package net.filebot.ui.list;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static java.util.stream.Collectors.*;
import static net.filebot.MediaTypes.*;
import static net.filebot.ui.transfer.FileTransferable.*;
import static net.filebot.util.FileUtilities.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import net.filebot.torrent.Torrent;
import net.filebot.ui.FileBotList;
import net.filebot.ui.transfer.ArrayTransferable;
import net.filebot.ui.transfer.FileTransferablePolicy;
import net.filebot.util.FileUtilities;
import net.filebot.util.FileUtilities.ExtensionFileFilter;
import net.filebot.web.Episode;
class FileListTransferablePolicy extends FileTransferablePolicy {
private FileBotList<? super String> list;
private static final DataFlavor episodeArrayFlavor = ArrayTransferable.flavor(Episode.class);
public FileListTransferablePolicy(FileBotList<? super String> list) {
this.list = list;
private Consumer<String> title;
private Consumer<String> format;
private Consumer<List<?>> model;
public FileListTransferablePolicy(Consumer<String> title, Consumer<String> format, Consumer<List<?>> model) {
this.title = title;
this.format = format;
this.model = model;
}
@Override
public boolean accept(Transferable tr) throws Exception {
return hasFileListFlavor(tr) || tr.isDataFlavorSupported(episodeArrayFlavor);
}
@Override
public void handleTransferable(Transferable tr, TransferAction action) throws Exception {
// handle episode data
if (tr.isDataFlavorSupported(episodeArrayFlavor)) {
Episode[] episodes = (Episode[]) tr.getTransferData((episodeArrayFlavor));
if (episodes.length > 0) {
format.accept(ListPanel.DEFAULT_EPISODE_FORMAT);
title.accept(episodes[0].getSeriesName());
model.accept(asList(episodes));
}
return;
}
// handle files
super.handleTransferable(tr, action);
}
@Override
@ -29,48 +64,44 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
@Override
protected void clear() {
list.getModel().clear();
format.accept("");
title.accept("");
model.accept(emptyList());
}
@Override
protected void load(List<File> files, TransferAction action) throws IOException {
// set title based on parent folder of first file
list.setTitle(FileUtilities.getFolderName(files.get(0).getParentFile()));
// clear selection
list.getListComponent().clearSelection();
title.accept(getFolderName(files.get(0).getParentFile()));
if (containsOnly(files, TORRENT_FILES)) {
loadTorrents(files);
} else {
// if only one folder was dropped, use its name as title
if (files.size() == 1 && files.get(0).isDirectory()) {
list.setTitle(FileUtilities.getFolderName(files.get(0)));
title.accept(getFolderName(files.get(0)));
}
// load all files from the given folders recursively up do a depth of 32
for (File file : listFiles(files)) {
list.getModel().add(FileUtilities.getName(file));
}
format.accept(ListPanel.DEFAULT_FILE_FORMAT);
model.accept(listFiles(files));
}
}
private void loadTorrents(List<File> files) throws IOException {
List<Torrent> torrents = new ArrayList<Torrent>(files.size());
for (File file : files) {
torrents.add(new Torrent(file));
}
if (torrents.size() == 1) {
list.setTitle(FileUtilities.getNameWithoutExtension(torrents.get(0).getName()));
// set title
if (torrents.size() > 0) {
title.accept(getNameWithoutExtension(torrents.get(0).getName()));
}
for (Torrent torrent : torrents) {
for (Torrent.Entry entry : torrent.getFiles()) {
list.getModel().add(FileUtilities.getNameWithoutExtension(entry.getName()));
}
}
// add torrent entries
format.accept(ListPanel.DEFAULT_FILE_FORMAT);
model.accept(torrents.stream().flatMap(t -> t.getFiles().stream()).collect(toList()));
}
@Override

View File

@ -0,0 +1,50 @@
package net.filebot.ui.list;
import static net.filebot.util.FileUtilities.*;
import java.io.File;
import java.util.List;
import java.util.Map;
import net.filebot.format.Define;
import net.filebot.format.MediaBindingBean;
import net.filebot.util.EntryList;
import net.filebot.util.FunctionList;
public class IndexedBindingBean extends MediaBindingBean {
private int i;
private int from;
private int to;
public IndexedBindingBean(Object object, int i, int from, int to, List<?> context) {
super(object, getMediaFile(object), getContext(context));
this.i = i;
this.from = from;
this.to = to;
}
@Define("i")
public Integer getModelIndex() {
return i;
}
@Define("from")
public Integer getFromIndex() {
return from;
}
@Define("to")
public Integer getToIndex() {
return to;
}
private static File getMediaFile(Object object) {
return object instanceof File ? (File) object : new File(object.toString());
}
private static Map<File, Object> getContext(List<?> context) {
return new EntryList<File, Object>(new FunctionList<Object, File>((List<Object>) context, IndexedBindingBean::getMediaFile), context);
}
}

View File

@ -0,0 +1,46 @@
package net.filebot.ui.list;
import net.filebot.format.ExpressionFormat;
public class ListItem {
private IndexedBindingBean bindings;
private ExpressionFormat format;
private String value;
public ListItem(IndexedBindingBean bindings, ExpressionFormat format) {
this.bindings = bindings;
this.format = format;
this.value = format != null ? null : bindings.getInfoObject().toString();
}
public IndexedBindingBean getBindings() {
return bindings;
}
public Object getObject() {
return bindings.getInfoObject();
}
public ExpressionFormat getFormat() {
return format;
}
public String getFormattedValue() {
if (value == null) {
value = format.format(bindings);
if (value == null && format.caughtScriptException() != null) {
value = format.caughtScriptException().getMessage();
}
}
return value;
}
@Override
public String toString() {
return getObject().toString();
}
}

View File

@ -1,34 +1,38 @@
package net.filebot.ui.list;
import static java.awt.Font.*;
import static java.lang.Math.*;
import static net.filebot.Logging.*;
import static net.filebot.media.MediaDetection.*;
import static java.util.stream.Collectors.*;
import static javax.swing.BorderFactory.*;
import static net.filebot.util.ui.SwingUI.*;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.io.PrintWriter;
import java.util.List;
import java.util.logging.Level;
import java.util.ListIterator;
import java.util.stream.IntStream;
import javax.script.Bindings;
import javax.script.SimpleBindings;
import javax.script.ScriptException;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JSpinner.NumberEditor;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rtextarea.RTextScrollPane;
import com.google.common.eventbus.Subscribe;
@ -40,42 +44,92 @@ import net.filebot.ui.transfer.LoadAction;
import net.filebot.ui.transfer.SaveAction;
import net.filebot.ui.transfer.TransferablePolicy;
import net.filebot.ui.transfer.TransferablePolicy.TransferAction;
import net.filebot.util.ExceptionUtilities;
import net.filebot.util.ui.DefaultFancyListCellRenderer;
import net.filebot.util.ui.LazyDocumentListener;
import net.filebot.util.ui.PrototypeCellValueUpdater;
import net.miginfocom.swing.MigLayout;
public class ListPanel extends JComponent {
private FileBotList<String> list = new FileBotList<String>();
public static final String DEFAULT_SEQUENCE_FORMAT = "Sequence - {i.pad(2)}";
public static final String DEFAULT_FILE_FORMAT = "{fn}";
public static final String DEFAULT_EPISODE_FORMAT = "{n} - {s00e00} - [{airdate.format(/dd MMM YYYY/)}] - {t}";
private JTextField textField = new JTextField("Name - {i}", 30);
private RSyntaxTextArea editor = createEditor();
private SpinnerNumberModel fromSpinnerModel = new SpinnerNumberModel(1, 0, Integer.MAX_VALUE, 1);
private SpinnerNumberModel toSpinnerModel = new SpinnerNumberModel(20, 0, Integer.MAX_VALUE, 1);
private FileBotList<ListItem> list = new FileBotList<ListItem>();
public ListPanel() {
list.setTitle("Title");
textField.setFont(new Font(MONOSPACED, PLAIN, 11));
list.setTransferablePolicy(new FileListTransferablePolicy(list));
list.setExportHandler(new FileBotListExportHandler(list));
// need a fixed cell size for high performance scrolling
list.getListComponent().setFixedCellHeight(28);
list.getListComponent().getModel().addListDataListener(new PrototypeCellValueUpdater(list.getListComponent(), ""));
list.getRemoveAction().setEnabled(true);
list.setTransferablePolicy(new FileListTransferablePolicy(list::setTitle, editor::setText, this::createItemSequence));
list.setExportHandler(new FileBotListExportHandler<ListItem>(list) {
@Override
public void export(ListItem item, PrintWriter out) {
out.println(item.getFormattedValue());
}
});
list.getListComponent().setCellRenderer(new DefaultFancyListCellRenderer() {
@Override
protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
ListItem item = (ListItem) value;
String text = item.getFormattedValue(); // format just-in-time
if (text.isEmpty()) {
if (item.getFormat() != null && item.getFormat().caughtScriptException() != null) {
setText(item.getFormat().caughtScriptException().getMessage());
} else {
setText("Expression yields no results for value " + item.getObject());
}
setIcon(ResourceManager.getIcon("status.warning"));
} else {
setText(text);
setIcon(null);
}
}
});
JSpinner fromSpinner = new JSpinner(fromSpinnerModel);
JSpinner toSpinner = new JSpinner(toSpinnerModel);
fromSpinner.setEditor(new NumberEditor(fromSpinner, "#"));
toSpinner.setEditor(new NumberEditor(toSpinner, "#"));
RTextScrollPane editorScrollPane = new RTextScrollPane(editor, false);
editorScrollPane.setLineNumbersEnabled(false);
editorScrollPane.setFoldIndicatorEnabled(false);
editorScrollPane.setIconRowHeaderEnabled(false);
editorScrollPane.setVerticalScrollBarPolicy(RTextScrollPane.VERTICAL_SCROLLBAR_NEVER);
editorScrollPane.setHorizontalScrollBarPolicy(RTextScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
editorScrollPane.setBackground(editor.getBackground());
editorScrollPane.setViewportBorder(createEmptyBorder(2, 2, 2, 2));
editorScrollPane.setOpaque(true);
editorScrollPane.setBorder(new JTextField().getBorder());
setLayout(new MigLayout("nogrid, fill, insets dialog", "align center", "[pref!, center][fill]"));
add(new JLabel("Pattern:"), "gapbefore indent");
add(textField, "gap related, wmin 2cm, sizegroupy editor");
JLabel patternLabel = new JLabel("Pattern:");
add(patternLabel, "gapbefore indent");
add(editorScrollPane, "gap related, growx, wmin 2cm, h pref!, sizegroupy editor");
add(new JLabel("From:"), "gap 5mm");
add(fromSpinner, "gap related, wmax 14mm, sizegroup spinner, sizegroupy editor");
add(fromSpinner, "gap related, wmax 15mm, sizegroup spinner, sizegroupy editor");
add(new JLabel("To:"), "gap 5mm");
add(toSpinner, "gap related, wmax 14mm, sizegroup spinner, sizegroupy editor");
add(newButton("Create", ResourceManager.getIcon("action.export"), this::create), "gap 7mm, gapafter indent, wrap paragraph");
add(toSpinner, "gap related, wmax 15mm, sizegroup spinner, sizegroupy editor");
add(newButton("Sequence", ResourceManager.getIcon("action.export"), evt -> createItemSequence()), "gap 7mm, gapafter indent, wrap paragraph");
add(list, "grow");
@ -86,57 +140,92 @@ public class ListPanel extends JComponent {
list.add(buttonPanel, BorderLayout.SOUTH);
installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), newAction("Create", this::create));
// initialize with default values
SwingUtilities.invokeLater(() -> {
if (list.getModel().isEmpty()) {
createItemSequence();
}
});
}
public void create(ActionEvent evt) {
// clear selection
list.getListComponent().clearSelection();
private RSyntaxTextArea createEditor() {
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("\\R", ""), 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));
// update format on change
editor.getDocument().addDocumentListener(new LazyDocumentListener(20) {
private Color valid = editor.getForeground();
private Color invalid = Color.red;
@Override
public void update(DocumentEvent evt) {
try {
String expression = editor.getText().trim();
setFormat(expression.isEmpty() ? null : new ExpressionFormat(expression));
editor.setForeground(valid);
} catch (ScriptException e) {
editor.setForeground(invalid);
}
}
});
return editor;
}
private ExpressionFormat format;
public ListItem createItem(Object object, int i, int from, int to, List<?> context) {
return new ListItem(new IndexedBindingBean(object, i, from, to, context), format);
}
public void setFormat(ExpressionFormat format) {
this.format = format;
// update items
for (ListIterator<ListItem> itr = list.getModel().listIterator(); itr.hasNext();) {
itr.set(new ListItem(itr.next().getBindings(), format));
}
}
public void createItemSequence(List<?> objects) {
List<ListItem> items = IntStream.range(0, objects.size()).mapToObj(i -> createItem(objects.get(i), i, 0, objects.size(), objects)).collect(toList());
list.getListComponent().clearSelection();
list.getModel().clear();
list.getModel().addAll(items);
}
public void createItemSequence() {
int from = fromSpinnerModel.getNumber().intValue();
int to = toSpinnerModel.getNumber().intValue();
try {
ExpressionFormat format = new ExpressionFormat(textField.getText());
List<Integer> context = IntStream.rangeClosed(from, to).boxed().collect(toList());
List<ListItem> items = context.stream().map(it -> createItem(it, it.intValue(), from, to, context)).collect(toList());
// pad episode numbers with zeros (e.g. %02d) so all numbers have the same number of digits
NumberFormat numberFormat = NumberFormat.getIntegerInstance();
numberFormat.setMinimumIntegerDigits(max(2, Integer.toString(max(from, to)).length()));
numberFormat.setGroupingUsed(false);
List<String> names = new ArrayList<String>();
int min = min(from, to);
int max = max(from, to);
for (int i = min; i <= max; i++) {
Bindings bindings = new SimpleBindings();
// strings
bindings.put("i", numberFormat.format(i));
// numbers
bindings.put("index", i);
bindings.put("from", from);
bindings.put("to", to);
names.add(format.format(bindings));
}
if (signum(to - from) < 0) {
Collections.reverse(names);
}
// try to match title from the first five names
Collection<String> title = getSeriesNameMatcher(true).matchAll((names.size() < 5 ? names : names.subList(0, 4)).toArray(new String[0]));
list.setTitle(title.isEmpty() ? "List" : title.iterator().next());
list.getModel().clear();
list.getModel().addAll(names);
} catch (Exception e) {
log.log(Level.WARNING, ExceptionUtilities.getMessage(e), e);
}
editor.setText(DEFAULT_SEQUENCE_FORMAT);
list.setTitle("Sequence");
list.getListComponent().clearSelection();
list.getModel().clear();
list.getModel().addAll(items);
}
@Subscribe

View File

@ -50,9 +50,9 @@ import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JTextField;
import javax.swing.SwingWorker;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
@ -206,11 +206,11 @@ public class FormatDialog extends JDialog {
editorScrollPane.setVerticalScrollBarPolicy(RTextScrollPane.VERTICAL_SCROLLBAR_NEVER);
editorScrollPane.setHorizontalScrollBarPolicy(RTextScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
editorScrollPane.setViewportBorder(new EmptyBorder(7, 0, 7, 0));
editorScrollPane.setBackground(editor.getBackground());
editorScrollPane.setViewportBorder(createEmptyBorder(7, 2, 7, 2));
editorScrollPane.setOpaque(true);
editorScrollPane.setBorder(new JTextField().getBorder());
content.add(editorScrollPane, "w 120px:min(pref, 420px), h 40px!, growx, wrap 4px, id editor");
content.add(editorScrollPane, "w 120px:min(pref, 420px), h pref!, growx, wrap 4px, id editor");
content.add(createImageButton(changeSampleAction), "sg action, w 25!, h 19!, pos n editor.y2+2 editor.x2 n");
content.add(createImageButton(selectFolderAction), "sg action, w 25!, h 19!, pos n editor.y2+2 editor.x2-(27*1) n");
content.add(createImageButton(showRecentAction), "sg action, w 25!, h 19!, pos n editor.y2+2 editor.x2-(27*2) n");

View File

@ -145,10 +145,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
protected void loadTorrentFiles(List<File> files, List<Object> values) throws IOException {
for (File file : files) {
Torrent torrent = new Torrent(file);
for (Torrent.Entry entry : torrent.getFiles()) {
values.add(new SimpleFileInfo(entry.getPath(), entry.getLength()));
}
values.addAll(torrent.getFiles());
}
}

View File

@ -11,16 +11,14 @@ import java.awt.event.MouseEvent;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import ca.odell.glazedlists.EventList;
import net.filebot.ResourceManager;
import net.filebot.ui.FileBotList;
import net.filebot.ui.transfer.LoadAction;
import net.filebot.util.ui.ActionPopup;
import net.filebot.util.ui.PrototypeCellValueUpdater;
import net.miginfocom.swing.MigLayout;
class RenameList<E> extends FileBotList<E> {
@ -36,43 +34,7 @@ class RenameList<E> extends FileBotList<E> {
// need a fixed cell size for high performance scrolling
list.setFixedCellHeight(28);
list.getModel().addListDataListener(new ListDataListener() {
private int longestItemLength = -1;
@Override
public void intervalRemoved(ListDataEvent evt) {
// reset prototype value
ListModel<?> m = (ListModel<?>) evt.getSource();
if (m.getSize() == 0) {
longestItemLength = -1;
list.setPrototypeCellValue(null);
}
}
@Override
public void intervalAdded(ListDataEvent evt) {
contentsChanged(evt);
}
@Override
public void contentsChanged(ListDataEvent evt) {
ListModel<?> m = (ListModel<?>) evt.getSource();
for (int i = evt.getIndex0(); i <= evt.getIndex1() && i < m.getSize(); i++) {
Object item = m.getElementAt(i);
int itemLength = item.toString().length();
if (itemLength > longestItemLength) {
// cell values will not be updated if the prototype object remains the same (even if the object has changed) so we need to reset it
if (item == list.getPrototypeCellValue()) {
list.setPrototypeCellValue("");
}
longestItemLength = itemLength;
list.setPrototypeCellValue(item);
}
}
}
});
list.getModel().addListDataListener(new PrototypeCellValueUpdater(list, ""));
list.addMouseListener(dndReorderMouseAdapter);
list.addMouseMotionListener(dndReorderMouseAdapter);

View File

@ -1,7 +1,6 @@
package net.filebot.ui.transfer;
import java.awt.datatransfer.Transferable;
import java.io.File;
import java.io.IOException;
@ -11,38 +10,28 @@ import java.io.StringWriter;
import javax.swing.JComponent;
import javax.swing.TransferHandler;
public abstract class TextFileExportHandler implements TransferableExportHandler, FileExportHandler {
@Override
public abstract boolean canExport();
public abstract void export(PrintWriter out);
@Override
public abstract String getDefaultFileName();
@Override
public void export(File file) throws IOException {
PrintWriter out = new PrintWriter(file, "UTF-8");
try {
try (PrintWriter out = new PrintWriter(file, "UTF-8")) {
export(out);
} finally {
out.close();
}
}
@Override
public int getSourceActions(JComponent c) {
return canExport() ? TransferHandler.COPY_OR_MOVE : TransferHandler.NONE;
}
@Override
public Transferable createTransferable(JComponent c) {
// get transfer data
@ -52,7 +41,6 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
return new TextFileTransferable(getDefaultFileName(), buffer.toString());
}
@Override
public void exportDone(JComponent source, Transferable data, int action) {

View File

@ -2,27 +2,20 @@ package net.filebot.util;
import static java.util.Collections.*;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class EntryList<K, V> extends AbstractMap<K, V> {
private final List<Entry<K, V>> entryList = new ArrayList<Entry<K, V>>();
private List<? extends K> keys;
private List<? extends V> values;
public EntryList(Iterable<? extends K> keys, Iterable<? extends V> values) {
Iterator<? extends K> keySeq = keys != null ? keys.iterator() : emptyIterator();
Iterator<? extends V> valueSeq = values != null ? values.iterator() : emptyIterator();
while (keySeq.hasNext() || valueSeq.hasNext()) {
K key = keySeq.hasNext() ? keySeq.next() : null;
V value = valueSeq.hasNext() ? valueSeq.next() : null;
entryList.add(new SimpleImmutableEntry<K, V>(key, value));
}
public EntryList(List<? extends K> keys, List<? extends V> values) {
this.keys = keys != null ? keys : emptyList();
this.values = values != null ? values : emptyList();
}
@Override
@ -31,35 +24,56 @@ public class EntryList<K, V> extends AbstractMap<K, V> {
@Override
public Iterator<Entry<K, V>> iterator() {
return entryList.iterator();
return new Iterator<Entry<K, V>>() {
private Iterator<? extends K> keySeq = keys.iterator();
private Iterator<? extends V> valueSeq = values.iterator();
@Override
public boolean hasNext() {
return keySeq.hasNext() || valueSeq.hasNext();
}
@Override
public Entry<K, V> next() {
K key = keySeq.hasNext() ? keySeq.next() : null;
V value = valueSeq.hasNext() ? valueSeq.next() : null;
return new SimpleImmutableEntry<K, V>(key, value);
}
};
}
@Override
public int size() {
return entryList.size();
return keys.size();
}
};
}
@Override
public Set<K> keySet() {
return new AbstractSet<K>() {
@Override
public Iterator<K> iterator() {
return (Iterator<K>) keys.iterator();
}
@Override
public int size() {
return keys.size();
}
};
}
@Override
public List<V> values() {
return new AbstractList<V>() {
@Override
public V get(int index) {
return entryList.get(index).getValue();
}
@Override
public int size() {
return entryList.size();
}
};
return (List<V>) values;
}
@Override
public int size() {
return entryList.size();
return Math.max(keys.size(), values.size());
}
}

View File

@ -0,0 +1,27 @@
package net.filebot.util;
import java.util.AbstractList;
import java.util.List;
import java.util.function.Function;
public class FunctionList<S, E> extends AbstractList<E> {
private List<S> source;
private Function<S, E> function;
public FunctionList(List<S> source, Function<S, E> function) {
this.source = source;
this.function = function;
}
@Override
public E get(int index) {
return function.apply(source.get(index));
}
@Override
public int size() {
return source.size();
}
}

View File

@ -1,7 +1,6 @@
package net.filebot.util.ui;
import java.awt.Color;
import java.awt.Insets;
@ -10,63 +9,52 @@ import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JList;
public class DefaultFancyListCellRenderer extends AbstractFancyListCellRenderer {
private final JLabel label = new DefaultListCellRenderer();
public DefaultFancyListCellRenderer() {
add(label);
}
public DefaultFancyListCellRenderer(int padding) {
super(new Insets(padding, padding, padding, padding));
add(label);
}
public DefaultFancyListCellRenderer(Insets padding) {
super(padding);
add(label);
}
protected DefaultFancyListCellRenderer(int padding, int margin, Color selectedBorderColor) {
super(new Insets(padding, padding, padding, padding), new Insets(margin, margin, margin, margin), selectedBorderColor);
add(label);
}
@Override
protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
label.setOpaque(false);
setText(String.valueOf(value));
label.setText(String.valueOf(value));
}
public void setIcon(Icon icon) {
label.setIcon(icon);
}
public void setText(String text) {
label.setText(text);
}
public void setHorizontalTextPosition(int textPosition) {
label.setHorizontalTextPosition(textPosition);
}
public void setVerticalTextPosition(int textPosition) {
label.setVerticalTextPosition(textPosition);
}
@Override
public void setForeground(Color fg) {
super.setForeground(fg);

View File

@ -0,0 +1,53 @@
package net.filebot.util.ui;
import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
public class PrototypeCellValueUpdater<T> implements ListDataListener {
private int longestItemLength = -1;
private JList<T> list;
private T defaultValue;
public PrototypeCellValueUpdater(JList<T> list, T defaultValue) {
this.list = list;
this.defaultValue = defaultValue;
}
@Override
public void intervalRemoved(ListDataEvent evt) {
// reset prototype value
ListModel<T> m = (ListModel<T>) evt.getSource();
if (m.getSize() == 0) {
longestItemLength = -1;
list.setPrototypeCellValue(null);
}
}
@Override
public void intervalAdded(ListDataEvent evt) {
contentsChanged(evt);
}
@Override
public void contentsChanged(ListDataEvent evt) {
ListModel<T> m = (ListModel<T>) evt.getSource();
for (int i = evt.getIndex0(); i <= evt.getIndex1() && i < m.getSize(); i++) {
T item = m.getElementAt(i);
int itemLength = item.toString().length();
if (itemLength > longestItemLength) {
// cell values will not be updated if the prototype object remains the same (even if the object has changed) so we need to reset it
if (item == list.getPrototypeCellValue()) {
list.setPrototypeCellValue(defaultValue);
}
longestItemLength = itemLength;
list.setPrototypeCellValue(item);
}
}
}
}