mirror of
https://github.com/mitb-archive/filebot
synced 2024-12-24 00:38:52 -05:00
Rewrite ListPanel for parallel editing and testing of format expressions
This commit is contained in:
parent
56e13f072f
commit
ef71e2fff8
@ -202,7 +202,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetch episode data
|
// 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) {
|
if (episodes.size() == 0) {
|
||||||
log.warning("Failed to fetch episode data: " + seriesNames);
|
log.warning("Failed to fetch episode data: " + seriesNames);
|
||||||
continue;
|
continue;
|
||||||
@ -277,7 +277,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||||||
return validMatches;
|
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<SearchResult> shows = new LinkedHashSet<SearchResult>();
|
||||||
Set<Episode> episodes = new LinkedHashSet<Episode>();
|
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 {
|
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
|
// unknown hash, try via imdb id from nfo file
|
||||||
if (movie == null) {
|
if (movie == null) {
|
||||||
log.fine(format("Auto-detect movie from context: [%s]", file));
|
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
|
// apply filter if defined
|
||||||
options = applyExpressionFilter(options, filter);
|
options = applyExpressionFilter(options, filter);
|
||||||
@ -871,13 +871,13 @@ public class CmdlineOperations implements CmdlineInterface {
|
|||||||
return destination;
|
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) {
|
if (filter == null) {
|
||||||
return new ArrayList<T>(input);
|
return new ArrayList<T>(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
log.fine(format("Apply Filter: {%s}", filter.getExpression()));
|
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());
|
List<T> output = new ArrayList<T>(input.size());
|
||||||
for (T it : input) {
|
for (T it : input) {
|
||||||
if (filter.matches(new MediaBindingBean(it, null, context))) {
|
if (filter.matches(new MediaBindingBean(it, null, context))) {
|
||||||
|
@ -67,7 +67,7 @@ public class MediaBindingBean {
|
|||||||
|
|
||||||
private final Object infoObject;
|
private final Object infoObject;
|
||||||
private final File mediaFile;
|
private final File mediaFile;
|
||||||
private final Map<File, Object> context;
|
private final Map<File, ?> context;
|
||||||
|
|
||||||
private MediaInfo mediaInfo;
|
private MediaInfo mediaInfo;
|
||||||
private Object metaInfo;
|
private Object metaInfo;
|
||||||
@ -76,7 +76,7 @@ public class MediaBindingBean {
|
|||||||
this(infoObject, mediaFile, singletonMap(mediaFile, infoObject));
|
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.infoObject = infoObject;
|
||||||
this.mediaFile = mediaFile;
|
this.mediaFile = mediaFile;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@ -903,14 +903,14 @@ public class MediaBindingBean {
|
|||||||
@Define("model")
|
@Define("model")
|
||||||
public List<AssociativeScriptObject> getModel() {
|
public List<AssociativeScriptObject> getModel() {
|
||||||
List<AssociativeScriptObject> result = new ArrayList<AssociativeScriptObject>();
|
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));
|
result.add(createBindingObject(it.getKey(), it.getValue(), context));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Define("json")
|
@Define("json")
|
||||||
public String getInfoObjectDump() throws Exception {
|
public String getInfoObjectDump() {
|
||||||
return JsonWriter.objectToJson(infoObject);
|
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()))) {
|
} else if (SUBTITLE_FILES.accept(getMediaFile()) || ((infoObject instanceof Episode || infoObject instanceof Movie) && !VIDEO_FILES.accept(getMediaFile()))) {
|
||||||
// prefer equal match from current context if possible
|
// prefer equal match from current context if possible
|
||||||
if (context != null) {
|
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())) {
|
if (infoObject.equals(it.getValue()) && VIDEO_FILES.accept(it.getKey())) {
|
||||||
return it.getKey();
|
return it.getKey();
|
||||||
}
|
}
|
||||||
@ -994,7 +994,7 @@ public class MediaBindingBean {
|
|||||||
return undefined(String.format("%s[%d][%s]", streamKind, streamNumber, join(keys, ", ")));
|
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) {
|
MediaBindingBean mediaBindingBean = new MediaBindingBean(info, file, context) {
|
||||||
@Override
|
@Override
|
||||||
@Define(undefined)
|
@Define(undefined)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.filebot.torrent;
|
package net.filebot.torrent;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.*;
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
|
import static java.util.Collections.*;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -9,11 +10,13 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import net.filebot.vfs.FileInfo;
|
||||||
|
import net.filebot.vfs.SimpleFileInfo;
|
||||||
|
|
||||||
public class Torrent {
|
public class Torrent {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
@ -24,7 +27,7 @@ public class Torrent {
|
|||||||
private Long creationDate;
|
private Long creationDate;
|
||||||
private Long pieceLength;
|
private Long pieceLength;
|
||||||
|
|
||||||
private List<Entry> files;
|
private List<FileInfo> files;
|
||||||
private boolean singleFileTorrent;
|
private boolean singleFileTorrent;
|
||||||
|
|
||||||
protected Torrent() {
|
protected Torrent() {
|
||||||
@ -59,7 +62,7 @@ public class Torrent {
|
|||||||
// torrent contains multiple entries
|
// torrent contains multiple entries
|
||||||
singleFileTorrent = false;
|
singleFileTorrent = false;
|
||||||
|
|
||||||
List<Entry> entries = new ArrayList<Entry>();
|
List<FileInfo> entries = new ArrayList<FileInfo>();
|
||||||
|
|
||||||
for (Object fileMapObject : (List<?>) infoMap.get("files")) {
|
for (Object fileMapObject : (List<?>) infoMap.get("files")) {
|
||||||
Map<?, ?> fileMap = (Map<?, ?>) fileMapObject;
|
Map<?, ?> fileMap = (Map<?, ?>) fileMapObject;
|
||||||
@ -81,17 +84,17 @@ public class Torrent {
|
|||||||
|
|
||||||
Long length = decodeLong(fileMap.get("length"));
|
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 {
|
} else {
|
||||||
// single file torrent
|
// single file torrent
|
||||||
singleFileTorrent = true;
|
singleFileTorrent = true;
|
||||||
|
|
||||||
Long length = decodeLong(infoMap.get("length"));
|
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;
|
return encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Entry> getFiles() {
|
public List<FileInfo> getFiles() {
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,35 +158,4 @@ public class Torrent {
|
|||||||
return singleFileTorrent;
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,39 @@
|
|||||||
|
|
||||||
package net.filebot.ui;
|
package net.filebot.ui;
|
||||||
|
|
||||||
|
import java.awt.Cursor;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
import net.filebot.ui.transfer.TextFileExportHandler;
|
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<T> list) {
|
||||||
|
|
||||||
|
|
||||||
public FileBotListExportHandler(FileBotList<?> list) {
|
|
||||||
this.list = list;
|
this.list = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canExport() {
|
public boolean canExport() {
|
||||||
return list.getModel().size() > 0;
|
return list.getModel().size() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void export(PrintWriter out) {
|
public void export(PrintWriter out) {
|
||||||
for (Object entry : list.getModel()) {
|
try {
|
||||||
out.println(entry);
|
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
|
@Override
|
||||||
public String getDefaultFileName() {
|
public String getDefaultFileName() {
|
||||||
|
@ -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) {
|
public EpisodeListExportHandler(FileBotList<Episode> list) {
|
||||||
super(list);
|
super(list);
|
||||||
|
@ -1,25 +1,60 @@
|
|||||||
package net.filebot.ui.list;
|
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.MediaTypes.*;
|
||||||
|
import static net.filebot.ui.transfer.FileTransferable.*;
|
||||||
import static net.filebot.util.FileUtilities.*;
|
import static net.filebot.util.FileUtilities.*;
|
||||||
|
|
||||||
|
import java.awt.datatransfer.DataFlavor;
|
||||||
|
import java.awt.datatransfer.Transferable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import net.filebot.torrent.Torrent;
|
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.ui.transfer.FileTransferablePolicy;
|
||||||
import net.filebot.util.FileUtilities;
|
|
||||||
import net.filebot.util.FileUtilities.ExtensionFileFilter;
|
import net.filebot.util.FileUtilities.ExtensionFileFilter;
|
||||||
|
import net.filebot.web.Episode;
|
||||||
|
|
||||||
class FileListTransferablePolicy extends FileTransferablePolicy {
|
class FileListTransferablePolicy extends FileTransferablePolicy {
|
||||||
|
|
||||||
private FileBotList<? super String> list;
|
private static final DataFlavor episodeArrayFlavor = ArrayTransferable.flavor(Episode.class);
|
||||||
|
|
||||||
public FileListTransferablePolicy(FileBotList<? super String> list) {
|
private Consumer<String> title;
|
||||||
this.list = list;
|
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
|
@Override
|
||||||
@ -29,48 +64,44 @@ class FileListTransferablePolicy extends FileTransferablePolicy {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void clear() {
|
protected void clear() {
|
||||||
list.getModel().clear();
|
format.accept("");
|
||||||
|
title.accept("");
|
||||||
|
model.accept(emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void load(List<File> files, TransferAction action) throws IOException {
|
protected void load(List<File> files, TransferAction action) throws IOException {
|
||||||
// set title based on parent folder of first file
|
// set title based on parent folder of first file
|
||||||
list.setTitle(FileUtilities.getFolderName(files.get(0).getParentFile()));
|
title.accept(getFolderName(files.get(0).getParentFile()));
|
||||||
|
|
||||||
// clear selection
|
|
||||||
list.getListComponent().clearSelection();
|
|
||||||
|
|
||||||
if (containsOnly(files, TORRENT_FILES)) {
|
if (containsOnly(files, TORRENT_FILES)) {
|
||||||
loadTorrents(files);
|
loadTorrents(files);
|
||||||
} else {
|
} else {
|
||||||
// if only one folder was dropped, use its name as title
|
// if only one folder was dropped, use its name as title
|
||||||
if (files.size() == 1 && files.get(0).isDirectory()) {
|
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
|
// load all files from the given folders recursively up do a depth of 32
|
||||||
for (File file : listFiles(files)) {
|
format.accept(ListPanel.DEFAULT_FILE_FORMAT);
|
||||||
list.getModel().add(FileUtilities.getName(file));
|
model.accept(listFiles(files));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadTorrents(List<File> files) throws IOException {
|
private void loadTorrents(List<File> files) throws IOException {
|
||||||
List<Torrent> torrents = new ArrayList<Torrent>(files.size());
|
List<Torrent> torrents = new ArrayList<Torrent>(files.size());
|
||||||
|
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
torrents.add(new Torrent(file));
|
torrents.add(new Torrent(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (torrents.size() == 1) {
|
// set title
|
||||||
list.setTitle(FileUtilities.getNameWithoutExtension(torrents.get(0).getName()));
|
if (torrents.size() > 0) {
|
||||||
|
title.accept(getNameWithoutExtension(torrents.get(0).getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Torrent torrent : torrents) {
|
// add torrent entries
|
||||||
for (Torrent.Entry entry : torrent.getFiles()) {
|
format.accept(ListPanel.DEFAULT_FILE_FORMAT);
|
||||||
list.getModel().add(FileUtilities.getNameWithoutExtension(entry.getName()));
|
model.accept(torrents.stream().flatMap(t -> t.getFiles().stream()).collect(toList()));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
50
source/net/filebot/ui/list/IndexedBindingBean.java
Normal file
50
source/net/filebot/ui/list/IndexedBindingBean.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
source/net/filebot/ui/list/ListItem.java
Normal file
46
source/net/filebot/ui/list/ListItem.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,34 +1,38 @@
|
|||||||
package net.filebot.ui.list;
|
package net.filebot.ui.list;
|
||||||
|
|
||||||
import static java.awt.Font.*;
|
import static java.awt.Font.*;
|
||||||
import static java.lang.Math.*;
|
import static java.util.stream.Collectors.*;
|
||||||
import static net.filebot.Logging.*;
|
import static javax.swing.BorderFactory.*;
|
||||||
import static net.filebot.media.MediaDetection.*;
|
|
||||||
import static net.filebot.util.ui.SwingUI.*;
|
import static net.filebot.util.ui.SwingUI.*;
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.awt.datatransfer.Transferable;
|
import java.awt.datatransfer.Transferable;
|
||||||
import java.awt.event.ActionEvent;
|
import java.io.PrintWriter;
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.text.NumberFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
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.ScriptException;
|
||||||
import javax.script.SimpleBindings;
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JList;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JSpinner;
|
import javax.swing.JSpinner;
|
||||||
import javax.swing.JSpinner.NumberEditor;
|
import javax.swing.JSpinner.NumberEditor;
|
||||||
import javax.swing.JTextField;
|
import javax.swing.JTextField;
|
||||||
import javax.swing.KeyStroke;
|
|
||||||
import javax.swing.SpinnerNumberModel;
|
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;
|
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.SaveAction;
|
||||||
import net.filebot.ui.transfer.TransferablePolicy;
|
import net.filebot.ui.transfer.TransferablePolicy;
|
||||||
import net.filebot.ui.transfer.TransferablePolicy.TransferAction;
|
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;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
public class ListPanel extends JComponent {
|
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 fromSpinnerModel = new SpinnerNumberModel(1, 0, Integer.MAX_VALUE, 1);
|
||||||
private SpinnerNumberModel toSpinnerModel = new SpinnerNumberModel(20, 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() {
|
public ListPanel() {
|
||||||
list.setTitle("Title");
|
list.setTitle("Title");
|
||||||
|
|
||||||
textField.setFont(new Font(MONOSPACED, PLAIN, 11));
|
// need a fixed cell size for high performance scrolling
|
||||||
|
list.getListComponent().setFixedCellHeight(28);
|
||||||
list.setTransferablePolicy(new FileListTransferablePolicy(list));
|
list.getListComponent().getModel().addListDataListener(new PrototypeCellValueUpdater(list.getListComponent(), ""));
|
||||||
list.setExportHandler(new FileBotListExportHandler(list));
|
|
||||||
|
|
||||||
list.getRemoveAction().setEnabled(true);
|
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 fromSpinner = new JSpinner(fromSpinnerModel);
|
||||||
JSpinner toSpinner = new JSpinner(toSpinnerModel);
|
JSpinner toSpinner = new JSpinner(toSpinnerModel);
|
||||||
|
|
||||||
fromSpinner.setEditor(new NumberEditor(fromSpinner, "#"));
|
fromSpinner.setEditor(new NumberEditor(fromSpinner, "#"));
|
||||||
toSpinner.setEditor(new NumberEditor(toSpinner, "#"));
|
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]"));
|
setLayout(new MigLayout("nogrid, fill, insets dialog", "align center", "[pref!, center][fill]"));
|
||||||
|
|
||||||
add(new JLabel("Pattern:"), "gapbefore indent");
|
JLabel patternLabel = new JLabel("Pattern:");
|
||||||
add(textField, "gap related, wmin 2cm, sizegroupy editor");
|
add(patternLabel, "gapbefore indent");
|
||||||
|
add(editorScrollPane, "gap related, growx, wmin 2cm, h pref!, sizegroupy editor");
|
||||||
add(new JLabel("From:"), "gap 5mm");
|
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(new JLabel("To:"), "gap 5mm");
|
||||||
add(toSpinner, "gap related, wmax 14mm, sizegroup spinner, sizegroupy editor");
|
add(toSpinner, "gap related, wmax 15mm, sizegroup spinner, sizegroupy editor");
|
||||||
add(newButton("Create", ResourceManager.getIcon("action.export"), this::create), "gap 7mm, gapafter indent, wrap paragraph");
|
add(newButton("Sequence", ResourceManager.getIcon("action.export"), evt -> createItemSequence()), "gap 7mm, gapafter indent, wrap paragraph");
|
||||||
|
|
||||||
add(list, "grow");
|
add(list, "grow");
|
||||||
|
|
||||||
@ -86,57 +140,92 @@ public class ListPanel extends JComponent {
|
|||||||
|
|
||||||
list.add(buttonPanel, BorderLayout.SOUTH);
|
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) {
|
private RSyntaxTextArea createEditor() {
|
||||||
// clear selection
|
RSyntaxTextArea editor = new RSyntaxTextArea(new RSyntaxDocument(SyntaxConstants.SYNTAX_STYLE_GROOVY) {
|
||||||
list.getListComponent().clearSelection();
|
@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 from = fromSpinnerModel.getNumber().intValue();
|
||||||
int to = toSpinnerModel.getNumber().intValue();
|
int to = toSpinnerModel.getNumber().intValue();
|
||||||
|
|
||||||
try {
|
List<Integer> context = IntStream.rangeClosed(from, to).boxed().collect(toList());
|
||||||
ExpressionFormat format = new ExpressionFormat(textField.getText());
|
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
|
editor.setText(DEFAULT_SEQUENCE_FORMAT);
|
||||||
NumberFormat numberFormat = NumberFormat.getIntegerInstance();
|
list.setTitle("Sequence");
|
||||||
numberFormat.setMinimumIntegerDigits(max(2, Integer.toString(max(from, to)).length()));
|
list.getListComponent().clearSelection();
|
||||||
numberFormat.setGroupingUsed(false);
|
list.getModel().clear();
|
||||||
|
list.getModel().addAll(items);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe
|
||||||
|
@ -50,9 +50,9 @@ import javax.swing.JLabel;
|
|||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
|
import javax.swing.JTextField;
|
||||||
import javax.swing.SwingWorker;
|
import javax.swing.SwingWorker;
|
||||||
import javax.swing.Timer;
|
import javax.swing.Timer;
|
||||||
import javax.swing.border.EmptyBorder;
|
|
||||||
import javax.swing.event.DocumentEvent;
|
import javax.swing.event.DocumentEvent;
|
||||||
import javax.swing.event.PopupMenuEvent;
|
import javax.swing.event.PopupMenuEvent;
|
||||||
import javax.swing.event.PopupMenuListener;
|
import javax.swing.event.PopupMenuListener;
|
||||||
@ -206,11 +206,11 @@ public class FormatDialog extends JDialog {
|
|||||||
|
|
||||||
editorScrollPane.setVerticalScrollBarPolicy(RTextScrollPane.VERTICAL_SCROLLBAR_NEVER);
|
editorScrollPane.setVerticalScrollBarPolicy(RTextScrollPane.VERTICAL_SCROLLBAR_NEVER);
|
||||||
editorScrollPane.setHorizontalScrollBarPolicy(RTextScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
editorScrollPane.setHorizontalScrollBarPolicy(RTextScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
editorScrollPane.setViewportBorder(new EmptyBorder(7, 0, 7, 0));
|
editorScrollPane.setViewportBorder(createEmptyBorder(7, 2, 7, 2));
|
||||||
editorScrollPane.setBackground(editor.getBackground());
|
|
||||||
editorScrollPane.setOpaque(true);
|
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(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(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");
|
content.add(createImageButton(showRecentAction), "sg action, w 25!, h 19!, pos n editor.y2+2 editor.x2-(27*2) n");
|
||||||
|
@ -145,10 +145,7 @@ class NamesListTransferablePolicy extends FileTransferablePolicy {
|
|||||||
protected void loadTorrentFiles(List<File> files, List<Object> values) throws IOException {
|
protected void loadTorrentFiles(List<File> files, List<Object> values) throws IOException {
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
Torrent torrent = new Torrent(file);
|
Torrent torrent = new Torrent(file);
|
||||||
|
values.addAll(torrent.getFiles());
|
||||||
for (Torrent.Entry entry : torrent.getFiles()) {
|
|
||||||
values.add(new SimpleFileInfo(entry.getPath(), entry.getLength()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,16 +11,14 @@ import java.awt.event.MouseEvent;
|
|||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.ListModel;
|
|
||||||
import javax.swing.ListSelectionModel;
|
import javax.swing.ListSelectionModel;
|
||||||
import javax.swing.event.ListDataEvent;
|
|
||||||
import javax.swing.event.ListDataListener;
|
|
||||||
|
|
||||||
import ca.odell.glazedlists.EventList;
|
import ca.odell.glazedlists.EventList;
|
||||||
import net.filebot.ResourceManager;
|
import net.filebot.ResourceManager;
|
||||||
import net.filebot.ui.FileBotList;
|
import net.filebot.ui.FileBotList;
|
||||||
import net.filebot.ui.transfer.LoadAction;
|
import net.filebot.ui.transfer.LoadAction;
|
||||||
import net.filebot.util.ui.ActionPopup;
|
import net.filebot.util.ui.ActionPopup;
|
||||||
|
import net.filebot.util.ui.PrototypeCellValueUpdater;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
class RenameList<E> extends FileBotList<E> {
|
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
|
// need a fixed cell size for high performance scrolling
|
||||||
list.setFixedCellHeight(28);
|
list.setFixedCellHeight(28);
|
||||||
list.getModel().addListDataListener(new ListDataListener() {
|
list.getModel().addListDataListener(new PrototypeCellValueUpdater(list, ""));
|
||||||
|
|
||||||
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.addMouseListener(dndReorderMouseAdapter);
|
list.addMouseListener(dndReorderMouseAdapter);
|
||||||
list.addMouseMotionListener(dndReorderMouseAdapter);
|
list.addMouseMotionListener(dndReorderMouseAdapter);
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
package net.filebot.ui.transfer;
|
package net.filebot.ui.transfer;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.datatransfer.Transferable;
|
import java.awt.datatransfer.Transferable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -11,38 +10,28 @@ import java.io.StringWriter;
|
|||||||
import javax.swing.JComponent;
|
import javax.swing.JComponent;
|
||||||
import javax.swing.TransferHandler;
|
import javax.swing.TransferHandler;
|
||||||
|
|
||||||
|
|
||||||
public abstract class TextFileExportHandler implements TransferableExportHandler, FileExportHandler {
|
public abstract class TextFileExportHandler implements TransferableExportHandler, FileExportHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract boolean canExport();
|
public abstract boolean canExport();
|
||||||
|
|
||||||
|
|
||||||
public abstract void export(PrintWriter out);
|
public abstract void export(PrintWriter out);
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public abstract String getDefaultFileName();
|
public abstract String getDefaultFileName();
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void export(File file) throws IOException {
|
public void export(File file) throws IOException {
|
||||||
PrintWriter out = new PrintWriter(file, "UTF-8");
|
try (PrintWriter out = new PrintWriter(file, "UTF-8")) {
|
||||||
|
|
||||||
try {
|
|
||||||
export(out);
|
export(out);
|
||||||
} finally {
|
|
||||||
out.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSourceActions(JComponent c) {
|
public int getSourceActions(JComponent c) {
|
||||||
return canExport() ? TransferHandler.COPY_OR_MOVE : TransferHandler.NONE;
|
return canExport() ? TransferHandler.COPY_OR_MOVE : TransferHandler.NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Transferable createTransferable(JComponent c) {
|
public Transferable createTransferable(JComponent c) {
|
||||||
// get transfer data
|
// get transfer data
|
||||||
@ -52,7 +41,6 @@ public abstract class TextFileExportHandler implements TransferableExportHandler
|
|||||||
return new TextFileTransferable(getDefaultFileName(), buffer.toString());
|
return new TextFileTransferable(getDefaultFileName(), buffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exportDone(JComponent source, Transferable data, int action) {
|
public void exportDone(JComponent source, Transferable data, int action) {
|
||||||
|
|
||||||
|
@ -2,27 +2,20 @@ package net.filebot.util;
|
|||||||
|
|
||||||
import static java.util.Collections.*;
|
import static java.util.Collections.*;
|
||||||
|
|
||||||
import java.util.AbstractList;
|
|
||||||
import java.util.AbstractMap;
|
import java.util.AbstractMap;
|
||||||
import java.util.AbstractSet;
|
import java.util.AbstractSet;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class EntryList<K, V> extends AbstractMap<K, V> {
|
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) {
|
public EntryList(List<? extends K> keys, List<? extends V> values) {
|
||||||
Iterator<? extends K> keySeq = keys != null ? keys.iterator() : emptyIterator();
|
this.keys = keys != null ? keys : emptyList();
|
||||||
Iterator<? extends V> valueSeq = values != null ? values.iterator() : emptyIterator();
|
this.values = values != null ? values : emptyList();
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -31,35 +24,56 @@ public class EntryList<K, V> extends AbstractMap<K, V> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<Entry<K, V>> iterator() {
|
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
|
@Override
|
||||||
public int size() {
|
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
|
@Override
|
||||||
public List<V> values() {
|
public List<V> values() {
|
||||||
return new AbstractList<V>() {
|
return (List<V>) values;
|
||||||
|
|
||||||
@Override
|
|
||||||
public V get(int index) {
|
|
||||||
return entryList.get(index).getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int size() {
|
|
||||||
return entryList.size();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int size() {
|
public int size() {
|
||||||
return entryList.size();
|
return Math.max(keys.size(), values.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
27
source/net/filebot/util/FunctionList.java
Normal file
27
source/net/filebot/util/FunctionList.java
Normal 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
|
|
||||||
package net.filebot.util.ui;
|
package net.filebot.util.ui;
|
||||||
|
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Insets;
|
import java.awt.Insets;
|
||||||
|
|
||||||
@ -10,63 +9,52 @@ import javax.swing.Icon;
|
|||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
|
|
||||||
|
|
||||||
public class DefaultFancyListCellRenderer extends AbstractFancyListCellRenderer {
|
public class DefaultFancyListCellRenderer extends AbstractFancyListCellRenderer {
|
||||||
|
|
||||||
private final JLabel label = new DefaultListCellRenderer();
|
private final JLabel label = new DefaultListCellRenderer();
|
||||||
|
|
||||||
|
|
||||||
public DefaultFancyListCellRenderer() {
|
public DefaultFancyListCellRenderer() {
|
||||||
add(label);
|
add(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public DefaultFancyListCellRenderer(int padding) {
|
public DefaultFancyListCellRenderer(int padding) {
|
||||||
super(new Insets(padding, padding, padding, padding));
|
super(new Insets(padding, padding, padding, padding));
|
||||||
add(label);
|
add(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public DefaultFancyListCellRenderer(Insets padding) {
|
public DefaultFancyListCellRenderer(Insets padding) {
|
||||||
super(padding);
|
super(padding);
|
||||||
add(label);
|
add(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected DefaultFancyListCellRenderer(int padding, int margin, Color selectedBorderColor) {
|
protected DefaultFancyListCellRenderer(int padding, int margin, Color selectedBorderColor) {
|
||||||
super(new Insets(padding, padding, padding, padding), new Insets(margin, margin, margin, margin), selectedBorderColor);
|
super(new Insets(padding, padding, padding, padding), new Insets(margin, margin, margin, margin), selectedBorderColor);
|
||||||
add(label);
|
add(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
protected void configureListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||||
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
super.configureListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||||
label.setOpaque(false);
|
label.setOpaque(false);
|
||||||
setText(String.valueOf(value));
|
label.setText(String.valueOf(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setIcon(Icon icon) {
|
public void setIcon(Icon icon) {
|
||||||
label.setIcon(icon);
|
label.setIcon(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setText(String text) {
|
public void setText(String text) {
|
||||||
label.setText(text);
|
label.setText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setHorizontalTextPosition(int textPosition) {
|
public void setHorizontalTextPosition(int textPosition) {
|
||||||
label.setHorizontalTextPosition(textPosition);
|
label.setHorizontalTextPosition(textPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setVerticalTextPosition(int textPosition) {
|
public void setVerticalTextPosition(int textPosition) {
|
||||||
label.setVerticalTextPosition(textPosition);
|
label.setVerticalTextPosition(textPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setForeground(Color fg) {
|
public void setForeground(Color fg) {
|
||||||
super.setForeground(fg);
|
super.setForeground(fg);
|
||||||
|
53
source/net/filebot/util/ui/PrototypeCellValueUpdater.java
Normal file
53
source/net/filebot/util/ui/PrototypeCellValueUpdater.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user