diff --git a/source/net/sourceforge/filebot/WebServices.java b/source/net/sourceforge/filebot/WebServices.java index 3ccd022f..937fa043 100644 --- a/source/net/sourceforge/filebot/WebServices.java +++ b/source/net/sourceforge/filebot/WebServices.java @@ -51,7 +51,7 @@ public final class WebServices { public static SubtitleProvider[] getSubtitleProviders() { - return new SubtitleProvider[] { OpenSubtitles, Subscene, Sublight }; + return new SubtitleProvider[] { OpenSubtitles, Sublight, Subscene }; } diff --git a/source/net/sourceforge/filebot/similarity/SeriesNameMatcher.java b/source/net/sourceforge/filebot/similarity/SeriesNameMatcher.java index 5d6b9785..98a92afe 100644 --- a/source/net/sourceforge/filebot/similarity/SeriesNameMatcher.java +++ b/source/net/sourceforge/filebot/similarity/SeriesNameMatcher.java @@ -290,6 +290,13 @@ public class SeriesNameMatcher { @Override public boolean add(String value) { + value = value.trim(); + + // require series name to have at least two characters + if (value.length() < 2) { + return false; + } + String current = data.get(key(value)); // prefer strings with similar upper/lower case ratio (e.g. prefer Roswell over roswell) diff --git a/source/net/sourceforge/filebot/ui/SelectDialog.java b/source/net/sourceforge/filebot/ui/SelectDialog.java index adde7d56..3101600b 100644 --- a/source/net/sourceforge/filebot/ui/SelectDialog.java +++ b/source/net/sourceforge/filebot/ui/SelectDialog.java @@ -2,9 +2,10 @@ package net.sourceforge.filebot.ui; +import static net.sourceforge.tuned.ui.TunedUtilities.*; + import java.awt.Component; import java.awt.Dimension; -import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -37,8 +38,8 @@ public class SelectDialog extends JDialog { private boolean valueSelected = false; - public SelectDialog(Window owner, Collection options) { - super(owner, "Select", ModalityType.DOCUMENT_MODAL); + public SelectDialog(Component parent, Collection options) { + super(getWindow(parent), "Select", ModalityType.DOCUMENT_MODAL); setDefaultCloseOperation(DISPOSE_ON_CLOSE); diff --git a/source/net/sourceforge/filebot/ui/rename/AutoCompleteMatcher.java b/source/net/sourceforge/filebot/ui/rename/AutoCompleteMatcher.java index 1bf54cbf..e5604c59 100644 --- a/source/net/sourceforge/filebot/ui/rename/AutoCompleteMatcher.java +++ b/source/net/sourceforge/filebot/ui/rename/AutoCompleteMatcher.java @@ -2,7 +2,7 @@ package net.sourceforge.filebot.ui.rename; -import java.awt.Window; +import java.awt.Component; import java.io.File; import java.util.List; import java.util.Locale; @@ -12,5 +12,5 @@ import net.sourceforge.filebot.similarity.Match; interface AutoCompleteMatcher { - List> match(List files, Locale locale, boolean autodetection, Window parent) throws Exception; + List> match(List files, Locale locale, boolean autodetection, Component parent) throws Exception; } diff --git a/source/net/sourceforge/filebot/ui/rename/EpisodeListMatcher.java b/source/net/sourceforge/filebot/ui/rename/EpisodeListMatcher.java index b6c978bf..6ca4df90 100644 --- a/source/net/sourceforge/filebot/ui/rename/EpisodeListMatcher.java +++ b/source/net/sourceforge/filebot/ui/rename/EpisodeListMatcher.java @@ -4,12 +4,13 @@ package net.sourceforge.filebot.ui.rename; import static java.util.Collections.*; import static net.sourceforge.filebot.MediaTypes.*; +import static net.sourceforge.filebot.similarity.SeriesNameMatcher.*; import static net.sourceforge.filebot.web.EpisodeUtilities.*; import static net.sourceforge.tuned.FileUtilities.*; import static net.sourceforge.tuned.ui.TunedUtilities.*; +import java.awt.Component; import java.awt.Dimension; -import java.awt.Window; import java.io.File; import java.util.ArrayList; import java.util.Collection; @@ -56,7 +57,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher { } - protected SearchResult selectSearchResult(final String query, final List searchResults, final Window window) throws Exception { + protected SearchResult selectSearchResult(final String query, final List searchResults, final Component parent) throws Exception { if (searchResults.size() == 1) { return searchResults.get(0); } @@ -67,9 +68,9 @@ class EpisodeListMatcher implements AutoCompleteMatcher { // use name similarity metric SimilarityMetric metric = new NameSimilarityMetric(); - // find probable matches using name similarity > 0.9 + // find probable matches using name similarity >= 0.9 for (SearchResult result : searchResults) { - if (metric.getSimilarity(normalizeName(query), normalizeName(result.getName())) > 0.9) { + if (metric.getSimilarity(normalizeName(query), normalizeName(result.getName())) >= 0.9) { probableMatches.add(result); } } @@ -85,7 +86,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher { @Override public SearchResult call() throws Exception { // multiple results have been found, user must select one - SelectDialog selectDialog = new SelectDialog(window, searchResults); + SelectDialog selectDialog = new SelectDialog(parent, searchResults); selectDialog.getHeaderLabel().setText(String.format("Shows matching '%s':", query)); selectDialog.getCancelAction().putValue(Action.NAME, "Ignore"); @@ -117,12 +118,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher { } - protected Collection detectSeriesNames(Collection files) { - return new SeriesNameMatcher().matchAll(files.toArray(new File[0])); - } - - - protected Set fetchEpisodeSet(Collection seriesNames, final Locale locale, final Window window) throws Exception { + protected Set fetchEpisodeSet(Collection seriesNames, final Locale locale, final Component parent) throws Exception { List>> tasks = new ArrayList>>(); // detect series names and create episode list fetch tasks @@ -135,7 +131,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher { // select search result if (results.size() > 0) { - SearchResult selectedSearchResult = selectSearchResult(query, results, window); + SearchResult selectedSearchResult = selectSearchResult(query, results, parent); if (selectedSearchResult != null) { List episodes = provider.getEpisodeList(selectedSearchResult, locale); @@ -171,14 +167,14 @@ class EpisodeListMatcher implements AutoCompleteMatcher { @Override - public List> match(final List files, final Locale locale, final boolean autodetection, final Window window) throws Exception { + public List> match(final List files, final Locale locale, final boolean autodetection, final Component parent) throws Exception { // focus on movie and subtitle files final List mediaFiles = FileUtilities.filter(files, VIDEO_FILES, SUBTITLE_FILES); final Map> filesByFolder = mapByFolder(mediaFiles); // do matching all at once - if (filesByFolder.keySet().size() <= 5 || detectSeriesNames(mediaFiles).size() <= 5) { - return matchEpisodeSet(mediaFiles, locale, autodetection, window); + if (filesByFolder.keySet().size() <= 5 || detectSeriesName(mediaFiles).size() <= 5) { + return matchEpisodeSet(mediaFiles, locale, autodetection, parent); } // assume that many shows will be matched, do it folder by folder @@ -190,7 +186,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher { @Override public List> call() throws Exception { - return matchEpisodeSet(folder, locale, autodetection, window); + return matchEpisodeSet(folder, locale, autodetection, parent); } }); } @@ -214,16 +210,16 @@ class EpisodeListMatcher implements AutoCompleteMatcher { } - public List> matchEpisodeSet(final List files, Locale locale, boolean autodetection, Window window) throws Exception { + public List> matchEpisodeSet(final List files, Locale locale, boolean autodetection, Component parent) throws Exception { Set episodes = emptySet(); // detect series name and fetch episode list if (autodetection) { - Collection names = detectSeriesNames(files); + Collection names = detectSeriesName(files); if (names.size() > 0) { // only allow one fetch session at a time so later requests can make use of cached results synchronized (provider) { - episodes = fetchEpisodeSet(names, locale, window); + episodes = fetchEpisodeSet(names, locale, parent); } } } @@ -239,15 +235,15 @@ class EpisodeListMatcher implements AutoCompleteMatcher { suggestion = files.get(0).getParentFile().getName(); } - String input = null; + List input = emptyList(); synchronized (this) { - input = showInputDialog("Enter series name:", suggestion, files.get(0).getParentFile().getName(), window); + input = showMultiValueInputDialog("Enter series name:", suggestion, files.get(0).getParentFile().getName(), parent); } - if (input != null) { + if (input.size() > 0) { // only allow one fetch session at a time so later requests can make use of cached results synchronized (provider) { - episodes = fetchEpisodeSet(singleton(input), locale, window); + episodes = fetchEpisodeSet(input, locale, parent); } } } @@ -272,5 +268,4 @@ class EpisodeListMatcher implements AutoCompleteMatcher { return matches; } - } diff --git a/source/net/sourceforge/filebot/ui/rename/MovieHashMatcher.java b/source/net/sourceforge/filebot/ui/rename/MovieHashMatcher.java index 5785e919..475d900f 100644 --- a/source/net/sourceforge/filebot/ui/rename/MovieHashMatcher.java +++ b/source/net/sourceforge/filebot/ui/rename/MovieHashMatcher.java @@ -8,7 +8,7 @@ import static net.sourceforge.filebot.MediaTypes.*; import static net.sourceforge.tuned.FileUtilities.*; import static net.sourceforge.tuned.ui.TunedUtilities.*; -import java.awt.Window; +import java.awt.Component; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -53,7 +53,7 @@ class MovieHashMatcher implements AutoCompleteMatcher { @Override - public List> match(final List files, Locale locale, boolean autodetect, Window window) throws Exception { + public List> match(final List files, Locale locale, boolean autodetect, Component parent) throws Exception { // handle movie files File[] movieFiles = filter(files, VIDEO_FILES).toArray(new File[0]); @@ -70,7 +70,7 @@ class MovieHashMatcher implements AutoCompleteMatcher { // unknown hash, try via imdb id from nfo file if (movie == null || !autodetect) { - movie = grabMovieName(movieFiles[i], locale, autodetect, window, movie); + movie = grabMovieName(movieFiles[i], locale, autodetect, parent, movie); if (movie != null) { Analytics.trackEvent(service.getName(), "SearchMovie", movie.toString(), 1); @@ -161,7 +161,7 @@ class MovieHashMatcher implements AutoCompleteMatcher { } - protected Movie grabMovieName(File movieFile, Locale locale, boolean autodetect, Window window, Movie... suggestions) throws Exception { + protected Movie grabMovieName(File movieFile, Locale locale, boolean autodetect, Component parent, Movie... suggestions) throws Exception { List options = new ArrayList(); // add default value if any @@ -200,7 +200,7 @@ class MovieHashMatcher implements AutoCompleteMatcher { String input = null; synchronized (this) { - input = showInputDialog("Enter movie name:", suggestion, options.get(0).getName(), window); + input = showInputDialog("Enter movie name:", suggestion, options.get(0).getName(), parent); } if (input != null) { @@ -210,11 +210,11 @@ class MovieHashMatcher implements AutoCompleteMatcher { } } - return options.isEmpty() ? null : selectMovie(options, window); + return options.isEmpty() ? null : selectMovie(options, parent); } - protected Movie selectMovie(final List options, final Window window) throws Exception { + protected Movie selectMovie(final List options, final Component parent) throws Exception { if (options.size() == 1) { return options.get(0); } @@ -225,7 +225,7 @@ class MovieHashMatcher implements AutoCompleteMatcher { @Override public Movie call() throws Exception { // multiple results have been found, user must select one - SelectDialog selectDialog = new SelectDialog(window, options); + SelectDialog selectDialog = new SelectDialog(parent, options); selectDialog.getHeaderLabel().setText("Select Movie:"); selectDialog.getCancelAction().putValue(Action.NAME, "Ignore"); diff --git a/source/net/sourceforge/filebot/ui/subtitle/VideoHashSubtitleDownloadDialog.java b/source/net/sourceforge/filebot/ui/subtitle/SubtitleAutoMatchDialog.java similarity index 89% rename from source/net/sourceforge/filebot/ui/subtitle/VideoHashSubtitleDownloadDialog.java rename to source/net/sourceforge/filebot/ui/subtitle/SubtitleAutoMatchDialog.java index 5c940376..fe7d9f54 100644 --- a/source/net/sourceforge/filebot/ui/subtitle/VideoHashSubtitleDownloadDialog.java +++ b/source/net/sourceforge/filebot/ui/subtitle/SubtitleAutoMatchDialog.java @@ -4,8 +4,11 @@ package net.sourceforge.filebot.ui.subtitle; import static javax.swing.BorderFactory.*; import static javax.swing.JOptionPane.*; +import static net.sourceforge.filebot.similarity.SeriesNameMatcher.*; import static net.sourceforge.filebot.subtitle.SubtitleUtilities.*; import static net.sourceforge.tuned.FileUtilities.*; +import static net.sourceforge.tuned.StringUtilities.*; +import static net.sourceforge.tuned.ui.TunedUtilities.*; import java.awt.Color; import java.awt.Component; @@ -59,9 +62,7 @@ import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.similarity.EpisodeMetrics; import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.similarity.Matcher; -import net.sourceforge.filebot.similarity.SeriesNameMatcher; import net.sourceforge.filebot.similarity.SimilarityMetric; -import net.sourceforge.filebot.ui.Language; import net.sourceforge.filebot.vfs.MemoryFile; import net.sourceforge.filebot.web.SubtitleDescriptor; import net.sourceforge.filebot.web.SubtitleProvider; @@ -73,7 +74,7 @@ import net.sourceforge.tuned.ui.LinkButton; import net.sourceforge.tuned.ui.RoundBorder; -class VideoHashSubtitleDownloadDialog extends JDialog { +class SubtitleAutoMatchDialog extends JDialog { private final JPanel hashMatcherServicePanel = createServicePanel(0xFAFAD2); // LightGoldenRodYellow private final JPanel nameMatcherServicePanel = createServicePanel(0xFFEBCD); // BlanchedAlmond @@ -85,8 +86,8 @@ class VideoHashSubtitleDownloadDialog extends JDialog { private ExecutorService downloadService; - public VideoHashSubtitleDownloadDialog(Window owner) { - super(owner, "Download Subtitles", ModalityType.MODELESS); + public SubtitleAutoMatchDialog(Window owner) { + super(owner, "Download Subtitles", ModalityType.DOCUMENT_MODAL); JComponent content = (JComponent) getContentPane(); content.setLayout(new MigLayout("fill, insets dialog, nogrid", "", "[fill][pref!]")); @@ -189,8 +190,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog { public void startQuery(String languageName) { final SubtitleMappingTableModel mappingModel = (SubtitleMappingTableModel) subtitleMappingTable.getModel(); - - QueryTask queryTask = new QueryTask(services, mappingModel.getVideoFiles(), languageName) { + QueryTask queryTask = new QueryTask(services, mappingModel.getVideoFiles(), languageName, SubtitleAutoMatchDialog.this) { @Override protected void process(List>> sequence) { @@ -212,8 +212,8 @@ class VideoHashSubtitleDownloadDialog extends JDialog { } }; - ExecutorService executor = Executors.newFixedThreadPool(1); - executor.submit(queryTask); + queryService = Executors.newFixedThreadPool(1); + queryService.submit(queryTask); } @@ -232,7 +232,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog { JOptionPane optionPane = new JOptionPane(message, WARNING_MESSAGE, YES_NO_CANCEL_OPTION, null, options); // display option dialog - optionPane.createDialog(VideoHashSubtitleDownloadDialog.this, "Replace").setVisible(true); + optionPane.createDialog(SubtitleAutoMatchDialog.this, "Replace").setVisible(true); // replace all if (options[0] == optionPane.getValue()) @@ -277,7 +277,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog { try { mapping.setSubtitleFile(get()); } catch (Exception e) { - Logger.getLogger(VideoHashSubtitleDownloadDialog.class.getName()).log(Level.WARNING, e.getMessage()); + Logger.getLogger(SubtitleAutoMatchDialog.class.getName()).log(Level.WARNING, e.getMessage(), e); } } }); @@ -642,7 +642,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog { public String getText() { - return formatSubtitle(descriptor.getName(), getLanguage(), getType()); + return formatSubtitle(descriptor.getName(), getLanguageName(), getType()); } @@ -651,8 +651,8 @@ class VideoHashSubtitleDownloadDialog extends JDialog { } - public String getLanguage() { - return Language.getISO3LanguageCodeByName(descriptor.getLanguageName()); + public String getLanguageName() { + return descriptor.getLanguageName(); } @@ -706,13 +706,15 @@ class VideoHashSubtitleDownloadDialog extends JDialog { private static class QueryTask extends SwingWorker, Map>> { + private final Component parent; private final Collection services; private final Collection remainingVideos; private final String languageName; - public QueryTask(Collection services, Collection videoFiles, String languageName) { + public QueryTask(Collection services, Collection videoFiles, String languageName, Component parent) { + this.parent = parent; this.services = services; this.remainingVideos = new TreeSet(videoFiles); this.languageName = languageName; @@ -722,16 +724,17 @@ class VideoHashSubtitleDownloadDialog extends JDialog { @Override protected Collection doInBackground() throws Exception { for (SubtitleServiceBean service : services) { + if (isCancelled() || Thread.interrupted()) { + throw new CancellationException(); + } + + if (remainingVideos.isEmpty()) { + break; + } + try { - if (isCancelled()) - throw new CancellationException(); - - if (remainingVideos.isEmpty()) - break; - Map> subtitleSet = new HashMap>(); - - for (final Entry> result : service.lookupSubtitles(remainingVideos, languageName).entrySet()) { + for (final Entry> result : service.lookupSubtitles(remainingVideos, languageName, parent).entrySet()) { List subtitles = new ArrayList(); // associate subtitles with services @@ -750,8 +753,15 @@ class VideoHashSubtitleDownloadDialog extends JDialog { } publish(subtitleSet); + } catch (CancellationException e) { + // don't ignore cancellation + throw e; + } catch (InterruptedException e) { + // don't ignore cancellation + throw e; } catch (Exception e) { - Logger.getLogger(VideoHashSubtitleDownloadDialog.class.getName()).log(Level.WARNING, e.getMessage()); + // log and ignore + Logger.getLogger(SubtitleAutoMatchDialog.class.getName()).log(Level.WARNING, e.getMessage(), e); } } @@ -781,9 +791,10 @@ class VideoHashSubtitleDownloadDialog extends JDialog { if (descriptor.getType() == null && subtitle == null) return null; + // prefer type from descriptor because we need to know before we download the actual subtitle file String base = FileUtilities.getName(video); - String ext = descriptor.getType() != null ? descriptor.getType() : getExtension(subtitle.getName()); - return new File(video.getParentFile(), formatSubtitle(base, descriptor.getLanguage(), ext)); + String ext = (descriptor.getType() != null) ? descriptor.getType() : getExtension(subtitle.getName()); + return new File(video.getParentFile(), formatSubtitle(base, descriptor.getLanguageName(), ext)); } @@ -802,7 +813,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog { return destination; } catch (Exception e) { - Logger.getLogger(VideoHashSubtitleDownloadDialog.class.getName()).log(Level.WARNING, e.getMessage()); + Logger.getLogger(SubtitleAutoMatchDialog.class.getName()).log(Level.WARNING, e.getMessage(), e); } return null; @@ -842,14 +853,14 @@ class VideoHashSubtitleDownloadDialog extends JDialog { } - protected abstract Map> getSubtitleList(Collection files, String languageName) throws Exception; + protected abstract Map> getSubtitleList(Collection files, String languageName, Component parent) throws Exception; - public final Map> lookupSubtitles(Collection files, String languageName) throws Exception { + public final Map> lookupSubtitles(Collection files, String languageName, Component parent) throws Exception { setState(StateValue.STARTED); try { - return getSubtitleList(files, languageName); + return getSubtitleList(files, languageName, parent); } catch (Exception e) { // remember error error = e; @@ -891,7 +902,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog { @Override - protected Map> getSubtitleList(Collection files, String languageName) throws Exception { + protected Map> getSubtitleList(Collection files, String languageName, Component parent) throws Exception { return service.getSubtitleList(files.toArray(new File[0]), languageName); } } @@ -909,17 +920,29 @@ class VideoHashSubtitleDownloadDialog extends JDialog { @Override - protected Map> getSubtitleList(Collection files, String languageName) throws Exception { + protected Map> getSubtitleList(Collection files, String languageName, Component parent) throws Exception { Map> subtitlesByFile = new HashMap>(); for (File file : files) { subtitlesByFile.put(file, new ArrayList()); } // auto-detect query and search for subtitles - Collection querySet = new SeriesNameMatcher().matchAll(files.toArray(new File[0])); + Collection querySet = detectSeriesName(files); List subtitles = findSubtitles(service, querySet, languageName); + + // if auto-detection fails, ask user for input if (subtitles.isEmpty()) { - throw new IllegalArgumentException("Unable to lookup subtitles:" + querySet); + // dialog may have been cancelled by now + if (Thread.interrupted()) + throw new CancellationException(); + + querySet = showMultiValueInputDialog("Enter series / movie names:", join(querySet, ","), service.getName(), parent); + subtitles = findSubtitles(service, querySet, languageName); + + // still no luck... na women ye mei banfa + if (subtitles.isEmpty()) { + throw new Exception("Unable to lookup subtitles:" + querySet); + } } // first match everything as best as possible, then filter possibly bad matches diff --git a/source/net/sourceforge/filebot/ui/subtitle/SubtitleDropTarget.java b/source/net/sourceforge/filebot/ui/subtitle/SubtitleDropTarget.java index db501cd2..eaf2793f 100644 --- a/source/net/sourceforge/filebot/ui/subtitle/SubtitleDropTarget.java +++ b/source/net/sourceforge/filebot/ui/subtitle/SubtitleDropTarget.java @@ -96,7 +96,7 @@ abstract class SubtitleDropTarget extends JButton { private boolean handleDownload(List videoFiles) { - VideoHashSubtitleDownloadDialog dialog = new VideoHashSubtitleDownloadDialog(getWindow(this)); + SubtitleAutoMatchDialog dialog = new SubtitleAutoMatchDialog(getWindow(this)); // initialize download parameters dialog.setVideoFiles(videoFiles.toArray(new File[0])); diff --git a/source/net/sourceforge/filebot/web/OpenSubtitlesClient.java b/source/net/sourceforge/filebot/web/OpenSubtitlesClient.java index 47a89a56..c17014ea 100644 --- a/source/net/sourceforge/filebot/web/OpenSubtitlesClient.java +++ b/source/net/sourceforge/filebot/web/OpenSubtitlesClient.java @@ -24,6 +24,8 @@ import java.util.logging.Logger; import javax.swing.Icon; +import redstone.xmlrpc.XmlRpcException; + import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.web.OpenSubtitlesXmlRpc.Query; import net.sourceforge.tuned.Timer; @@ -65,10 +67,14 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS // require login login(); - // search for movies / series - SearchResult[] result = xmlrpc.searchMoviesOnIMDB(query).toArray(new SearchResult[0]); - - return Arrays.asList(result); + try { + // search for movies / series + List resultSet = xmlrpc.searchMoviesOnIMDB(query); + return Arrays.asList(resultSet.toArray(new SearchResult[0])); + } catch (ClassCastException e) { + // unexpected xmlrpc responses (e.g. error messages instead of results) will trigger this + throw new XmlRpcException("Illegal XMLRPC response on searchMoviesOnIMDB"); + } } diff --git a/source/net/sourceforge/tuned/StringUtilities.java b/source/net/sourceforge/tuned/StringUtilities.java index 63ea277e..21b89e6f 100644 --- a/source/net/sourceforge/tuned/StringUtilities.java +++ b/source/net/sourceforge/tuned/StringUtilities.java @@ -2,23 +2,20 @@ package net.sourceforge.tuned; +import static java.util.Arrays.*; + import java.util.Iterator; public final class StringUtilities { + public static boolean isEmptyValue(Object object) { + return object != null && object.toString().length() > 0; + } + + public static String join(Object[] values, CharSequence delimiter) { - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < values.length; i++) { - sb.append(values[i]); - - if (i < values.length - 1) { - sb.append(delimiter); - } - } - - return sb.toString(); + return join(asList(values), delimiter); } @@ -26,10 +23,13 @@ public final class StringUtilities { StringBuilder sb = new StringBuilder(); for (Iterator iterator = values.iterator(); iterator.hasNext();) { - sb.append(iterator.next()); - - if (iterator.hasNext()) { - sb.append(delimiter); + Object value = iterator.next(); + if (!isEmptyValue(value)) { + if (sb.length() > 0) { + sb.append(delimiter); + } + + sb.append(value); } } diff --git a/source/net/sourceforge/tuned/ui/TunedUtilities.java b/source/net/sourceforge/tuned/ui/TunedUtilities.java index ac254b4a..eb4d36c7 100644 --- a/source/net/sourceforge/tuned/ui/TunedUtilities.java +++ b/source/net/sourceforge/tuned/ui/TunedUtilities.java @@ -2,6 +2,7 @@ package net.sourceforge.tuned.ui; +import static java.util.Collections.*; import static javax.swing.JOptionPane.*; import java.awt.Color; @@ -18,6 +19,8 @@ import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; import javax.swing.AbstractAction; import javax.swing.Action; @@ -118,7 +121,32 @@ public final class TunedUtilities { } - public static String showInputDialog(final String text, final String initialValue, final String title, final Window parent) throws InvocationTargetException, InterruptedException { + public static List showMultiValueInputDialog(final String text, final String initialValue, final String title, final Component parent) throws InvocationTargetException, InterruptedException { + String input = showInputDialog(text, initialValue, title, parent); + if (input == null || input.isEmpty()) { + return emptyList(); + } + + for (char separator : new char[] { '|', ';', ',' }) { + if (input.indexOf(separator) >= 0) { + List values = new ArrayList(); + for (String field : input.split(Character.toString(separator))) { + if (field.length() > 0) { + values.add(field); + } + } + + if (values.size() > 0) { + return values; + } + } + } + + return singletonList(input); + } + + + public static String showInputDialog(final String text, final String initialValue, final String title, final Component parent) throws InvocationTargetException, InterruptedException { final StringBuilder buffer = new StringBuilder(); SwingUtilities.invokeAndWait(new Runnable() {