From 0c674849d8f2df540a6bc0fdbc3be9825eaf9423 Mon Sep 17 00:00:00 2001 From: Reinhard Pointner Date: Wed, 30 Jul 2008 22:37:01 +0000 Subject: [PATCH] * refactored and simplified transfer api * use more GlazedLists stuff (EventList, AutoCompleteSupport) and remove obsolete classes (SimpleListModel, TextCompletion) * don't use SearchResultCache in EpisodeListClient (was only done for better ui interactions) * removed caching from ResourceManager * some improvements based on FindBugs warnings * use args4j for improved argument parsing * updated ant build script * more general MessageBus/Handler (use Object as message type instead of string) * ChecksumComputationService is not a singleton anymore * TemporaryFolder is always recreated if it is deleted by the user, or another instance shutting down * Notifications flicker less when one window is removed and the others are layouted * lots of other refactoring --- build.xml | 30 ++- lib/args4j-2.0.9.jar | Bin 0 -> 40654 bytes source/Main.java | 71 +++---- .../net/sourceforge/filebot/ArgumentBean.java | 95 ++++++++++ .../net/sourceforge/filebot/FileBotUtil.java | 9 +- source/net/sourceforge/filebot/Settings.java | 1 - .../filebot/resources/ResourceManager.java | 48 +---- .../filebot/resources/search.imdb.png | Bin 325 -> 0 bytes .../filebot/resources/tab.history.png | Bin 0 -> 875 bytes .../sourceforge/filebot/torrent/Torrent.java | 36 ++-- .../filebot/ui/AbstractSearchPanel.java | 74 ++++---- .../sourceforge/filebot/ui/FileBotList.java | 133 ++++++------- .../filebot/ui/FileBotListExportHandler.java | 43 +++++ .../sourceforge/filebot/ui/FileBotPanel.java | 38 ---- .../filebot/ui/FileBotPanelSelectionList.java | 18 +- .../sourceforge/filebot/ui/FileBotTree.java | 13 +- .../sourceforge/filebot/ui/FileBotWindow.java | 41 ++-- .../ui/FileTransferableMessageHandler.java | 54 ++++-- .../sourceforge/filebot/ui/SelectDialog.java | 8 +- .../ui/panel/analyze/AnalyzePanel.java | 2 +- .../filebot/ui/panel/analyze/FileTree.java | 37 ++-- .../ui/panel/analyze/FileTreePanel.java | 2 +- .../analyze/FileTreeTransferablePolicy.java | 4 +- .../list/FileListTransferablePolicy.java | 11 +- .../filebot/ui/panel/list/ListPanel.java | 20 +- .../rename/FilesListTransferablePolicy.java | 12 +- .../filebot/ui/panel/rename/MatchAction.java | 21 ++- .../rename/NamesListTransferablePolicy.java | 178 +++++++++--------- .../filebot/ui/panel/rename/RenameAction.java | 10 +- .../filebot/ui/panel/rename/RenameList.java | 82 ++++---- .../filebot/ui/panel/rename/RenamePanel.java | 30 +-- .../ui/panel/rename/ValidateNamesDialog.java | 8 +- .../ui/panel/rename/entry/FileEntry.java | 1 + .../metric/AbstractNameSimilarityMetric.java | 4 +- .../metric/NumericSimilarityMetric.java | 2 +- .../ui/panel/search/EpisodeListPanel.java | 10 +- .../filebot/ui/panel/search/SearchPanel.java | 46 ++--- .../filebot/ui/panel/sfv/Checksum.java | 8 +- .../panel/sfv/ChecksumComputationService.java | 127 ++++++------- .../filebot/ui/panel/sfv/ChecksumRow.java | 38 ++-- .../panel/sfv/ChecksumTableExportHandler.java | 90 +++++++++ .../ui/panel/sfv/ChecksumTableModel.java | 85 +++++---- .../filebot/ui/panel/sfv/SfvPanel.java | 148 +++++++-------- .../filebot/ui/panel/sfv/SfvTable.java | 121 ++++-------- .../ui/panel/sfv/SfvTransferablePolicy.java | 25 +-- .../ui/panel/sfv/TotalProgressPanel.java | 30 +-- .../filebot/ui/panel/subtitle/Language.java | 9 +- .../ui/panel/subtitle/LanguageResolver.java | 10 +- .../subtitle/LanguageSelectionPanel.java | 8 +- .../panel/subtitle/SubtitleCellRenderer.java | 5 +- .../ui/panel/subtitle/SubtitlePackage.java | 7 +- .../ui/panel/subtitle/SubtitlePanel.java | 15 +- .../transfer/AdaptiveFileExportHandler.java | 40 ++++ .../BackgroundFileTransferablePolicy.java | 138 ++++++++++++++ .../ui/transfer/DefaultListExportHandler.java | 45 +++++ .../ui/transfer/DefaultTransferHandler.java | 15 ++ ...ortHandler.java => FileExportHandler.java} | 33 +++- .../filebot/ui/transfer/FileTransferable.java | 3 +- .../ui/transfer/FileTransferablePolicy.java | 124 ++++++++++++ .../filebot/ui/transfer/LoadAction.java | 11 +- .../filebot/ui/transfer/SaveAction.java | 49 ++--- .../filebot/ui/transfer/Saveable.java | 17 -- .../ui/transfer/StringTransferablePolicy.java | 47 +++++ .../ui/transfer/TransferablePolicy.java | 96 ++++++++++ .../TransferablePolicyFileFilter.java | 11 +- .../TransferablePolicyImportHandler.java | 63 ------- .../sourceforge/filebot/web/AnidbClient.java | 12 +- .../net/sourceforge/filebot/web/Episode.java | 8 +- .../net/sourceforge/filebot/web/HtmlUtil.java | 5 +- .../filebot/web/OpenSubtitlesClient.java | 45 ++--- .../filebot/web/OpenSubtitlesHasher.java | 14 +- .../web/OpenSubtitlesSubtitleClient.java | 3 +- .../web/OpenSubtitlesSubtitleDescriptor.java | 3 +- .../filebot/web/SearchResultCache.java | 39 ---- .../filebot/web/SubsceneSubtitleClient.java | 12 +- .../filebot/web/TVDotComClient.java | 14 +- .../sourceforge/filebot/web/TVRageClient.java | 10 +- .../net/sourceforge/tuned/DownloadTask.java | 9 +- source/net/sourceforge/tuned/FileUtil.java | 4 +- .../ListChangeSynchronizer.java | 3 +- source/net/sourceforge/tuned/MessageBus.java | 31 +-- .../net/sourceforge/tuned/MessageHandler.java | 4 +- .../sourceforge/tuned/PreferencesList.java | 15 +- .../net/sourceforge/tuned/PreferencesMap.java | 10 +- .../sourceforge/tuned/TemporaryFolder.java | 31 +-- .../sourceforge/tuned/ui/ArrayListModel.java | 45 +++++ .../net/sourceforge/tuned/ui/FancyBorder.java | 85 --------- .../sourceforge/tuned/ui/IconViewPanel.java | 24 ++- .../sourceforge/tuned/ui/ProgressDialog.java | 2 +- .../tuned/ui/SelectButtonTextField.java | 82 ++++++-- .../tuned/ui/SimpleLabelProvider.java | 40 ++-- .../sourceforge/tuned/ui/SimpleListModel.java | 139 -------------- .../sourceforge/tuned/ui/TextCompletion.java | 129 ------------- .../net/sourceforge/tuned/ui/TunedUtil.java | 22 ++- .../tuned/ui/notification/Factor.java | 11 +- .../ui/notification/NotificationManager.java | 40 ++-- .../ui/notification/NotificationWindow.java | 36 ++-- .../ui/notification/SeparatorBorder.java | 9 +- .../sourceforge/filebot/ArgumentBeanTest.java | 62 ++++++ .../sourceforge/filebot/FileBotTestSuite.java | 2 +- 100 files changed, 1949 insertions(+), 1661 deletions(-) create mode 100644 lib/args4j-2.0.9.jar create mode 100644 source/net/sourceforge/filebot/ArgumentBean.java delete mode 100644 source/net/sourceforge/filebot/resources/search.imdb.png create mode 100644 source/net/sourceforge/filebot/resources/tab.history.png create mode 100644 source/net/sourceforge/filebot/ui/FileBotListExportHandler.java create mode 100644 source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableExportHandler.java create mode 100644 source/net/sourceforge/filebot/ui/transfer/AdaptiveFileExportHandler.java create mode 100644 source/net/sourceforge/filebot/ui/transfer/BackgroundFileTransferablePolicy.java create mode 100644 source/net/sourceforge/filebot/ui/transfer/DefaultListExportHandler.java rename source/net/sourceforge/filebot/ui/transfer/{SaveableExportHandler.java => FileExportHandler.java} (60%) create mode 100644 source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java delete mode 100644 source/net/sourceforge/filebot/ui/transfer/Saveable.java create mode 100644 source/net/sourceforge/filebot/ui/transfer/StringTransferablePolicy.java create mode 100644 source/net/sourceforge/filebot/ui/transfer/TransferablePolicy.java delete mode 100644 source/net/sourceforge/filebot/ui/transfer/TransferablePolicyImportHandler.java delete mode 100644 source/net/sourceforge/filebot/web/SearchResultCache.java rename source/net/sourceforge/{filebot => tuned}/ListChangeSynchronizer.java (94%) create mode 100644 source/net/sourceforge/tuned/ui/ArrayListModel.java delete mode 100644 source/net/sourceforge/tuned/ui/FancyBorder.java delete mode 100644 source/net/sourceforge/tuned/ui/SimpleListModel.java delete mode 100644 source/net/sourceforge/tuned/ui/TextCompletion.java create mode 100644 test/net/sourceforge/filebot/ArgumentBeanTest.java diff --git a/build.xml b/build.xml index 37f4370a..17046373 100644 --- a/build.xml +++ b/build.xml @@ -1,6 +1,6 @@ - + @@ -9,20 +9,26 @@ + - + - - - - - - + + + + + + + + + + + @@ -63,6 +69,10 @@ + + + + @@ -98,13 +108,13 @@ - + - + diff --git a/lib/args4j-2.0.9.jar b/lib/args4j-2.0.9.jar new file mode 100644 index 0000000000000000000000000000000000000000..a894c6e53b70d6b39fa131f1cf33accd5eb803dd GIT binary patch literal 40654 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

Btf|( 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 literal 0 HcmV?d00001 diff --git a/source/Main.java b/source/Main.java index 314e4a68..0ac43175 100644 --- a/source/Main.java +++ b/source/Main.java @@ -1,20 +1,15 @@ -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import net.sourceforge.filebot.ArgumentBean; import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.ui.FileBotWindow; -import net.sourceforge.tuned.MessageBus; + +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; public class Main { @@ -24,11 +19,10 @@ public class Main { */ public static void main(String... args) { - final Arguments arguments = new Arguments(args); + final ArgumentBean argumentBean = parseArguments(args); - if (arguments.containsParameter("clear")) { + if (argumentBean.isClear()) Settings.getSettings().clear(); - } try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); @@ -43,7 +37,7 @@ public class Main { FileBotWindow window = new FileBotWindow(); // publish messages from arguments to the newly created components - arguments.publishMessages(); + argumentBean.publishMessages(); // start window.setVisible(true); @@ -51,43 +45,26 @@ public class Main { }); } - - private static class Arguments { - - private final Set parameters = new HashSet(3); - private final Map> messages = new LinkedHashMap>(); - - - public Arguments(String[] args) { - Pattern topicPattern = Pattern.compile("--(\\w+)"); - - String currentTopic = null; - - for (String arg : args) { - Matcher m = topicPattern.matcher(arg); - - if (m.matches()) { - currentTopic = m.group(1).toLowerCase(); - messages.put(currentTopic, new ArrayList(1)); - } else if (currentTopic != null) { - messages.get(currentTopic).add(arg); - } else { - parameters.add(arg.toLowerCase()); - } - } - } - - public boolean containsParameter(String argument) { - return parameters.contains(argument); + private static ArgumentBean parseArguments(String... args) { + + ArgumentBean argumentBean = new ArgumentBean(); + CmdLineParser argumentParser = new CmdLineParser(argumentBean); + + try { + argumentParser.parseArgument(args); + } catch (CmdLineException e) { + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, e.getMessage()); } - - public void publishMessages() { - for (String topic : messages.keySet()) { - MessageBus.getDefault().publish(topic, messages.get(topic).toArray(new String[0])); - } + if (argumentBean.isHelp()) { + System.out.println("Options:"); + argumentParser.printUsage(System.out); + + // just print help message and exit afterwards + System.exit(0); } + + return argumentBean; } - } diff --git a/source/net/sourceforge/filebot/ArgumentBean.java b/source/net/sourceforge/filebot/ArgumentBean.java new file mode 100644 index 00000000..67246a65 --- /dev/null +++ b/source/net/sourceforge/filebot/ArgumentBean.java @@ -0,0 +1,95 @@ + +package net.sourceforge.filebot; + + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.sourceforge.tuned.MessageBus; + +import org.kohsuke.args4j.Option; + + +public class ArgumentBean { + + @Option(name = "-help", usage = "Print this help message") + private boolean help = false; + + @Option(name = "-clear", usage = "Clear history and settings") + private boolean clear = false; + + @Message(topic = "list") + @Option(name = "--list", usage = "Open file in 'List' panel", metaVar = "") + private File listPanelFile; + + @Message(topic = "analyze") + @Option(name = "--analyze", usage = "Open file in 'Analyze' panel", metaVar = "") + private File analyzePanelFile; + + @Message(topic = "sfv") + @Option(name = "--sfv", usage = "Open file in 'SFV' panel", metaVar = "") + private File sfvPanelFile; + + + public boolean isHelp() { + return help; + } + + + public boolean isClear() { + return clear; + } + + + public File getListPanelFile() { + return listPanelFile; + } + + + public File getAnalyzePanelFile() { + return analyzePanelFile; + } + + + public File getSfvPanelFile() { + return sfvPanelFile; + } + + + public void publishMessages() { + for (Field field : getClass().getDeclaredFields()) { + + Message message = field.getAnnotation(Message.class); + + if (message == null) + continue; + + try { + Object value = field.get(this); + + if (value != null) { + MessageBus.getDefault().publish(message.topic(), value); + } + } catch (Exception e) { + // should not happen + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + } + } + } + + + @Retention(RUNTIME) + @Target(FIELD) + private @interface Message { + + String topic(); + } + +} diff --git a/source/net/sourceforge/filebot/FileBotUtil.java b/source/net/sourceforge/filebot/FileBotUtil.java index 756393b2..55ac8bce 100644 --- a/source/net/sourceforge/filebot/FileBotUtil.java +++ b/source/net/sourceforge/filebot/FileBotUtil.java @@ -13,10 +13,6 @@ import net.sourceforge.tuned.FileUtil; public class FileBotUtil { - private FileBotUtil() { - // hide constructor - } - /** * Invalid characters in filenames: \, /, :, *, ?, ", <, >, |, \r and \n */ @@ -107,4 +103,9 @@ public class FileBotUtil { }; + + private FileBotUtil() { + // hide constructor + } + } diff --git a/source/net/sourceforge/filebot/Settings.java b/source/net/sourceforge/filebot/Settings.java index 69cf6c7a..3447fb00 100644 --- a/source/net/sourceforge/filebot/Settings.java +++ b/source/net/sourceforge/filebot/Settings.java @@ -23,7 +23,6 @@ public class Settings { public static final String SELECTED_PANEL = "panel"; public static final String SEARCH_HISTORY = "search/history"; public static final String SUBTITLE_HISTORY = "subtitle/history"; - public static final String SUBTITLE_LANGUAGE = "subtitle/language"; private static final Settings settings = new Settings(); diff --git a/source/net/sourceforge/filebot/resources/ResourceManager.java b/source/net/sourceforge/filebot/resources/ResourceManager.java index 3501b586..bcfa5d14 100644 --- a/source/net/sourceforge/filebot/resources/ResourceManager.java +++ b/source/net/sourceforge/filebot/resources/ResourceManager.java @@ -5,10 +5,6 @@ package net.sourceforge.filebot.resources; import java.awt.Image; import java.io.IOException; import java.net.URL; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.WeakHashMap; import javax.imageio.ImageIO; import javax.swing.ImageIcon; @@ -16,40 +12,18 @@ import javax.swing.ImageIcon; public class ResourceManager { - private ResourceManager() { - // hide constructor - } - - private static final Map aliasMap = new HashMap(); - - static { - aliasMap.put("tab.loading", "tab.loading.gif"); - aliasMap.put("tab.history", "action.find.png"); - aliasMap.put("loading", "loading.gif"); - } - - private static final Map iconCache = Collections.synchronizedMap(new WeakHashMap()); - - public static ImageIcon getIcon(String name) { return getIcon(name, null); } public static ImageIcon getIcon(String name, String def) { - ImageIcon icon = iconCache.get(name); + URL resource = getResource(name, def); - if (icon == null) { - // load image if not in cache - URL resource = getResource(name, def); - - if (resource != null) { - icon = new ImageIcon(resource); - iconCache.put(name, icon); - } - } + if (resource != null) + return new ImageIcon(resource); - return icon; + return null; } @@ -73,14 +47,7 @@ public class ResourceManager { private static URL getResource(String name) { - String resource = null; - - if (aliasMap.containsKey(name)) - resource = aliasMap.get(name); - else - resource = name + ".png"; - - return ResourceManager.class.getResource(resource); + return ResourceManager.class.getResource(name + ".png"); } @@ -93,4 +60,9 @@ public class ResourceManager { return resource; } + + private ResourceManager() { + throw new UnsupportedOperationException(); + } + } diff --git a/source/net/sourceforge/filebot/resources/search.imdb.png b/source/net/sourceforge/filebot/resources/search.imdb.png deleted file mode 100644 index 9b12a004e01e9094aacf36f83f2643294f4a8445..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 325 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`EX7WqAsj$Z!;#Vf4nJ za0`JjN_dpHnjT@@Bt5+qlAoK)#t?R7a);L`&c+EJ_QabRYA#w-GG214q*&nexr~o4 zrmcQ4Vb`R(z6rq>Y;*H>xajR|VJ-3B@~`!!metLRt?!O1$sYJ`f0^4N&!cmR-|>E+ PKNvh+{an^LB{Ts5GvInk diff --git a/source/net/sourceforge/filebot/resources/tab.history.png b/source/net/sourceforge/filebot/resources/tab.history.png new file mode 100644 index 0000000000000000000000000000000000000000..f4ceda5f2fed0296f878ab8895f53057b21d0e55 GIT binary patch literal 875 zcmV-x1C;!UP)2-}06c0~|`buYrgDn<-$E7ES01s6Q+JH2;#-}k=o;3*+Ip@SP68&`B)UpqNDxt2<$mVMu!>-BmE*=+WM zZnt|_DwRF~y#F&;-`?K7n@*=G7K>Pxh3mTH^LZ+j3bt*tySw|U>$*QazIaB`>2!K$ zXXo15+S<}=Hlx*QQLoqObUJvRM?4;HKp@iZ_i-GDOeV89KR>@*E|;$ZhyaSBEJmZz%X@o!^m;uU$D!Zv z({8s9eBYmp$Kwl%q9DsMmSqu(#op3&y=a=|eeqHJH%!wEFD)%02m+;23CD3bJv}W0 zr?zchkY$<8%}qj~5bbt*E|EyQX`1H!03b+`bXiqZbX`Xf1g6s|;c)l__#F5`lB9;` zc?5$&GMNmTrV))sF9Cr7Fe?-a;b<(z6sT0IJgW5}a2L3zY1&7EAaGQ#^Gq~~6qU*4 zasj{>RaMO!FRr{Yv@E2--=rfE%v_#f#|oT1csLx2+I*H=GRdS-#~nC4`}_+ls;cJ2 zM5 z;q~(}3iywv$7#{u6cwAvRZG7j;EUuqNN@)zN4%ZJxxL&6k^hWOC(IoQRPuBFM zpY54&>WE literal 0 HcmV?d00001 diff --git a/source/net/sourceforge/filebot/torrent/Torrent.java b/source/net/sourceforge/filebot/torrent/Torrent.java index 9c12addc..f52c5152 100644 --- a/source/net/sourceforge/filebot/torrent/Torrent.java +++ b/source/net/sourceforge/filebot/torrent/Torrent.java @@ -6,7 +6,6 @@ import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; @@ -33,16 +32,7 @@ public class Torrent { public Torrent(File torrent) throws IOException { - this(new FileInputStream(torrent)); - } - - - /** - * Load torrent data from an InputStream. The given stream will be closed - * after data has been read. - */ - public Torrent(InputStream inputStream) throws IOException { - this(decodeTorrent(inputStream)); + this(decodeTorrent(torrent)); } @@ -56,7 +46,7 @@ public class Torrent { charset = Charset.forName(encoding); } catch (IllegalArgumentException e) { // invalid encoding, just keep using UTF-8 - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, e.getMessage()); + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid encoding: " + encoding); } createdBy = decodeString(torrentMap.get("created by"), charset); @@ -113,6 +103,17 @@ public class Torrent { } + private static Map decodeTorrent(File torrent) throws IOException { + BufferedInputStream in = new BufferedInputStream(new FileInputStream(torrent)); + + try { + return BDecoder.decode(in); + } finally { + in.close(); + } + } + + private String decodeString(Object byteArray, Charset charset) { if (byteArray == null) return null; @@ -173,17 +174,6 @@ public class Torrent { return singleFileTorrent; } - - private static Map decodeTorrent(InputStream torrent) throws IOException { - BufferedInputStream in = new BufferedInputStream(torrent); - - try { - return BDecoder.decode(in); - } finally { - in.close(); - } - } - public static class Entry { diff --git a/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java b/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java index 795e1106..d84beeb6 100644 --- a/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java +++ b/source/net/sourceforge/filebot/ui/AbstractSearchPanel.java @@ -91,12 +91,14 @@ public abstract class AbstractSearchPanel extends Fi completionList.addMemberList(fetchHistory); */ + searchField.getEditor().setAction(searchAction); + searchField.getSelectButton().setModel(createSearchEngines()); searchField.getSelectButton().setLabelProvider(createSearchEngineLabelProvider()); AutoCompleteSupport.install(searchField.getEditor(), searchHistory); - TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction); + TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction); } @@ -109,9 +111,6 @@ public abstract class AbstractSearchPanel extends Fi protected abstract SearchTask createSearchTask(); - protected abstract void configureSelectDialog(SelectDialog selectDialog); - - protected abstract FetchTask createFetchTask(SearchTask searchTask, SearchResult selectedSearchResult); @@ -163,6 +162,41 @@ public abstract class AbstractSearchPanel extends Fi protected abstract Collection doInBackground() throws Exception; + protected SearchResult chooseSearchResult() throws Exception { + + switch (get().size()) { + case 0: + MessageManager.showWarning(String.format("\"%s\" has not been found.", getSearchText())); + return null; + case 1: + return get().iterator().next(); + } + + // check if an exact match has been found + for (SearchResult searchResult : get()) { + if (getSearchText().equalsIgnoreCase(searchResult.getName())) + return searchResult; + } + + // multiple results have been found, user must select one + Window window = SwingUtilities.getWindowAncestor(AbstractSearchPanel.this); + + SelectDialog selectDialog = new SelectDialog(window, get()); + + configureSelectDialog(selectDialog); + + selectDialog.setVisible(true); + + // selected value or null if the dialog was canceled by the user + return selectDialog.getSelectedValue(); + } + + + protected void configureSelectDialog(SelectDialog selectDialog) throws Exception { + selectDialog.setIconImage(TunedUtil.getImage(searchField.getSelectButton().getLabelProvider().getIcon(getClient()))); + } + + public String getSearchText() { return searchText; } @@ -210,14 +244,9 @@ public abstract class AbstractSearchPanel extends Fi SearchTask task = (SearchTask) evt.getSource(); try { - SearchResult selectedResult = selectSearchResult(task); + SearchResult selectedResult = task.chooseSearchResult(); if (selectedResult == null) { - if (task.get().isEmpty()) { - // no search results - MessageManager.showWarning(String.format("\"%s\" has not been found.", task.getSearchText())); - } - tab.close(); return; } @@ -245,31 +274,6 @@ public abstract class AbstractSearchPanel extends Fi } - - private SearchResult selectSearchResult(SearchTask task) throws Exception { - Collection searchResults = task.get(); - - switch (searchResults.size()) { - case 0: - return null; - case 1: - return searchResults.iterator().next(); - } - - // multiple results have been found, user must selected one - Window window = SwingUtilities.getWindowAncestor(AbstractSearchPanel.this); - - SelectDialog selectDialog = new SelectDialog(window, searchResults); - - selectDialog.setIconImage(TunedUtil.getImage(searchField.getSelectButton().getLabelProvider().getIcon(task.getClient()))); - - configureSelectDialog(selectDialog); - selectDialog.setVisible(true); - - // selected value or null if canceled by the user - return selectDialog.getSelectedValue(); - } - } diff --git a/source/net/sourceforge/filebot/ui/FileBotList.java b/source/net/sourceforge/filebot/ui/FileBotList.java index 756a89b4..a08fb910 100644 --- a/source/net/sourceforge/filebot/ui/FileBotList.java +++ b/source/net/sourceforge/filebot/ui/FileBotList.java @@ -4,14 +4,9 @@ package net.sourceforge.filebot.ui; import java.awt.BorderLayout; import java.awt.event.ActionEvent; -import java.io.File; -import java.io.PrintStream; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.swing.AbstractAction; -import javax.swing.BorderFactory; +import javax.swing.Action; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -20,59 +15,48 @@ import javax.swing.ListSelectionModel; import javax.swing.border.TitledBorder; import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler; -import net.sourceforge.filebot.ui.transfer.ExportHandler; -import net.sourceforge.filebot.ui.transfer.FileTransferable; -import net.sourceforge.filebot.ui.transfer.Saveable; -import net.sourceforge.filebot.ui.transfer.SaveableExportHandler; -import net.sourceforge.filebot.ui.transfer.TransferablePolicyImportHandler; -import net.sourceforge.filebot.ui.transferablepolicies.MutableTransferablePolicy; -import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy; +import net.sourceforge.filebot.ui.transfer.FileExportHandler; +import net.sourceforge.filebot.ui.transfer.TransferablePolicy; import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer; -import net.sourceforge.tuned.ui.SimpleListModel; import net.sourceforge.tuned.ui.TunedUtil; +import ca.odell.glazedlists.BasicEventList; +import ca.odell.glazedlists.EventList; +import ca.odell.glazedlists.swing.EventListModel; -public class FileBotList extends JPanel implements Saveable { +public class FileBotList extends JPanel { - private final JList list = new JList(new SimpleListModel()); + protected final EventList model = new BasicEventList(); - private final MutableTransferablePolicy mutableTransferablePolicy = new MutableTransferablePolicy(); + protected final JList list = new JList(new EventListModel(model)); - private final TitledBorder titledBorder; + protected final JScrollPane listScrollPane = new JScrollPane(list); - private String title; + private String title = null; - public FileBotList(boolean enableExport, boolean enableRemoveAction, boolean border) { + public FileBotList() { super(new BorderLayout()); - JScrollPane listScrollPane = new JScrollPane(list); - - if (border) { - titledBorder = new TitledBorder(""); - setBorder(titledBorder); - } else { - titledBorder = null; - listScrollPane.setBorder(BorderFactory.createEmptyBorder()); - } + setBorder(new TitledBorder(getTitle())); list.setCellRenderer(new DefaultFancyListCellRenderer()); list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + list.setTransferHandler(new DefaultTransferHandler(null, null)); + list.setDragEnabled(false); + add(listScrollPane, BorderLayout.CENTER); - ExportHandler exportHandler = null; + // Shortcut DELETE, disabled by default + removeAction.setEnabled(false); - if (enableExport) - exportHandler = new SaveableExportHandler(this); - - list.setTransferHandler(new DefaultTransferHandler(new TransferablePolicyImportHandler(mutableTransferablePolicy), exportHandler)); - list.setDragEnabled(enableExport); - - if (enableRemoveAction) { - // Shortcut DELETE - TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction); - } + TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction); + } + + + public EventList getModel() { + return model; } @@ -81,13 +65,34 @@ public class FileBotList extends JPanel implements Saveable { } + @Override + public DefaultTransferHandler getTransferHandler() { + return (DefaultTransferHandler) list.getTransferHandler(); + } + + public void setTransferablePolicy(TransferablePolicy transferablePolicy) { - mutableTransferablePolicy.setTransferablePolicy(transferablePolicy); + getTransferHandler().setImportHandler(transferablePolicy); } public TransferablePolicy getTransferablePolicy() { - return mutableTransferablePolicy; + TransferablePolicy importHandler = (TransferablePolicy) getTransferHandler().getImportHandler(); + + return importHandler; + } + + + public void setExportHandler(FileExportHandler exportHandler) { + getTransferHandler().setExportHandler(exportHandler); + + // enable drag if ExportHandler is available + list.setDragEnabled(exportHandler != null); + } + + + public FileExportHandler getExportHandler() { + return (FileExportHandler) getTransferHandler().getExportHandler(); } @@ -99,50 +104,18 @@ public class FileBotList extends JPanel implements Saveable { public void setTitle(String title) { this.title = title; - if (titledBorder != null) + if (getBorder() instanceof TitledBorder) { + TitledBorder titledBorder = (TitledBorder) getBorder(); titledBorder.setTitle(title); - - revalidate(); - repaint(); - } - - - public SimpleListModel getModel() { - return (SimpleListModel) list.getModel(); - } - - - public void save(File file) { - try { - PrintStream out = new PrintStream(file); - for (Object object : getModel().getCopy()) { - out.println(object.toString()); - } - - out.close(); - } catch (Exception e) { - // should not happen - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + revalidate(); + repaint(); } } - public String getDefaultFileName() { - return title + ".txt"; - } - - - public boolean isSaveable() { - return !getModel().isEmpty(); - } - - - public void load(List files) { - FileTransferable tr = new FileTransferable(files); - - if (mutableTransferablePolicy.accept(tr)) - mutableTransferablePolicy.handleTransferable(tr, false); + public Action getRemoveAction() { + return removeAction; } private final AbstractAction removeAction = new AbstractAction("Remove") { diff --git a/source/net/sourceforge/filebot/ui/FileBotListExportHandler.java b/source/net/sourceforge/filebot/ui/FileBotListExportHandler.java new file mode 100644 index 00000000..0fa43331 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/FileBotListExportHandler.java @@ -0,0 +1,43 @@ + +package net.sourceforge.filebot.ui; + + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import net.sourceforge.filebot.ui.transfer.FileExportHandler; + + +public class FileBotListExportHandler extends FileExportHandler { + + private final FileBotList list; + + + public FileBotListExportHandler(FileBotList list) { + this.list = list; + } + + + @Override + public boolean canExport() { + return !list.getModel().isEmpty(); + } + + + @Override + public void export(OutputStream out) throws IOException { + PrintStream printer = new PrintStream(out); + + for (Object entry : list.getModel()) { + printer.println(entry); + } + } + + + @Override + public String getDefaultFileName() { + return list.getTitle() + ".txt"; + } + +} diff --git a/source/net/sourceforge/filebot/ui/FileBotPanel.java b/source/net/sourceforge/filebot/ui/FileBotPanel.java index af516039..9d33895a 100644 --- a/source/net/sourceforge/filebot/ui/FileBotPanel.java +++ b/source/net/sourceforge/filebot/ui/FileBotPanel.java @@ -3,51 +3,13 @@ package net.sourceforge.filebot.ui; import java.awt.BorderLayout; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import javax.swing.Icon; import javax.swing.JPanel; -import net.sourceforge.filebot.ui.panel.analyze.AnalyzePanel; -import net.sourceforge.filebot.ui.panel.list.ListPanel; -import net.sourceforge.filebot.ui.panel.rename.RenamePanel; -import net.sourceforge.filebot.ui.panel.search.SearchPanel; -import net.sourceforge.filebot.ui.panel.sfv.SfvPanel; -import net.sourceforge.filebot.ui.panel.subtitle.SubtitlePanel; - public class FileBotPanel extends JPanel { - private static List registry; - - - public static synchronized List getAvailablePanels() { - if (registry == null) { - registry = new ArrayList(6); - - registry.add(new ListPanel()); - registry.add(new RenamePanel()); - registry.add(new AnalyzePanel()); - registry.add(new SearchPanel()); - registry.add(new SubtitlePanel()); - registry.add(new SfvPanel()); - } - - return Collections.unmodifiableList(registry); - } - - - public static FileBotPanel forName(String name) { - for (FileBotPanel panel : registry) { - if (panel.getPanelName().equalsIgnoreCase(name)) - return panel; - } - - return null; - } - private final String name; private final Icon icon; diff --git a/source/net/sourceforge/filebot/ui/FileBotPanelSelectionList.java b/source/net/sourceforge/filebot/ui/FileBotPanelSelectionList.java index 6f0bc270..787fac0e 100644 --- a/source/net/sourceforge/filebot/ui/FileBotPanelSelectionList.java +++ b/source/net/sourceforge/filebot/ui/FileBotPanelSelectionList.java @@ -18,16 +18,23 @@ import javax.swing.Timer; import javax.swing.border.EmptyBorder; import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer; -import net.sourceforge.tuned.ui.SimpleListModel; import net.sourceforge.tuned.ui.TunedUtil; +import ca.odell.glazedlists.BasicEventList; +import ca.odell.glazedlists.EventList; +import ca.odell.glazedlists.swing.EventListModel; class FileBotPanelSelectionList extends JList { private static final int SELECTDELAY_ON_DRAG_OVER = 300; + private final EventList panelModel = new BasicEventList(); + public FileBotPanelSelectionList() { + + setModel(new EventListModel(panelModel)); + setCellRenderer(new PanelCellRenderer()); setSelectionMode(ListSelectionModel.SINGLE_SELECTION); @@ -35,12 +42,15 @@ class FileBotPanelSelectionList extends JList { // initialize "drag over" panel selection new DropTarget(this, new DragDropListener()); - - setModel(new SimpleListModel(FileBotPanel.getAvailablePanels())); + } + + + public EventList getPanelModel() { + return panelModel; } - private class PanelCellRenderer extends DefaultFancyListCellRenderer { + private static class PanelCellRenderer extends DefaultFancyListCellRenderer { public PanelCellRenderer() { super(BorderLayout.CENTER, 10, 0, new Color(0x163264)); diff --git a/source/net/sourceforge/filebot/ui/FileBotTree.java b/source/net/sourceforge/filebot/ui/FileBotTree.java index 32412991..3f4a52de 100644 --- a/source/net/sourceforge/filebot/ui/FileBotTree.java +++ b/source/net/sourceforge/filebot/ui/FileBotTree.java @@ -42,12 +42,17 @@ public class FileBotTree extends JTree { } + @Override + public DefaultTreeModel getModel() { + return (DefaultTreeModel) super.getModel(); + } + + public void clear() { - DefaultTreeModel model = (DefaultTreeModel) getModel(); - DefaultMutableTreeNode root = (DefaultMutableTreeNode) model.getRoot(); - + DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot(); root.removeAllChildren(); - model.reload(root); + + getModel().reload(root); } diff --git a/source/net/sourceforge/filebot/ui/FileBotWindow.java b/source/net/sourceforge/filebot/ui/FileBotWindow.java index 299c674b..a035b57d 100644 --- a/source/net/sourceforge/filebot/ui/FileBotWindow.java +++ b/source/net/sourceforge/filebot/ui/FileBotWindow.java @@ -20,10 +20,15 @@ import javax.swing.event.ListSelectionListener; import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.resources.ResourceManager; +import net.sourceforge.filebot.ui.panel.analyze.AnalyzePanel; +import net.sourceforge.filebot.ui.panel.list.ListPanel; +import net.sourceforge.filebot.ui.panel.rename.RenamePanel; +import net.sourceforge.filebot.ui.panel.search.SearchPanel; +import net.sourceforge.filebot.ui.panel.sfv.SfvPanel; +import net.sourceforge.filebot.ui.panel.subtitle.SubtitlePanel; import net.sourceforge.tuned.MessageBus; import net.sourceforge.tuned.MessageHandler; import net.sourceforge.tuned.ui.ShadowBorder; -import net.sourceforge.tuned.ui.SimpleListModel; public class FileBotWindow extends JFrame implements ListSelectionListener { @@ -45,6 +50,7 @@ public class FileBotWindow extends JFrame implements ListSelectionListener { icons.add(ResourceManager.getImage("window.icon.big")); setIconImages(icons); + selectionListPanel.getPanelModel().addAll(createPanels()); selectionListPanel.addListSelectionListener(this); JComponent contentPane = createContentPane(); @@ -55,7 +61,21 @@ public class FileBotWindow extends JFrame implements ListSelectionListener { selectionListPanel.setSelectedIndex(Settings.getSettings().getInt(Settings.SELECTED_PANEL, 3)); - MessageBus.getDefault().addMessageHandler("panel", panelMessageHandler); + MessageBus.getDefault().addMessageHandler("panel", panelSelectMessageHandler); + } + + + private List createPanels() { + List panels = new ArrayList(); + + panels.add(new ListPanel()); + panels.add(new RenamePanel()); + panels.add(new AnalyzePanel()); + panels.add(new SearchPanel()); + panels.add(new SubtitlePanel()); + panels.add(new SfvPanel()); + + return panels; } @@ -99,7 +119,6 @@ public class FileBotWindow extends JFrame implements ListSelectionListener { } - @SuppressWarnings("unchecked") private JComponent createPageLayer() { JPanel pageLayer = new JPanel(new BorderLayout()); @@ -108,10 +127,7 @@ public class FileBotWindow extends JFrame implements ListSelectionListener { pageLayer.add(headerPanel, BorderLayout.NORTH); pageLayer.add(pagePanel, BorderLayout.CENTER); - SimpleListModel model = (SimpleListModel) selectionListPanel.getModel(); - - for (FileBotPanel panel : (List) model.getCopy()) { - panel.setVisible(false); + for (FileBotPanel panel : selectionListPanel.getPanelModel()) { pagePanel.add(panel, panel.getPanelName()); } @@ -132,12 +148,15 @@ public class FileBotWindow extends JFrame implements ListSelectionListener { return contentPane; } - private final MessageHandler panelMessageHandler = new MessageHandler() { + private final MessageHandler panelSelectMessageHandler = new MessageHandler() { @Override - public void handle(String topic, String... messages) { - for (String panel : messages) { - selectionListPanel.setSelectedValue(FileBotPanel.forName(panel), true); + public void handle(String topic, Object... messages) { + if (messages.length >= 1) { + Object panel = messages[messages.length - 1]; + + if (panel instanceof FileBotPanel) + selectionListPanel.setSelectedValue(panel, true); } } diff --git a/source/net/sourceforge/filebot/ui/FileTransferableMessageHandler.java b/source/net/sourceforge/filebot/ui/FileTransferableMessageHandler.java index ee859656..6436ae6f 100644 --- a/source/net/sourceforge/filebot/ui/FileTransferableMessageHandler.java +++ b/source/net/sourceforge/filebot/ui/FileTransferableMessageHandler.java @@ -10,45 +10,63 @@ import java.util.logging.Level; import java.util.logging.Logger; import net.sourceforge.filebot.ui.transfer.FileTransferable; -import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy; +import net.sourceforge.filebot.ui.transfer.TransferablePolicy; +import net.sourceforge.filebot.ui.transfer.TransferablePolicy.TransferAction; import net.sourceforge.tuned.MessageBus; import net.sourceforge.tuned.MessageHandler; public class FileTransferableMessageHandler implements MessageHandler { - private final String name; + private final FileBotPanel panel; private final TransferablePolicy transferablePolicy; - public FileTransferableMessageHandler(String name, TransferablePolicy transferablePolicy) { - this.name = name; + public FileTransferableMessageHandler(FileBotPanel panel, TransferablePolicy transferablePolicy) { + this.panel = panel; this.transferablePolicy = transferablePolicy; } @Override - public void handle(String topic, String... messages) { - // change panel - MessageBus.getDefault().publish("panel", name); + public void handle(String topic, Object... messages) { + // switch to panel + MessageBus.getDefault().publish("panel", panel); List files = new ArrayList(messages.length); - for (String filename : messages) { - try { - File file = new File(filename); - - if (file.exists()) { - // file might be relative, use absolute file + for (Object message : messages) { + File file = fromMessage(message); + + if (file == null) + continue; + + if (file.exists()) { + try { + // path may be relative, use absolute path files.add(file.getCanonicalFile()); - } else { - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, String.format("Invalid File: %s", filename)); + } catch (IOException e) { + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); } - } catch (IOException e) { - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + } else { + // file doesn't exist + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid File: " + file); } } - transferablePolicy.handleTransferable(new FileTransferable(files), true); + transferablePolicy.handleTransferable(new FileTransferable(files), TransferAction.PUT); } + + + private File fromMessage(Object message) { + + if (message instanceof File) + return (File) message; + + if (message instanceof String) + return new File((String) message); + + return null; + } + } diff --git a/source/net/sourceforge/filebot/ui/SelectDialog.java b/source/net/sourceforge/filebot/ui/SelectDialog.java index 185b8aff..cde494e0 100644 --- a/source/net/sourceforge/filebot/ui/SelectDialog.java +++ b/source/net/sourceforge/filebot/ui/SelectDialog.java @@ -25,8 +25,8 @@ import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import net.sourceforge.filebot.resources.ResourceManager; +import net.sourceforge.tuned.ui.ArrayListModel; import net.sourceforge.tuned.ui.DefaultFancyListCellRenderer; -import net.sourceforge.tuned.ui.SimpleListModel; import net.sourceforge.tuned.ui.TunedUtil; @@ -78,14 +78,14 @@ public class SelectDialog extends JDialog { setLocation(TunedUtil.getPreferredLocation(this)); // default selection - list.setModel(new SimpleListModel(options)); + list.setModel(new ArrayListModel(options)); list.setSelectedIndex(0); // Shortcut Enter - TunedUtil.registerActionForKeystroke(list, KeyStroke.getKeyStroke("released ENTER"), selectAction); + TunedUtil.putActionForKeystroke(list, KeyStroke.getKeyStroke("released ENTER"), selectAction); // Shortcut Escape - TunedUtil.registerActionForKeystroke(list, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction); + TunedUtil.putActionForKeystroke(list, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction); } diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/AnalyzePanel.java b/source/net/sourceforge/filebot/ui/panel/analyze/AnalyzePanel.java index b77372ff..a4b1325a 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/AnalyzePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/AnalyzePanel.java @@ -62,7 +62,7 @@ public class AnalyzePanel extends FileBotPanel { fileTreePanel.getFileTree().addPropertyChangeListener(FileTree.CONTENT_PROPERTY, fileTreeChangeListener); - MessageBus.getDefault().addMessageHandler(getPanelName(), new FileTransferableMessageHandler(getPanelName(), fileTreePanel.getFileTree().getTransferablePolicy())); + MessageBus.getDefault().addMessageHandler(getPanelName(), new FileTransferableMessageHandler(this, fileTreePanel.getFileTree().getTransferablePolicy())); } diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java b/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java index 70733372..aefd0db5 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/FileTree.java @@ -12,15 +12,12 @@ import java.util.logging.Logger; import javax.swing.SwingWorker; import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import net.sourceforge.filebot.ui.FileBotTree; import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler; -import net.sourceforge.filebot.ui.transfer.FileTransferable; -import net.sourceforge.filebot.ui.transfer.TransferablePolicyImportHandler; -import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy; +import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy; class FileTree extends FileBotTree { @@ -37,11 +34,11 @@ class FileTree extends FileBotTree { transferablePolicy = new FileTreeTransferablePolicy(this); transferablePolicy.addPropertyChangeListener(LOADING_PROPERTY, new LoadingPropertyChangeListener()); - setTransferHandler(new DefaultTransferHandler(new TransferablePolicyImportHandler(transferablePolicy), null)); + setTransferHandler(new DefaultTransferHandler(transferablePolicy, null)); } - public TransferablePolicy getTransferablePolicy() { + public FileTransferablePolicy getTransferablePolicy() { return transferablePolicy; } @@ -58,24 +55,14 @@ class FileTree extends FileBotTree { } } - DefaultTreeModel model = (DefaultTreeModel) getModel(); - for (TreeNode treeNode : changedNodes) { - model.reload(treeNode); + getModel().reload(treeNode); } contentChanged(); } - public void load(List files) { - FileTransferable tr = new FileTransferable(files); - - if (transferablePolicy.accept(tr)) - transferablePolicy.handleTransferable(tr, true); - } - - @Override public void clear() { transferablePolicy.reset(); @@ -85,14 +72,12 @@ class FileTree extends FileBotTree { } - private void contentChanged() { - synchronized (this) { - if (postProcessor != null) - postProcessor.cancel(true); - - postProcessor = new PostProcessor(); - postProcessor.execute(); - } + private synchronized void contentChanged() { + if (postProcessor != null) + postProcessor.cancel(true); + + postProcessor = new PostProcessor(); + postProcessor.execute(); }; @@ -105,7 +90,7 @@ class FileTree extends FileBotTree { firePropertyChange(FileTree.LOADING_PROPERTY, null, loading); if (!loading) { - ((DefaultTreeModel) getModel()).reload(); + getModel().reload(); contentChanged(); } } diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/FileTreePanel.java b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreePanel.java index 3e8510be..7227f832 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/FileTreePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreePanel.java @@ -39,7 +39,7 @@ class FileTreePanel extends JPanel { buttons.add(Box.createGlue()); // Shortcut DELETE - TunedUtil.registerActionForKeystroke(fileTree, KeyStroke.getKeyStroke("pressed DELETE"), removeAction); + TunedUtil.putActionForKeystroke(fileTree, KeyStroke.getKeyStroke("pressed DELETE"), removeAction); add(new LoadingOverlayPane(new JScrollPane(fileTree), ResourceManager.getIcon("loading")), BorderLayout.CENTER); add(buttons, BorderLayout.SOUTH); diff --git a/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeTransferablePolicy.java index 2959f5cc..3a712fbe 100644 --- a/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/analyze/FileTreeTransferablePolicy.java @@ -9,7 +9,7 @@ import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import net.sourceforge.filebot.FileBotUtil; -import net.sourceforge.filebot.ui.transferablepolicies.BackgroundFileTransferablePolicy; +import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy; class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy { @@ -74,7 +74,7 @@ class FileTreeTransferablePolicy extends BackgroundFileTransferablePolicy list; - public FileListTransferablePolicy(FileBotList list) { + public FileListTransferablePolicy(FileBotList list) { this.list = list; } @@ -50,12 +50,13 @@ class FileListTransferablePolicy extends FileTransferablePolicy { private void loadFolderList(List folders) { if (folders.size() == 1) { + // if only one folder was dropped, use its name as title list.setTitle(FileUtil.getFolderName(folders.get(0))); } for (File folder : folders) { for (File file : folder.listFiles()) { - list.getModel().add(FileUtil.getFolderName(file)); + list.getModel().add(FileUtil.getFileName(file)); } } } @@ -92,7 +93,7 @@ class FileListTransferablePolicy extends FileTransferablePolicy { @Override - public String getDescription() { + public String getFileFilterDescription() { return "files, folders and torrents"; } diff --git a/source/net/sourceforge/filebot/ui/panel/list/ListPanel.java b/source/net/sourceforge/filebot/ui/panel/list/ListPanel.java index c78d8a5b..4de870b9 100644 --- a/source/net/sourceforge/filebot/ui/panel/list/ListPanel.java +++ b/source/net/sourceforge/filebot/ui/panel/list/ListPanel.java @@ -23,6 +23,7 @@ import javax.swing.border.EmptyBorder; import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.ui.FileBotList; +import net.sourceforge.filebot.ui.FileBotListExportHandler; import net.sourceforge.filebot.ui.FileBotPanel; import net.sourceforge.filebot.ui.FileTransferableMessageHandler; import net.sourceforge.filebot.ui.MessageManager; @@ -36,10 +37,7 @@ public class ListPanel extends FileBotPanel { private static final String INDEX_VARIABLE = ""; - private FileBotList list = new FileBotList(true, true, true); - - private SaveAction saveAction = new SaveAction(list); - private LoadAction loadAction = new LoadAction(list.getTransferablePolicy()); + private FileBotList list = new FileBotList(); private JTextField textField = new JTextField(String.format("Name - %s", INDEX_VARIABLE), 25); private SpinnerNumberModel fromSpinnerModel = new SpinnerNumberModel(1, 0, Integer.MAX_VALUE, 1); @@ -50,14 +48,17 @@ public class ListPanel extends FileBotPanel { super("List", ResourceManager.getIcon("panel.list")); list.setTransferablePolicy(new FileListTransferablePolicy(list)); + list.setExportHandler(new FileBotListExportHandler(list)); + + list.getRemoveAction().setEnabled(true); Box buttons = Box.createHorizontalBox(); buttons.setBorder(new EmptyBorder(5, 5, 5, 5)); buttons.add(Box.createHorizontalGlue()); - buttons.add(new JButton(loadAction)); + buttons.add(new JButton(new LoadAction(list.getTransferablePolicy()))); buttons.add(Box.createHorizontalStrut(5)); - buttons.add(new JButton(saveAction)); + buttons.add(new JButton(new SaveAction(list.getExportHandler()))); buttons.add(Box.createHorizontalGlue()); list.add(buttons, BorderLayout.SOUTH); @@ -88,9 +89,9 @@ public class ListPanel extends FileBotPanel { add(spinners, BorderLayout.NORTH); add(list, BorderLayout.CENTER); - TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), createAction); + TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), createAction); - MessageBus.getDefault().addMessageHandler(getPanelName(), new FileTransferableMessageHandler(getPanelName(), list.getTransferablePolicy())); + MessageBus.getDefault().addMessageHandler(getPanelName(), new FileTransferableMessageHandler(this, list.getTransferablePolicy())); } @@ -144,7 +145,8 @@ public class ListPanel extends FileBotPanel { index += increment; } while (index != (to + increment)); - list.getModel().set(entries); + list.getModel().clear(); + list.getModel().addAll(entries); } }; diff --git a/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java index 669bbe07..c8346815 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/FilesListTransferablePolicy.java @@ -8,17 +8,17 @@ import java.util.List; import net.sourceforge.filebot.FileBotUtil; import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry; -import net.sourceforge.filebot.ui.transferablepolicies.FileTransferablePolicy; -import net.sourceforge.tuned.ui.SimpleListModel; +import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy; +import ca.odell.glazedlists.EventList; class FilesListTransferablePolicy extends FileTransferablePolicy { - private final SimpleListModel model; + private final EventList model; - public FilesListTransferablePolicy(SimpleListModel listModel) { - this.model = listModel; + public FilesListTransferablePolicy(EventList model) { + this.model = model; } @@ -47,7 +47,7 @@ class FilesListTransferablePolicy extends FileTransferablePolicy { @Override - public String getDescription() { + public String getFileFilterDescription() { return "files and folders"; } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java b/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java index 15156cf3..2229bf8c 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java @@ -18,6 +18,7 @@ import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import net.sourceforge.filebot.resources.ResourceManager; +import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry; import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; import net.sourceforge.filebot.ui.panel.rename.matcher.Match; import net.sourceforge.filebot.ui.panel.rename.matcher.Matcher; @@ -32,8 +33,8 @@ class MatchAction extends AbstractAction { private CompositeSimilarityMetric metrics; - private final RenameList namesList; - private final RenameList filesList; + private final RenameList namesList; + private final RenameList filesList; private boolean matchName2File; @@ -41,7 +42,7 @@ class MatchAction extends AbstractAction { public static final String MATCH_FILES_2_NAMES_DESCRIPTION = "Match files to names"; - public MatchAction(RenameList namesList, RenameList filesList) { + public MatchAction(RenameList namesList, RenameList filesList) { super("Match"); this.namesList = namesList; @@ -77,13 +78,14 @@ class MatchAction extends AbstractAction { } + @SuppressWarnings("unchecked") public void actionPerformed(ActionEvent evt) { JComponent source = (JComponent) evt.getSource(); SwingUtilities.getRoot(source).setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); - RenameList primaryList = matchName2File ? namesList : filesList; - RenameList secondaryList = matchName2File ? filesList : namesList; + RenameList primaryList = (RenameList) (matchName2File ? namesList : filesList); + RenameList secondaryList = (RenameList) (matchName2File ? filesList : namesList); BackgroundMatcher backgroundMatcher = new BackgroundMatcher(primaryList, secondaryList, metrics); SwingWorkerProgressMonitor monitor = new SwingWorkerProgressMonitor(SwingUtilities.getWindowAncestor(source), backgroundMatcher); @@ -109,15 +111,15 @@ class MatchAction extends AbstractAction { } - private class BackgroundMatcher extends SwingWorker, Void> { + private static class BackgroundMatcher extends SwingWorker, Void> { - private final RenameList primaryList; - private final RenameList secondaryList; + private final RenameList primaryList; + private final RenameList secondaryList; private final Matcher matcher; - public BackgroundMatcher(RenameList primaryList, RenameList secondaryList, SimilarityMetric similarityMetric) { + public BackgroundMatcher(RenameList primaryList, RenameList secondaryList, SimilarityMetric similarityMetric) { this.primaryList = primaryList; this.secondaryList = secondaryList; @@ -167,7 +169,6 @@ class MatchAction extends AbstractAction { primaryList.getModel().clear(); secondaryList.getModel().clear(); - for (Match match : matches) { primaryList.getModel().add(match.getA()); secondaryList.getModel().add(match.getB()); diff --git a/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java index f7d0bd03..c6d7b68e 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/NamesListTransferablePolicy.java @@ -2,6 +2,7 @@ package net.sourceforge.filebot.ui.panel.rename; +import java.awt.datatransfer.Transferable; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -19,20 +20,35 @@ import net.sourceforge.filebot.torrent.Torrent; import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; import net.sourceforge.filebot.ui.panel.rename.entry.StringEntry; import net.sourceforge.filebot.ui.panel.rename.entry.TorrentEntry; -import net.sourceforge.filebot.ui.transferablepolicies.CompositeTransferablePolicy; -import net.sourceforge.filebot.ui.transferablepolicies.TextTransferablePolicy; +import net.sourceforge.filebot.ui.transfer.StringTransferablePolicy; -class NamesListTransferablePolicy extends CompositeTransferablePolicy { +class NamesListTransferablePolicy extends FilesListTransferablePolicy { - private final RenameList list; + private final RenameList list; + + private final TextPolicy textPolicy = new TextPolicy(); - public NamesListTransferablePolicy(RenameList list) { - this.list = list; + public NamesListTransferablePolicy(RenameList list) { + super(list.getModel()); - addPolicy(new FilePolicy()); - addPolicy(new TextPolicy()); + this.list = list; + } + + + @Override + public boolean accept(Transferable tr) { + return textPolicy.accept(tr) || super.accept(tr); + } + + + @Override + public void handleTransferable(Transferable tr, TransferAction action) { + if (super.accept(tr)) + super.handleTransferable(tr, action); + else if (textPolicy.accept(tr)) + textPolicy.handleTransferable(tr, action); } @@ -57,90 +73,81 @@ class NamesListTransferablePolicy extends CompositeTransferablePolicy { @Override - protected void clear() { - list.getModel().clear(); + protected void load(List files) { + + if (FileBotUtil.containsOnlyListFiles(files)) { + loadListFiles(files); + } else if (FileBotUtil.containsOnlyTorrentFiles(files)) { + loadTorrentFiles(files); + } else { + super.load(files); + } + } + + + private void loadListFiles(List files) { + try { + List entries = new ArrayList(); + + for (File file : files) { + BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + + String line = null; + + while ((line = in.readLine()) != null) { + if (line.trim().length() > 0) { + entries.add(new StringEntry(line)); + } + } + + in.close(); + } + + submit(entries); + } catch (IOException e) { + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + } + } + + + private void loadTorrentFiles(List files) { + try { + List entries = new ArrayList(); + + for (File file : files) { + Torrent torrent = new Torrent(file); + + for (Torrent.Entry entry : torrent.getFiles()) { + entries.add(new TorrentEntry(entry)); + } + } + + submit(entries); + } catch (IOException e) { + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + } + } + + + @Override + public String getFileFilterDescription() { + return "text files and torrent files"; } - private class FilePolicy extends FilesListTransferablePolicy { + private class TextPolicy extends StringTransferablePolicy { - public FilePolicy() { - super(list.getModel()); + @Override + protected void clear() { + NamesListTransferablePolicy.this.clear(); } @Override - protected void load(List files) { - - if (FileBotUtil.containsOnlyListFiles(files)) { - loadListFiles(files); - } else if (FileBotUtil.containsOnlyTorrentFiles(files)) { - loadTorrentFiles(files); - } else { - super.load(files); - } - } - - - private void loadListFiles(List files) { - try { - List entries = new ArrayList(); - - for (File file : files) { - BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file))); - - String line = null; - - while ((line = in.readLine()) != null) { - if (line.trim().length() > 0) { - entries.add(new StringEntry(line)); - } - } - - in.close(); - } - - submit(entries); - } catch (IOException e) { - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); - } - } - - - private void loadTorrentFiles(List files) { - try { - List entries = new ArrayList(); - - for (File file : files) { - Torrent torrent = new Torrent(file); - - for (Torrent.Entry entry : torrent.getFiles()) { - entries.add(new TorrentEntry(entry)); - } - } - - submit(entries); - } catch (IOException e) { - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); - } - } - - - @Override - public String getDescription() { - return "text files and torrent files"; - } - - }; - - - private class TextPolicy extends TextTransferablePolicy { - - @Override - protected void load(String text) { + protected void load(String string) { List entries = new ArrayList(); - String[] lines = text.split("\r?\n"); + String[] lines = string.split("\r?\n"); for (String line : lines) { @@ -153,11 +160,6 @@ class NamesListTransferablePolicy extends CompositeTransferablePolicy { } } - - @Override - public String getDescription() { - return "lines of text"; - } - }; + } } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenameAction.java b/source/net/sourceforge/filebot/ui/panel/rename/RenameAction.java index feb49bf5..3111ac97 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenameAction.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenameAction.java @@ -18,11 +18,11 @@ import net.sourceforge.tuned.FileUtil; public class RenameAction extends AbstractAction { - private final RenameList namesList; - private final RenameList filesList; + private final RenameList namesList; + private final RenameList filesList; - public RenameAction(RenameList namesList, RenameList filesList) { + public RenameAction(RenameList namesList, RenameList filesList) { super("Rename", ResourceManager.getIcon("action.rename")); this.namesList = namesList; this.filesList = filesList; @@ -33,7 +33,7 @@ public class RenameAction extends AbstractAction { public void actionPerformed(ActionEvent e) { List nameEntries = namesList.getEntries(); - List fileEntries = filesList.getEntries(); + List fileEntries = filesList.getEntries(); int minLength = Math.min(nameEntries.size(), fileEntries.size()); @@ -41,7 +41,7 @@ public class RenameAction extends AbstractAction { int errors = 0; for (i = 0; i < minLength; i++) { - FileEntry fileEntry = (FileEntry) fileEntries.get(i); + FileEntry fileEntry = fileEntries.get(i); File f = fileEntry.getFile(); String newName = nameEntries.get(i).toString() + FileUtil.getExtension(f, true); diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenameList.java b/source/net/sourceforge/filebot/ui/panel/rename/RenameList.java index e7fe060b..84996d86 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenameList.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenameList.java @@ -6,12 +6,12 @@ import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.ArrayList; import java.util.List; import javax.swing.AbstractAction; import javax.swing.Box; import javax.swing.JButton; -import javax.swing.JList; import javax.swing.JViewport; import javax.swing.ListSelectionModel; import javax.swing.border.EmptyBorder; @@ -20,25 +20,26 @@ import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.ui.FileBotList; import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; import net.sourceforge.filebot.ui.transfer.LoadAction; +import net.sourceforge.filebot.ui.transfer.TransferablePolicy; -class RenameList extends FileBotList { +class RenameList extends FileBotList { + + private JButton loadButton = new JButton(); + public RenameList() { - super(false, true, true); - Box buttons = Box.createHorizontalBox(); buttons.setBorder(new EmptyBorder(5, 5, 5, 5)); buttons.add(Box.createGlue()); buttons.add(new JButton(downAction)); buttons.add(new JButton(upAction)); buttons.add(Box.createHorizontalStrut(10)); - buttons.add(new JButton(loadAction)); + buttons.add(loadButton); buttons.add(Box.createGlue()); add(buttons, BorderLayout.SOUTH); - JList list = getListComponent(); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); list.addMouseListener(dndReorderMouseAdapter); @@ -46,69 +47,76 @@ class RenameList extends FileBotList { JViewport viewport = (JViewport) list.getParent(); viewport.setBackground(list.getBackground()); + + getRemoveAction().setEnabled(true); } - @SuppressWarnings("unchecked") - public List getEntries() { - return (List) getModel().getCopy(); + @Override + public void setTransferablePolicy(TransferablePolicy transferablePolicy) { + super.setTransferablePolicy(transferablePolicy); + loadButton.setAction(new LoadAction(transferablePolicy)); + } + + + public List getEntries() { + return new ArrayList(getModel()); + } + + + private boolean moveEntry(int fromIndex, int toIndex) { + if (toIndex < 0 || toIndex >= getModel().size()) + return false; + + getModel().add(toIndex, getModel().remove(fromIndex)); + return true; } private final AbstractAction upAction = new AbstractAction(null, ResourceManager.getIcon("action.up")) { public void actionPerformed(ActionEvent e) { - int index = getListComponent().getSelectedIndex(); + int selectedIndex = getListComponent().getSelectedIndex(); + int toIndex = selectedIndex + 1; - if (index <= 0) // first element - return; - - Object object = getModel().remove(index); - - int newIndex = index - 1; - getModel().add(newIndex, object); - getListComponent().setSelectedIndex(newIndex); + if (moveEntry(selectedIndex, toIndex)) { + getListComponent().setSelectedIndex(toIndex); + } } }; private final AbstractAction downAction = new AbstractAction(null, ResourceManager.getIcon("action.down")) { public void actionPerformed(ActionEvent e) { - int index = getListComponent().getSelectedIndex(); + int selectedIndex = getListComponent().getSelectedIndex(); + int toIndex = selectedIndex - 1; - if (index >= getModel().getSize() - 1) // last element - return; - - Object object = getModel().remove(index); - - int newIndex = index + 1; - getModel().add(newIndex, object); - getListComponent().setSelectedIndex(newIndex); + if (moveEntry(selectedIndex, toIndex)) { + getListComponent().setSelectedIndex(toIndex); + } } }; - protected final LoadAction loadAction = new LoadAction(getTransferablePolicy()); - - private MouseAdapter dndReorderMouseAdapter = new MouseAdapter() { + private final MouseAdapter dndReorderMouseAdapter = new MouseAdapter() { - private int from = -1; + private int fromIndex = -1; @Override public void mousePressed(MouseEvent m) { - from = getListComponent().getSelectedIndex(); + fromIndex = getListComponent().getSelectedIndex(); } @Override public void mouseDragged(MouseEvent m) { - int to = getListComponent().getSelectedIndex(); + int toIndex = getListComponent().getSelectedIndex(); - if (to == from) + if (toIndex == fromIndex) return; - Object object = getModel().remove(from); - getModel().add(to, object); - from = to; + moveEntry(fromIndex, toIndex); + + fromIndex = toIndex; } }; diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java index ea9caa31..094f1322 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java @@ -27,12 +27,14 @@ import javax.swing.event.ListDataListener; import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.ui.FileBotPanel; +import net.sourceforge.filebot.ui.panel.rename.entry.FileEntry; +import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; public class RenamePanel extends FileBotPanel { - private RenameList namesList = new RenameList(); - private RenameList filesList = new RenameList(); + private RenameList namesList = new RenameList(); + private RenameList filesList = new RenameList(); private MatchAction matchAction = new MatchAction(namesList, filesList); @@ -52,23 +54,23 @@ public class RenamePanel extends FileBotPanel { filesList.setTitle("Files"); filesList.setTransferablePolicy(new FilesListTransferablePolicy(filesList.getModel())); - RenameListCellRenderer cellrenderer = new RenameListCellRenderer(namesList.getModel(), filesList.getModel()); + JList namesListComponent = namesList.getListComponent(); + JList filesListComponent = filesList.getListComponent(); - namesList.getListComponent().setCellRenderer(cellrenderer); - filesList.getListComponent().setCellRenderer(cellrenderer); + RenameListCellRenderer cellrenderer = new RenameListCellRenderer(namesListComponent.getModel(), filesListComponent.getModel()); - JList list1 = namesList.getListComponent(); - JList list2 = filesList.getListComponent(); + namesListComponent.setCellRenderer(cellrenderer); + filesListComponent.setCellRenderer(cellrenderer); ListSelectionModel selectionModel = new DefaultListSelectionModel(); selectionModel.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - namesList.getListComponent().setSelectionModel(selectionModel); - filesList.getListComponent().setSelectionModel(selectionModel); + namesListComponent.setSelectionModel(selectionModel); + filesListComponent.setSelectionModel(selectionModel); - viewPortSynchroniser = new ViewPortSynchronizer((JViewport) list1.getParent(), (JViewport) list2.getParent()); + viewPortSynchroniser = new ViewPortSynchronizer((JViewport) namesListComponent.getParent(), (JViewport) filesListComponent.getParent()); - similarityPanel = new SimilarityPanel(list1, list2); + similarityPanel = new SimilarityPanel(namesListComponent, filesListComponent); similarityPanel.setVisible(false); similarityPanel.setMetrics(matchAction.getMetrics()); @@ -89,8 +91,8 @@ public class RenamePanel extends FileBotPanel { add(box, BorderLayout.CENTER); - namesList.getModel().addListDataListener(repaintOnDataChange); - filesList.getModel().addListDataListener(repaintOnDataChange); + namesListComponent.getModel().addListDataListener(repaintOnDataChange); + filesListComponent.getModel().addListDataListener(repaintOnDataChange); } @@ -120,7 +122,7 @@ public class RenamePanel extends FileBotPanel { return centerBox; } - private ListDataListener repaintOnDataChange = new ListDataListener() { + private final ListDataListener repaintOnDataChange = new ListDataListener() { public void contentsChanged(ListDataEvent e) { diff --git a/source/net/sourceforge/filebot/ui/panel/rename/ValidateNamesDialog.java b/source/net/sourceforge/filebot/ui/panel/rename/ValidateNamesDialog.java index 52c7a4de..1ee6e50f 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/ValidateNamesDialog.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/ValidateNamesDialog.java @@ -29,7 +29,7 @@ import javax.swing.border.EmptyBorder; import net.sourceforge.filebot.FileBotUtil; import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.ui.panel.rename.entry.ListEntry; -import net.sourceforge.tuned.ui.SimpleListModel; +import net.sourceforge.tuned.ui.ArrayListModel; import net.sourceforge.tuned.ui.TunedUtil; @@ -51,7 +51,7 @@ public class ValidateNamesDialog extends JDialog { setDefaultCloseOperation(DISPOSE_ON_CLOSE); - JList list = new JList(new SimpleListModel(entries)); + JList list = new JList(new ArrayListModel(entries)); list.setEnabled(false); list.setCellRenderer(new HighlightListCellRenderer(FileBotUtil.INVALID_CHARACTERS_PATTERN, new CharacterHighlightPainter(new Color(0xFF4200), new Color(0xFF1200)), 4)); @@ -89,7 +89,7 @@ public class ValidateNamesDialog extends JDialog { setPreferredSize(new Dimension(365, 280)); pack(); - TunedUtil.registerActionForKeystroke(c, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction); + TunedUtil.putActionForKeystroke(c, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction); } @@ -176,7 +176,7 @@ public class ValidateNamesDialog extends JDialog { protected void actionPropertyChanged(Action action, String propertyName) { super.actionPropertyChanged(action, propertyName); - if (propertyName == ContinueAction.ALPHA) { + if (propertyName.equals(ContinueAction.ALPHA)) { alpha = getAlpha(action); } } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/entry/FileEntry.java b/source/net/sourceforge/filebot/ui/panel/rename/entry/FileEntry.java index 1162e833..f4d8c9ef 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/entry/FileEntry.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/entry/FileEntry.java @@ -38,4 +38,5 @@ public class FileEntry extends AbstractFileEntry { public File getFile() { return file; } + } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetric.java b/source/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetric.java index f731ae34..6c565171 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetric.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/metric/AbstractNameSimilarityMetric.java @@ -16,10 +16,8 @@ public abstract class AbstractNameSimilarityMetric implements SimilarityMetric { protected String normalize(String name) { name = stripChecksum(name); name = normalizeSeparators(name); - name = name.trim(); - name = name.toLowerCase(); - return name; + return name.trim().toLowerCase(); } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetric.java b/source/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetric.java index bf2d0926..20b3d513 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetric.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/metric/NumericSimilarityMetric.java @@ -46,7 +46,7 @@ public class NumericSimilarityMetric extends AbstractNameSimilarityMetric { private static class NumberTokeniser implements InterfaceTokeniser { - private final String delimiter = "(\\D)+"; + private static final String delimiter = "(\\D)+"; @Override diff --git a/source/net/sourceforge/filebot/ui/panel/search/EpisodeListPanel.java b/source/net/sourceforge/filebot/ui/panel/search/EpisodeListPanel.java index 4cb93d4a..380173ce 100644 --- a/source/net/sourceforge/filebot/ui/panel/search/EpisodeListPanel.java +++ b/source/net/sourceforge/filebot/ui/panel/search/EpisodeListPanel.java @@ -8,9 +8,11 @@ import javax.swing.JComponent; import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.ui.FileBotList; import net.sourceforge.filebot.ui.FileBotTabComponent; +import net.sourceforge.filebot.ui.transfer.DefaultListExportHandler; +import net.sourceforge.filebot.web.Episode; -public class EpisodeListPanel extends FileBotList { +public class EpisodeListPanel extends FileBotList { private final FileBotTabComponent tabComponent = new FileBotTabComponent(); @@ -20,7 +22,11 @@ public class EpisodeListPanel extends FileBotList { public EpisodeListPanel() { - super(true, true, false); + setExportHandler(new DefaultListExportHandler(list)); + getRemoveAction().setEnabled(true); + + setBorder(null); + listScrollPane.setBorder(null); } diff --git a/source/net/sourceforge/filebot/ui/panel/search/SearchPanel.java b/source/net/sourceforge/filebot/ui/panel/search/SearchPanel.java index f062d7cb..5831dd55 100644 --- a/source/net/sourceforge/filebot/ui/panel/search/SearchPanel.java +++ b/source/net/sourceforge/filebot/ui/panel/search/SearchPanel.java @@ -3,7 +3,6 @@ package net.sourceforge.filebot.ui.panel.search; import java.awt.BorderLayout; -import java.awt.Component; import java.awt.Window; import java.awt.event.ActionEvent; import java.beans.PropertyChangeEvent; @@ -32,12 +31,14 @@ import javax.swing.SwingWorker; import javax.swing.border.EmptyBorder; import net.sourceforge.filebot.resources.ResourceManager; +import net.sourceforge.filebot.ui.FileBotList; import net.sourceforge.filebot.ui.FileBotPanel; import net.sourceforge.filebot.ui.HistoryPanel; import net.sourceforge.filebot.ui.MessageManager; import net.sourceforge.filebot.ui.SelectDialog; +import net.sourceforge.filebot.ui.transfer.AdaptiveFileExportHandler; +import net.sourceforge.filebot.ui.transfer.FileExportHandler; import net.sourceforge.filebot.ui.transfer.SaveAction; -import net.sourceforge.filebot.ui.transfer.Saveable; import net.sourceforge.filebot.web.AnidbClient; import net.sourceforge.filebot.web.Episode; import net.sourceforge.filebot.web.EpisodeListClient; @@ -118,9 +119,9 @@ public class SearchPanel extends FileBotPanel { this.add(mainPanel, BorderLayout.CENTER); - TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction); - TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("UP"), upAction); - TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("DOWN"), downAction); + TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("ENTER"), searchAction); + TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("UP"), upAction); + TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("DOWN"), downAction); } @@ -179,21 +180,27 @@ public class SearchPanel extends FileBotPanel { } }; - private final SaveAction saveAction = new SaveAction(null) { + private final SaveAction saveAction = new SaveAction(new SelectedTabExportHandler()); + + + private class SelectedTabExportHandler extends AdaptiveFileExportHandler { + /** + * @return the FileExportHandler of the currently selected tab + */ @Override - public void actionPerformed(ActionEvent e) { - Component comp = tabbedPane.getSelectedComponent(); - - if (comp instanceof Saveable) { - setSaveable((Saveable) comp); - super.actionPerformed(e); + protected FileExportHandler getExportHandler() { + try { + FileBotList list = (FileBotList) tabbedPane.getSelectedComponent(); + return list.getExportHandler(); + } catch (ClassCastException e) { + // selected component is the history panel + return null; } } - - }; - + } + private class SearchTask extends SwingWorker, Void> { private final String query; @@ -267,14 +274,7 @@ public class SearchPanel extends FileBotPanel { } SearchResult selectedResult = null; - /* - * NEEDED??? exact find without cache??? - /// TODO: ?????? - if (task.client.getFoundName(task.query) != null) { - // a show matching the search term exactly has already been found - showname = task.client.getFoundName(task.query); - }*/ - + if (searchResults.size() == 1) { // only one show found, select this one selectedResult = searchResults.iterator().next(); diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/Checksum.java b/source/net/sourceforge/filebot/ui/panel/sfv/Checksum.java index 4d4bc0c5..e96e4e50 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/Checksum.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/Checksum.java @@ -92,7 +92,7 @@ public class Checksum { } - public Integer getProgress() { + public synchronized Integer getProgress() { if (state == State.INPROGRESS) return computationTask.getProgress(); @@ -130,8 +130,10 @@ public class Checksum { @Override public void done(PropertyChangeEvent evt) { try { - if (!computationTask.isCancelled()) { - setChecksum(computationTask.get()); + ChecksumComputationTask task = (ChecksumComputationTask) evt.getSource(); + + if (!task.isCancelled()) { + setChecksum(task.get()); } } catch (Exception e) { // might happen if file system is corrupt (e.g. CRC errors) diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumComputationService.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumComputationService.java index 101ff3ff..dd7b35e2 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumComputationService.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumComputationService.java @@ -2,6 +2,7 @@ package net.sourceforge.filebot.ui.panel.sfv; +import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; @@ -15,6 +16,8 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.SwingUtilities; @@ -23,36 +26,37 @@ import net.sourceforge.tuned.DefaultThreadFactory; public class ChecksumComputationService { - public static final String ACTIVE_PROPERTY = "ACTIVE_PROPERTY"; - public static final String REMAINING_TASK_COUNT_PROPERTY = "REMAINING_TASK_COUNT_PROPERTY"; - - private static final ThreadFactory checksumComputationThreadFactory = new DefaultThreadFactory("ChecksumComputationPool", Thread.MIN_PRIORITY); - - private static final ChecksumComputationService service = new ChecksumComputationService(); - - - public static ChecksumComputationService getService() { - return service; - } + public static final String ACTIVE_PROPERTY = "active"; + public static final String REMAINING_TASK_COUNT_PROPERTY = "remainingTaskCount"; private final Map executors = new HashMap(); private final AtomicInteger activeSessionTaskCount = new AtomicInteger(0); private final AtomicInteger remainingTaskCount = new AtomicInteger(0); - private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); + private final ThreadFactory threadFactory; + + /** + * Property change events will be fired on the event dispatch thread + */ + private final SwingWorkerPropertyChangeSupport propertyChangeSupport = new SwingWorkerPropertyChangeSupport(this); - private ChecksumComputationService() { - // hide constructor + public ChecksumComputationService() { + this(new DefaultThreadFactory("ChecksumComputationPool", Thread.MIN_PRIORITY)); } - public Checksum getChecksum(File file, File workerQueueKey) { + public ChecksumComputationService(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + } + + + public Checksum schedule(File file, File workerQueue) { ChecksumComputationTask task = new ChecksumComputationTask(file); Checksum checksum = new Checksum(task); - getExecutor(workerQueueKey).execute(task); + getExecutor(workerQueue).execute(task); return checksum; } @@ -63,21 +67,18 @@ public class ChecksumComputationService { } - private void deactivate(boolean shutdownNow) { - synchronized (executors) { - for (ChecksumComputationExecutor executor : executors.values()) { - if (shutdownNow) { - executor.shutdownNow(); - } else { - executor.shutdown(); - } - } - - executors.clear(); - - activeSessionTaskCount.set(0); - remainingTaskCount.set(0); + private synchronized void deactivate(boolean shutdownNow) { + for (ChecksumComputationExecutor executor : executors.values()) { + if (shutdownNow) + executor.shutdownNow(); + else + executor.shutdown(); } + + executors.clear(); + + activeSessionTaskCount.set(0); + remainingTaskCount.set(0); } @@ -96,26 +97,22 @@ public class ChecksumComputationService { } - public void purge() { - synchronized (executors) { - for (ChecksumComputationExecutor executor : executors.values()) { - executor.purge(); - } + public synchronized void purge() { + for (ChecksumComputationExecutor executor : executors.values()) { + executor.purge(); } } - private ChecksumComputationExecutor getExecutor(File workerQueueKey) { - synchronized (executors) { - ChecksumComputationExecutor executor = executors.get(workerQueueKey); - - if (executor == null) { - executor = new ChecksumComputationExecutor(); - executors.put(workerQueueKey, executor); - } - - return executor; + private synchronized ChecksumComputationExecutor getExecutor(File workerQueue) { + ChecksumComputationExecutor executor = executors.get(workerQueue); + + if (executor == null) { + executor = new ChecksumComputationExecutor(); + executors.put(workerQueue, executor); } + + return executor; } @@ -125,7 +122,7 @@ public class ChecksumComputationService { public ChecksumComputationExecutor() { - super(MINIMUM_POOL_SIZE, MINIMUM_POOL_SIZE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), checksumComputationThreadFactory); + super(MINIMUM_POOL_SIZE, MINIMUM_POOL_SIZE, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), threadFactory); } @@ -134,13 +131,7 @@ public class ChecksumComputationService { // for lots of files, use multiple threads // e.g 50 files ~ 1 thread, 1000 files ~ 3 threads, 40000 files ~ 5 threads - int preferredPoolSize = MINIMUM_POOL_SIZE; - - int queueSize = getQueue().size(); - - if (queueSize > 0) { - preferredPoolSize += Math.log10(Math.max(queueSize / 10, 1)); - } + int preferredPoolSize = (int) Math.log10(Math.max(getQueue().size() / 10, MINIMUM_POOL_SIZE)); if (getCorePoolSize() != preferredPoolSize) { setCorePoolSize(preferredPoolSize); @@ -179,8 +170,8 @@ public class ChecksumComputationService { for (ChecksumComputationTask task : cancelledTasks) { remove(task); } - } catch (ConcurrentModificationException ex) { - + } catch (ConcurrentModificationException e) { + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); } } @@ -218,12 +209,12 @@ public class ChecksumComputationService { private void setActive(boolean active) { - SwingUtilities.invokeLater(new FirePropertyChangeRunnable(ACTIVE_PROPERTY, active)); + propertyChangeSupport.firePropertyChange(ACTIVE_PROPERTY, null, active); } private void fireRemainingTaskCountChange() { - SwingUtilities.invokeLater(new FirePropertyChangeRunnable(REMAINING_TASK_COUNT_PROPERTY, getRemainingTaskCount())); + propertyChangeSupport.firePropertyChange(REMAINING_TASK_COUNT_PROPERTY, null, getRemainingTaskCount()); } @@ -237,21 +228,23 @@ public class ChecksumComputationService { } - private class FirePropertyChangeRunnable implements Runnable { + private static class SwingWorkerPropertyChangeSupport extends PropertyChangeSupport { - private final String property; - private final Object newValue; - - - public FirePropertyChangeRunnable(String property, Object newValue) { - this.property = property; - this.newValue = newValue; + public SwingWorkerPropertyChangeSupport(Object sourceBean) { + super(sourceBean); } @Override - public void run() { - propertyChangeSupport.firePropertyChange(property, null, newValue); + public void firePropertyChange(final PropertyChangeEvent evt) { + SwingUtilities.invokeLater(new Runnable() { + + @Override + public void run() { + SwingWorkerPropertyChangeSupport.super.firePropertyChange(evt); + } + + }); } } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumRow.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumRow.java index c94e2602..585ba2d0 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumRow.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumRow.java @@ -16,24 +16,28 @@ public class ChecksumRow { private HashMap checksumMap = new HashMap(); - private Long checksumFromFileName = null; + /** + * Checksum that is embedded in the file name (e.g. My File [49A93C5F].txt) + */ + private Long embeddedChecksum = null; public static enum State { - OK, UNKNOWN, WARNING, ERROR; + OK, + WARNING, + ERROR, + UNKNOWN; } public ChecksumRow(String name) { this.name = name; - // look for a patter like [49A93C5F] - Pattern pattern = Pattern.compile(".*\\[(\\p{XDigit}{8})\\].*"); - Matcher matcher = pattern.matcher(getName()); + // look for a checksum pattern like [49A93C5F] + Matcher matcher = Pattern.compile("\\[(\\p{XDigit}{8})\\]").matcher(name); - if (matcher.matches()) { - String checksumString = matcher.group(matcher.groupCount()); - checksumFromFileName = Long.parseLong(checksumString, 16); + if (matcher.find()) { + embeddedChecksum = Long.parseLong(matcher.group(1), 16); } } @@ -61,9 +65,9 @@ public class ChecksumRow { return State.ERROR; } - if (!checksums.isEmpty() && checksumFromFileName != null) { - // check if the checksum in the filename matches - if (!checksums.contains(checksumFromFileName)) + if (!checksums.isEmpty() && embeddedChecksum != null) { + // check if the embedded checksum matches + if (!checksums.contains(embeddedChecksum)) return State.WARNING; } @@ -71,8 +75,8 @@ public class ChecksumRow { } - public Checksum getChecksum(File columnRoot) { - return checksumMap.get(columnRoot); + public Checksum getChecksum(File column) { + return checksumMap.get(column); } @@ -81,13 +85,13 @@ public class ChecksumRow { } - public void putChecksum(File columnRoot, Checksum checksum) { - checksumMap.put(columnRoot, checksum); + public void putChecksum(File column, Checksum checksum) { + checksumMap.put(column, checksum); } - public void removeChecksum(File columnRoot) { - checksumMap.remove(columnRoot); + public void removeChecksum(File column) { + checksumMap.remove(column); } } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableExportHandler.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableExportHandler.java new file mode 100644 index 00000000..97ea6649 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableExportHandler.java @@ -0,0 +1,90 @@ + +package net.sourceforge.filebot.ui.panel.sfv; + + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.Map.Entry; + +import net.sourceforge.filebot.ui.transfer.FileExportHandler; +import net.sourceforge.tuned.FileUtil; + + +public class ChecksumTableExportHandler extends FileExportHandler { + + private final ChecksumTableModel model; + + + public ChecksumTableExportHandler(ChecksumTableModel model) { + this.model = model; + } + + + @Override + public boolean canExport() { + return model.getRowCount() > 0 && model.getChecksumColumnCount() > 0; + } + + + @Override + public void export(OutputStream out) throws IOException { + export(out, model.getChecksumColumn(0)); + } + + + @Override + public String getDefaultFileName() { + return getDefaultFileName(model.getChecksumColumn(0)); + } + + + public void export(File file, File column) throws IOException { + OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); + + try { + export(out, column); + } finally { + out.close(); + } + } + + + public void export(OutputStream out, File column) throws IOException { + PrintStream printer = new PrintStream(out); + + SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd"); + SimpleDateFormat time = new SimpleDateFormat("HH:mm:ss"); + + Date now = new Date(); + printer.println("; Generated by FileBot on " + date.format(now) + " at " + time.format(now)); + printer.println(";"); + printer.println(";"); + + Map checksumMap = model.getChecksumColumn(column); + + for (Entry entry : checksumMap.entrySet()) { + printer.println(String.format("%s %s", entry.getKey(), entry.getValue())); + } + } + + + public String getDefaultFileName(File column) { + String name = ""; + + if (column != null) + name = FileUtil.getFileName(column); + + if (name.isEmpty()) + name = "name"; + + return name + ".sfv"; + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableModel.java b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableModel.java index 505fc14f..e43f0f05 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableModel.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/ChecksumTableModel.java @@ -6,6 +6,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -20,12 +21,19 @@ import net.sourceforge.tuned.FileUtil; class ChecksumTableModel extends AbstractTableModel { - private List rows = new ArrayList(); - private Map rowMap = new HashMap(); + private List rows = new ArrayList(50); - private List checksumColumnRoots = new ArrayList(); + /** + * Hash map for fast access to the row of a given name + */ + private Map rowMap = new HashMap(50); - private final int checksumColumnsOffset = 2; + private List columns = new ArrayList(); + + /** + * Checksum start at column 3 + */ + private static final int checksumColumnOffset = 2; @Override @@ -36,9 +44,11 @@ class ChecksumTableModel extends AbstractTableModel { if (columnIndex == 1) return "Name"; - if (columnIndex >= checksumColumnsOffset) { - File columnRoot = checksumColumnRoots.get(columnIndex - checksumColumnsOffset); - return FileUtil.getFolderName(columnRoot); + if (columnIndex >= checksumColumnOffset) { + File column = columns.get(columnIndex - checksumColumnOffset); + + // works for files too and simply returns the name unchanged + return FileUtil.getFolderName(column); } return null; @@ -53,7 +63,7 @@ class ChecksumTableModel extends AbstractTableModel { if (columnIndex == 1) return String.class; - if (columnIndex >= checksumColumnsOffset) + if (columnIndex >= checksumColumnOffset) return Checksum.class; return null; @@ -61,12 +71,17 @@ class ChecksumTableModel extends AbstractTableModel { public int getColumnCount() { - return checksumColumnsOffset + getChecksumColumnCount(); + return checksumColumnOffset + getChecksumColumnCount(); } public int getChecksumColumnCount() { - return checksumColumnRoots.size(); + return columns.size(); + } + + + public List getChecksumColumns() { + return Collections.unmodifiableList(columns); } @@ -84,20 +99,20 @@ class ChecksumTableModel extends AbstractTableModel { if (columnIndex == 1) return row.getName(); - if (columnIndex >= checksumColumnsOffset) { - File columnRoot = checksumColumnRoots.get(columnIndex - checksumColumnsOffset); - return row.getChecksum(columnRoot); + if (columnIndex >= checksumColumnOffset) { + File column = columns.get(columnIndex - checksumColumnOffset); + return row.getChecksum(column); } return null; } - public synchronized void addAll(List list) { + public void addAll(List list) { int firstRow = getRowCount(); for (ChecksumCell entry : list) { - addChecksum(entry.getName(), entry.getChecksum(), entry.getColumnRoot()); + addChecksum(entry.getName(), entry.getChecksum(), entry.getColumn()); } int lastRow = getRowCount() - 1; @@ -108,7 +123,7 @@ class ChecksumTableModel extends AbstractTableModel { } - private synchronized void addChecksum(String name, Checksum checksum, File columnRoot) { + private void addChecksum(String name, Checksum checksum, File column) { ChecksumRow row = rowMap.get(name); if (row == null) { @@ -117,17 +132,17 @@ class ChecksumTableModel extends AbstractTableModel { rowMap.put(name, row); } - row.putChecksum(columnRoot, checksum); + row.putChecksum(column, checksum); checksum.addPropertyChangeListener(checksumListener); - if (!checksumColumnRoots.contains(columnRoot)) { - checksumColumnRoots.add(columnRoot); + if (!columns.contains(column)) { + columns.add(column); fireTableStructureChanged(); } } - public synchronized void removeRows(int... rowIndices) { + public void removeRows(int... rowIndices) { ArrayList rowsToRemove = new ArrayList(rowIndices.length); for (int i : rowIndices) { @@ -137,37 +152,35 @@ class ChecksumTableModel extends AbstractTableModel { for (Checksum checksum : row.getChecksums()) { checksum.cancelComputationTask(); } + + rowMap.remove(row.getName()); } rows.removeAll(rowsToRemove); fireTableRowsDeleted(rowIndices[0], rowIndices[rowIndices.length - 1]); - - ChecksumComputationService.getService().purge(); } - public synchronized void clear() { - ChecksumComputationService.getService().reset(); - - checksumColumnRoots.clear(); + public void clear() { + columns.clear(); rows.clear(); rowMap.clear(); - fireTableStructureChanged(); + fireTableDataChanged(); } - public File getChecksumColumnRoot(int checksumColumnIndex) { - return checksumColumnRoots.get(checksumColumnIndex); + public File getChecksumColumn(int columnIndex) { + return columns.get(columnIndex); } - public Map getChecksumColumn(File columnRoot) { + public Map getChecksumColumn(File column) { LinkedHashMap checksumMap = new LinkedHashMap(); for (ChecksumRow row : rows) { - Checksum checksum = row.getChecksum(columnRoot); + Checksum checksum = row.getChecksum(column); if ((checksum != null) && (checksum.getState() == Checksum.State.READY)) { checksumMap.put(row.getName(), checksum); @@ -189,13 +202,13 @@ class ChecksumTableModel extends AbstractTableModel { private final String name; private final Checksum checksum; - private final File columnRoot; + private final File column; - public ChecksumCell(String name, Checksum checksum, File columnRoot) { + public ChecksumCell(String name, Checksum checksum, File column) { this.name = name; this.checksum = checksum; - this.columnRoot = columnRoot; + this.column = column; } @@ -209,8 +222,8 @@ class ChecksumTableModel extends AbstractTableModel { } - public File getColumnRoot() { - return columnRoot; + public File getColumn() { + return column; } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/SfvPanel.java b/source/net/sourceforge/filebot/ui/panel/sfv/SfvPanel.java index 37e23599..bc13de7e 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/SfvPanel.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/SfvPanel.java @@ -5,7 +5,8 @@ package net.sourceforge.filebot.ui.panel.sfv; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.io.File; -import java.util.ArrayList; +import java.io.IOException; +import java.util.List; import javax.swing.AbstractAction; import javax.swing.BorderFactory; @@ -32,7 +33,7 @@ public class SfvPanel extends FileBotPanel { private SfvTable sfvTable = new SfvTable(); - private TotalProgressPanel totalProgressPanel = new TotalProgressPanel(); + private TotalProgressPanel totalProgressPanel = new TotalProgressPanel(sfvTable.getChecksumComputationService()); public SfvPanel() { @@ -61,85 +62,12 @@ public class SfvPanel extends FileBotPanel { add(southPanel, BorderLayout.SOUTH); // Shortcut DELETE - TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction); + TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("pressed DELETE"), removeAction); - MessageBus.getDefault().addMessageHandler(getPanelName(), new FileTransferableMessageHandler(getPanelName(), sfvTable.getTransferablePolicy())); + MessageBus.getDefault().addMessageHandler(getPanelName(), new FileTransferableMessageHandler(this, sfvTable.getTransferablePolicy())); } - private final SaveAction saveAction = new SaveAction(sfvTable) { - - private int index; - private String name; - - private File folder = null; - - - @Override - protected void save(File file) { - sfvTable.save(file, index); - } - - - @Override - protected String getDefaultFileName() { - return name; - } - - - @Override - protected File getDefaultFolder() { - return folder; - } - - - @Override - public void actionPerformed(ActionEvent e) { - ChecksumTableModel model = (ChecksumTableModel) sfvTable.getModel(); - - ArrayList options = new ArrayList(); - - for (int i = 0; i < model.getChecksumColumnCount(); i++) { - options.add(model.getChecksumColumnRoot(i)); - } - - File selected = null; - - if (options.size() > 1) { - SelectDialog selectDialog = new SelectDialog(SwingUtilities.getWindowAncestor(SfvPanel.this), options) { - - @Override - protected String convertValueToString(Object value) { - File columnRoot = (File) value; - return FileUtil.getFolderName(columnRoot); - } - }; - - selectDialog.setText("Select checksum column:"); - selectDialog.setVisible(true); - selected = selectDialog.getSelectedValue(); - } else if (options.size() == 1) { - selected = options.get(0); - } - - if (selected == null) - return; - - index = options.indexOf(selected); - name = FileUtil.getFileName(selected); - - if (name.isEmpty()) - name = "name"; - - name += ".sfv"; - - // selected is either a folder or a sfv file - if (selected.isDirectory()) { - folder = selected; - } - - super.actionPerformed(e); - } - }; + private final SaveAction saveAction = new ChecksumTableSaveAction(); private final LoadAction loadAction = new LoadAction(sfvTable.getTransferablePolicy()); @@ -169,4 +97,68 @@ public class SfvPanel extends FileBotPanel { } }; + + private class ChecksumTableSaveAction extends SaveAction { + + private File selectedColumn = null; + + + @Override + protected boolean canExport() { + return selectedColumn != null && sfvTable.getExportHandler().canExport(); + } + + + @Override + protected void export(File file) throws IOException { + sfvTable.getExportHandler().export(file, selectedColumn); + } + + + @Override + protected String getDefaultFileName() { + return sfvTable.getExportHandler().getDefaultFileName(selectedColumn); + } + + + @Override + protected File getDefaultFolder() { + // if column is a folder use it as default folder in file dialog + return selectedColumn.isDirectory() ? selectedColumn : null; + } + + + @Override + public void actionPerformed(ActionEvent e) { + List options = sfvTable.getModel().getChecksumColumns(); + + this.selectedColumn = null; + + if (options.size() == 1) { + // auto-select if there is only one option + this.selectedColumn = options.get(0); + } else if (options.size() > 1) { + // show user his/her options + SelectDialog selectDialog = new SelectDialog(SwingUtilities.getWindowAncestor(SfvPanel.this), options) { + + @Override + protected String convertValueToString(Object value) { + return FileUtil.getFolderName((File) value); + } + }; + + selectDialog.setText("Select checksum column:"); + selectDialog.setVisible(true); + + this.selectedColumn = selectDialog.getSelectedValue(); + } + + if (this.selectedColumn != null) { + // continue if a column was selected + super.actionPerformed(e); + } + } + + } + } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/SfvTable.java b/source/net/sourceforge/filebot/ui/panel/sfv/SfvTable.java index 8feade47..a1843263 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/SfvTable.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/SfvTable.java @@ -2,14 +2,6 @@ package net.sourceforge.filebot.ui.panel.sfv; -import java.io.File; -import java.io.PrintStream; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - import javax.swing.JTable; import javax.swing.ListSelectionModel; import javax.swing.event.TableModelEvent; @@ -20,26 +12,20 @@ import net.sourceforge.filebot.ui.panel.sfv.ChecksumTableModel.ChecksumTableMode import net.sourceforge.filebot.ui.panel.sfv.renderer.ChecksumTableCellRenderer; import net.sourceforge.filebot.ui.panel.sfv.renderer.StateIconTableCellRenderer; import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler; -import net.sourceforge.filebot.ui.transfer.ExportHandler; -import net.sourceforge.filebot.ui.transfer.ImportHandler; -import net.sourceforge.filebot.ui.transfer.Saveable; -import net.sourceforge.filebot.ui.transfer.SaveableExportHandler; -import net.sourceforge.filebot.ui.transfer.TransferablePolicyImportHandler; -import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy; -import net.sourceforge.tuned.FileUtil; -class SfvTable extends JTable implements Saveable { +class SfvTable extends JTable { private final SfvTransferablePolicy transferablePolicy; + private final ChecksumTableExportHandler exportHandler; + + private final ChecksumComputationService checksumComputationService = new ChecksumComputationService(); public SfvTable() { - ChecksumTableModel model = (ChecksumTableModel) getModel(); - transferablePolicy = new SfvTransferablePolicy(model); - - setModel(model); + transferablePolicy = new SfvTransferablePolicy(getModel(), checksumComputationService); + exportHandler = new ChecksumTableExportHandler(getModel()); setFillsViewportHeight(true); setAutoCreateRowSorter(true); @@ -50,10 +36,7 @@ class SfvTable extends JTable implements Saveable { setRowHeight(20); - ImportHandler importHandler = new TransferablePolicyImportHandler(transferablePolicy); - ExportHandler exportHandler = new SaveableExportHandler(this); - - setTransferHandler(new DefaultTransferHandler(importHandler, exportHandler)); + setTransferHandler(new DefaultTransferHandler(transferablePolicy, exportHandler)); setDragEnabled(true); setDefaultRenderer(ChecksumRow.State.class, new StateIconTableCellRenderer()); @@ -61,17 +44,39 @@ class SfvTable extends JTable implements Saveable { } - public TransferablePolicy getTransferablePolicy() { + public SfvTransferablePolicy getTransferablePolicy() { return transferablePolicy; } + public ChecksumTableExportHandler getExportHandler() { + return exportHandler; + } + + + public ChecksumComputationService getChecksumComputationService() { + return checksumComputationService; + } + + + @Override + public DefaultTransferHandler getTransferHandler() { + return (DefaultTransferHandler) super.getTransferHandler(); + } + + @Override protected TableModel createDefaultDataModel() { return new ChecksumTableModel(); } + @Override + public ChecksumTableModel getModel() { + return (ChecksumTableModel) super.getModel(); + } + + @Override public void createDefaultColumnsFromModel() { super.createDefaultColumnsFromModel(); @@ -90,82 +95,30 @@ class SfvTable extends JTable implements Saveable { public void clear() { + checksumComputationService.reset(); transferablePolicy.reset(); - ((ChecksumTableModel) getModel()).clear(); - } - - - public String getDefaultFileName() { - ChecksumTableModel model = (ChecksumTableModel) getModel(); - File columnRoot = model.getChecksumColumnRoot(0); - - String name = ""; - - if (columnRoot != null) - name = FileUtil.getFileName(columnRoot); - - if (name.isEmpty()) - name = "name"; - - return name + ".sfv"; - } - - - public boolean isSaveable() { - return getModel().getRowCount() > 0; + getModel().clear(); } public void removeRows(int... rowIndices) { - ChecksumTableModel model = (ChecksumTableModel) getModel(); - model.removeRows(rowIndices); + getModel().removeRows(rowIndices); } @Override public void tableChanged(TableModelEvent e) { + // only request repaint when progress changes, or selection will go haywire if (e.getType() == ChecksumTableModelEvent.CHECKSUM_PROGRESS) { repaint(); } else { super.tableChanged(e); - } - } - - - public void save(File file, int checksumColumnIndex) { - try { - PrintStream out = new PrintStream(file); - ChecksumTableModel model = (ChecksumTableModel) getModel(); - File columnRoot = model.getChecksumColumnRoot(checksumColumnIndex); - - if (columnRoot != null) { - SimpleDateFormat date = new SimpleDateFormat("yyyy-MM-dd"); - SimpleDateFormat time = new SimpleDateFormat("HH:mm:ss"); - - Date now = new Date(); - out.println("; Generated by FileBot on " + date.format(now) + " at " + time.format(now)); - out.println(";"); - out.println(";"); - - Map checksumMap = model.getChecksumColumn(columnRoot); - - for (String name : checksumMap.keySet()) { - out.println(name + " " + checksumMap.get(name).getChecksumString()); - } + if (e.getType() == TableModelEvent.DELETE) { + // remove cancelled task from queue + checksumComputationService.purge(); } - - out.close(); - } catch (Exception e) { - // should not happen - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); } } - - - public void save(File file) { - save(file, 0); - } - } diff --git a/source/net/sourceforge/filebot/ui/panel/sfv/SfvTransferablePolicy.java b/source/net/sourceforge/filebot/ui/panel/sfv/SfvTransferablePolicy.java index c4cdd10c..f618ef80 100644 --- a/source/net/sourceforge/filebot/ui/panel/sfv/SfvTransferablePolicy.java +++ b/source/net/sourceforge/filebot/ui/panel/sfv/SfvTransferablePolicy.java @@ -14,21 +14,24 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import net.sourceforge.filebot.FileBotUtil; -import net.sourceforge.filebot.ui.transferablepolicies.BackgroundFileTransferablePolicy; +import net.sourceforge.filebot.ui.transfer.BackgroundFileTransferablePolicy; class SfvTransferablePolicy extends BackgroundFileTransferablePolicy { - private ChecksumTableModel tableModel; + private final ChecksumTableModel tableModel; + private final ChecksumComputationService checksumComputationService; - public SfvTransferablePolicy(ChecksumTableModel tableModel) { + public SfvTransferablePolicy(ChecksumTableModel tableModel, ChecksumComputationService checksumComputationService) { this.tableModel = tableModel; + this.checksumComputationService = checksumComputationService; } @Override protected void clear() { + checksumComputationService.reset(); tableModel.clear(); } @@ -60,11 +63,11 @@ class SfvTransferablePolicy extends BackgroundFileTransferablePolicy { private final Locale locale; private final String code; - private final ImageIcon icon; + private final Icon icon; public Language(String languageName) { @@ -57,13 +56,11 @@ class Language implements Comparable { @Override public boolean equals(Object obj) { - if (this == obj) { + if (this == obj) return true; - } - if (obj instanceof Language) { + if (obj instanceof Language) return getName().equalsIgnoreCase(((Language) obj).getName()); - } return false; } diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageResolver.java b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageResolver.java index 9af7e0e1..0f45ce0c 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageResolver.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageResolver.java @@ -26,13 +26,12 @@ public class LanguageResolver { * @return the locale for this language or null if no locale for this language exists */ public synchronized Locale getLocale(String languageName) { - languageName = languageName.toLowerCase(); - Locale locale = cache.get(languageName); + Locale locale = cache.get(languageName.toLowerCase()); if (locale == null) { locale = findLocale(languageName); - cache.put(languageName, locale); + cache.put(languageName.toLowerCase(), locale); } return locale; @@ -59,16 +58,17 @@ public class LanguageResolver { /** * Find the {@link Locale} for a given language name. * - * @param languageName lower-case language name + * @param languageName language name * @return {@link Locale} for the given language, or null if no matching {@link Locale} is * available */ private Locale findLocale(String languageName) { for (Locale locale : Locale.getAvailableLocales()) { - if (locale.getDisplayLanguage(Locale.ENGLISH).toLowerCase().equals(languageName)) + if (locale.getDisplayLanguage(Locale.ENGLISH).equalsIgnoreCase(languageName)) return locale; } return null; } + } diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageSelectionPanel.java b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageSelectionPanel.java index 681aebe6..0425c26f 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageSelectionPanel.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/LanguageSelectionPanel.java @@ -16,7 +16,6 @@ import java.util.TreeMap; import javax.swing.JPanel; import javax.swing.JToggleButton; -import net.sourceforge.filebot.Settings; import ca.odell.glazedlists.EventList; import ca.odell.glazedlists.FunctionList; import ca.odell.glazedlists.ListSelection; @@ -31,13 +30,14 @@ public class LanguageSelectionPanel extends JPanel { private final ListSelection selectionModel; private final Map defaultSelection = new TreeMap(String.CASE_INSENSITIVE_ORDER); - private final Map globalSelection = Settings.getSettings().asBooleanMap(Settings.SUBTITLE_LANGUAGE); + // private final Map globalSelection = Settings.getSettings().asBooleanMap(Settings.SUBTITLE_LANGUAGE); + public LanguageSelectionPanel(EventList source) { super(new FlowLayout(FlowLayout.RIGHT, 5, 1)); - defaultSelection.putAll(globalSelection); + // defaultSelection.putAll(globalSelection); EventList languageList = new FunctionList(source, new LanguageFunction()); EventList languageSet = new UniqueList(languageList); @@ -68,7 +68,7 @@ public class LanguageSelectionPanel extends JPanel { String key = language.getName(); defaultSelection.put(key, selected); - globalSelection.put(key, selected); + // globalSelection.put(key, selected); if (selected) selectionModel.select(language); diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleCellRenderer.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleCellRenderer.java index bad18cba..8a3ec1b2 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleCellRenderer.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitleCellRenderer.java @@ -14,6 +14,7 @@ import javax.swing.SwingConstants; import net.sourceforge.tuned.ui.ColorTintImageFilter; import net.sourceforge.tuned.ui.IconViewCellRenderer; +import net.sourceforge.tuned.ui.TunedUtil; public class SubtitleCellRenderer extends IconViewCellRenderer { @@ -52,10 +53,10 @@ public class SubtitleCellRenderer extends IconViewCellRenderer { info1.setIcon(icon); - ImageIcon icon = subtitle.getArchiveIcon(); + Icon icon = subtitle.getArchiveIcon(); if (isSelected) { - setIcon(new ImageIcon(createImage(new FilteredImageSource(icon.getImage().getSource(), new ColorTintImageFilter(list.getSelectionBackground(), 0.5f))))); + setIcon(new ImageIcon(createImage(new FilteredImageSource(TunedUtil.getImage(icon).getSource(), new ColorTintImageFilter(list.getSelectionBackground(), 0.5f))))); info1.setForeground(list.getSelectionForeground()); info2.setForeground(list.getSelectionForeground()); diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java index 55db3bc4..d04402a4 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePackage.java @@ -4,6 +4,7 @@ package net.sourceforge.filebot.ui.panel.subtitle; import java.beans.PropertyChangeEvent; +import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.SwingWorker.StateValue; @@ -57,7 +58,7 @@ public class SubtitlePackage extends AbstractBean { } - public ImageIcon getArchiveIcon() { + public Icon getArchiveIcon() { return archiveIcon; } @@ -68,9 +69,9 @@ public class SubtitlePackage extends AbstractBean { } - public synchronized void download() { + public synchronized void startDownload() { if (downloadTask != null) - throw new IllegalStateException("Download has been started already"); + throw new IllegalStateException("Download has already been started"); downloadTask = subtitleDescriptor.createDownloadTask(); downloadTask.addPropertyChangeListener(new DownloadTaskPropertyChangeAdapter()); diff --git a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java index c168c88e..e1faaa40 100644 --- a/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/subtitle/SubtitlePanel.java @@ -8,7 +8,6 @@ import java.util.Collection; import java.util.List; import java.util.Locale; -import net.sourceforge.filebot.ListChangeSynchronizer; import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.resources.ResourceManager; import net.sourceforge.filebot.ui.AbstractSearchPanel; @@ -18,6 +17,7 @@ import net.sourceforge.filebot.web.SearchResult; import net.sourceforge.filebot.web.SubsceneSubtitleClient; import net.sourceforge.filebot.web.SubtitleClient; import net.sourceforge.filebot.web.SubtitleDescriptor; +import net.sourceforge.tuned.ListChangeSynchronizer; import net.sourceforge.tuned.ui.LabelProvider; import net.sourceforge.tuned.ui.SimpleLabelProvider; @@ -63,12 +63,6 @@ public class SubtitlePanel extends AbstractSearchPanel selectDialog) { - selectDialog.setText("Select a Show / Movie:"); - } - - @Override protected FetchTask createFetchTask(SearchTask searchTask, SearchResult selectedSearchResult) { return new SubtitleFetchTask(searchTask.getClient(), selectedSearchResult, searchTask.getTabPanel()); @@ -93,6 +87,13 @@ public class SubtitlePanel extends AbstractSearchPanel selectDialog) throws Exception { + super.configureSelectDialog(selectDialog); + selectDialog.setText("Select a Show / Movie:"); + } + } diff --git a/source/net/sourceforge/filebot/ui/transfer/AdaptiveFileExportHandler.java b/source/net/sourceforge/filebot/ui/transfer/AdaptiveFileExportHandler.java new file mode 100644 index 00000000..9b44e87f --- /dev/null +++ b/source/net/sourceforge/filebot/ui/transfer/AdaptiveFileExportHandler.java @@ -0,0 +1,40 @@ + +package net.sourceforge.filebot.ui.transfer; + + +import java.io.IOException; +import java.io.OutputStream; + + +public abstract class AdaptiveFileExportHandler extends FileExportHandler { + + /** + * @return the FileExportHandler that that should be used, or + * null if export is not possible in the first place + */ + protected abstract FileExportHandler getExportHandler(); + + + @Override + public boolean canExport() { + FileExportHandler handler = getExportHandler(); + + if (handler == null) + return false; + + return handler.canExport(); + } + + + @Override + public void export(OutputStream out) throws IOException { + getExportHandler().export(out); + } + + + @Override + public String getDefaultFileName() { + return getExportHandler().getDefaultFileName(); + } + +} diff --git a/source/net/sourceforge/filebot/ui/transfer/BackgroundFileTransferablePolicy.java b/source/net/sourceforge/filebot/ui/transfer/BackgroundFileTransferablePolicy.java new file mode 100644 index 00000000..c37108bc --- /dev/null +++ b/source/net/sourceforge/filebot/ui/transfer/BackgroundFileTransferablePolicy.java @@ -0,0 +1,138 @@ + +package net.sourceforge.filebot.ui.transfer; + + +import java.awt.datatransfer.Transferable; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import java.util.List; + +import javax.swing.SwingWorker; + +import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter; + + +public abstract class BackgroundFileTransferablePolicy extends FileTransferablePolicy { + + public static final String LOADING_PROPERTY = "loading"; + + private BackgroundWorker worker = null; + + + @Override + public boolean accept(Transferable tr) { + if (isActive()) + return false; + + return super.accept(tr); + } + + + @Override + public synchronized void handleTransferable(Transferable tr, TransferAction action) { + List files = getFilesFromTransferable(tr); + + if (action != TransferAction.ADD) + clear(); + + worker = new BackgroundWorker(files); + worker.addPropertyChangeListener(new BackgroundWorkerListener()); + worker.execute(); + } + + + public synchronized boolean isActive() { + return (worker != null) && !worker.isDone(); + } + + + public synchronized void reset() { + if (isActive()) { + worker.cancel(true); + } + } + + + /** + * Receives data chunks from the publish method asynchronously on the Event Dispatch + * Thread. + * + * @param chunks + */ + protected abstract void process(List chunks); + + + /** + * Sends data chunks to the process method. + * + * @param chunks + */ + protected synchronized final void publish(V... chunks) { + if (worker != null) { + worker.publishChunks(chunks); + } + } + + + private class BackgroundWorker extends SwingWorker { + + private final List files; + + + public BackgroundWorker(List files) { + this.files = files; + } + + + @Override + protected Void doInBackground() { + load(files); + + return null; + } + + + public void publishChunks(V... chunks) { + if (!isCancelled()) { + publish(chunks); + } + } + + + @Override + protected void process(List chunks) { + if (!isCancelled()) { + BackgroundFileTransferablePolicy.this.process(chunks); + } + } + } + + + private class BackgroundWorkerListener extends SwingWorkerPropertyChangeAdapter { + + @Override + public void started(PropertyChangeEvent evt) { + propertyChangeSupport.firePropertyChange(LOADING_PROPERTY, null, true); + } + + + @Override + public void done(PropertyChangeEvent evt) { + propertyChangeSupport.firePropertyChange(LOADING_PROPERTY, null, false); + } + } + + private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this); + + + public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeSupport.addPropertyChangeListener(propertyName, listener); + } + + + public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { + propertyChangeSupport.removePropertyChangeListener(propertyName, listener); + } +} diff --git a/source/net/sourceforge/filebot/ui/transfer/DefaultListExportHandler.java b/source/net/sourceforge/filebot/ui/transfer/DefaultListExportHandler.java new file mode 100644 index 00000000..d05493a6 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/transfer/DefaultListExportHandler.java @@ -0,0 +1,45 @@ + +package net.sourceforge.filebot.ui.transfer; + + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import javax.swing.JList; +import javax.swing.ListModel; + + +public class DefaultListExportHandler extends FileExportHandler { + + private final JList list; + + + public DefaultListExportHandler(JList list) { + this.list = list; + } + + + @Override + public boolean canExport() { + return list.getModel().getSize() > 0; + } + + + @Override + public void export(OutputStream out) throws IOException { + PrintStream printer = new PrintStream(out); + + ListModel model = list.getModel(); + + for (int i = 0; i < model.getSize(); i++) { + printer.println(model.getElementAt(i)); + } + } + + + @Override + public String getDefaultFileName() { + return list.getClientProperty("title") + ".txt"; + } +} diff --git a/source/net/sourceforge/filebot/ui/transfer/DefaultTransferHandler.java b/source/net/sourceforge/filebot/ui/transfer/DefaultTransferHandler.java index e3a31f35..2399f2b6 100644 --- a/source/net/sourceforge/filebot/ui/transfer/DefaultTransferHandler.java +++ b/source/net/sourceforge/filebot/ui/transfer/DefaultTransferHandler.java @@ -94,16 +94,31 @@ public class DefaultTransferHandler extends TransferHandler { } + public ImportHandler getImportHandler() { + return importHandler; + } + + public void setImportHandler(ImportHandler importHandler) { this.importHandler = importHandler; } + public ExportHandler getExportHandler() { + return exportHandler; + } + + public void setExportHandler(ExportHandler exportHandler) { this.exportHandler = exportHandler; } + public ClipboardHandler getClipboardHandler() { + return clipboardHandler; + } + + public void setClipboardHandler(ClipboardHandler clipboardHandler) { this.clipboardHandler = clipboardHandler; } diff --git a/source/net/sourceforge/filebot/ui/transfer/SaveableExportHandler.java b/source/net/sourceforge/filebot/ui/transfer/FileExportHandler.java similarity index 60% rename from source/net/sourceforge/filebot/ui/transfer/SaveableExportHandler.java rename to source/net/sourceforge/filebot/ui/transfer/FileExportHandler.java index 15694e93..5209f04b 100644 --- a/source/net/sourceforge/filebot/ui/transfer/SaveableExportHandler.java +++ b/source/net/sourceforge/filebot/ui/transfer/FileExportHandler.java @@ -3,8 +3,11 @@ package net.sourceforge.filebot.ui.transfer; import java.awt.datatransfer.Transferable; +import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.logging.Level; import java.util.logging.Logger; @@ -16,19 +19,31 @@ import net.sourceforge.filebot.Settings; import net.sourceforge.tuned.TemporaryFolder; -public class SaveableExportHandler implements ExportHandler { +public abstract class FileExportHandler implements ExportHandler { - private final Saveable saveable; + public abstract boolean canExport(); + + public abstract void export(OutputStream out) throws IOException; - public SaveableExportHandler(Saveable saveable) { - this.saveable = saveable; + + public abstract String getDefaultFileName(); + + + public void export(File file) throws IOException { + OutputStream out = new BufferedOutputStream(new FileOutputStream(file)); + + try { + export(out); + } finally { + out.close(); + } } @Override public int getSourceActions(JComponent c) { - if ((saveable == null) || !saveable.isSaveable()) + if (!canExport()) return TransferHandler.NONE; return TransferHandler.MOVE | TransferHandler.COPY; @@ -38,11 +53,12 @@ public class SaveableExportHandler implements ExportHandler { @Override public Transferable createTransferable(JComponent c) { try { - // Remove invalid characters from default filename - String name = FileBotUtil.validateFileName(saveable.getDefaultFileName()); + // remove invalid characters from file name + String name = FileBotUtil.validateFileName(getDefaultFileName()); File temporaryFile = TemporaryFolder.getFolder(Settings.ROOT).createFile(name); - saveable.save(temporaryFile); + + export(temporaryFile); return new FileTransferable(temporaryFile); } catch (IOException e) { @@ -58,4 +74,5 @@ public class SaveableExportHandler implements ExportHandler { public void exportDone(JComponent source, Transferable data, int action) { } + } diff --git a/source/net/sourceforge/filebot/ui/transfer/FileTransferable.java b/source/net/sourceforge/filebot/ui/transfer/FileTransferable.java index 65137e6f..600bf8b9 100644 --- a/source/net/sourceforge/filebot/ui/transfer/FileTransferable.java +++ b/source/net/sourceforge/filebot/ui/transfer/FileTransferable.java @@ -56,7 +56,6 @@ public class FileTransferable implements Transferable { /** - * * @return line separated list of file uris */ private String getUriList() { @@ -72,7 +71,7 @@ public class FileTransferable implements Transferable { public DataFlavor[] getTransferDataFlavors() { - return supportedFlavors; + return supportedFlavors.clone(); } diff --git a/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java b/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java new file mode 100644 index 00000000..3f9cfd65 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/transfer/FileTransferablePolicy.java @@ -0,0 +1,124 @@ + +package net.sourceforge.filebot.ui.transfer; + + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + + +public abstract class FileTransferablePolicy extends TransferablePolicy { + + @Override + public boolean accept(Transferable tr) { + List files = getFilesFromTransferable(tr); + + if (files.isEmpty()) + return false; + + return accept(files); + } + + + @SuppressWarnings("unchecked") + protected List getFilesFromTransferable(Transferable tr) { + try { + if (tr.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + // file list flavor + return (List) tr.getTransferData(DataFlavor.javaFileListFlavor); + } else if (tr.isDataFlavorSupported(FileTransferable.uriListFlavor)) { + // file uri list flavor + String transferString = (String) tr.getTransferData(FileTransferable.uriListFlavor); + + String lines[] = transferString.split("\r?\n"); + ArrayList files = new ArrayList(lines.length); + + for (String line : lines) { + if (line.startsWith("#")) { + // the line is a comment (as per the RFC 2483) + continue; + } + + try { + File file = new File(new URI(line)); + + if (!file.exists()) + throw new FileNotFoundException(file.toString()); + + files.add(file); + } catch (Exception e) { + // URISyntaxException, IllegalArgumentException, FileNotFoundException + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, "Invalid file url: " + line); + } + } + + return files; + } + } catch (UnsupportedFlavorException e) { + // should not happen + throw new RuntimeException(e); + } catch (IOException e) { + // should not happen + throw new RuntimeException(e); + } + + return Collections.EMPTY_LIST; + } + + + @Override + public void handleTransferable(Transferable tr, TransferAction action) { + List files = getFilesFromTransferable(tr); + + if (action != TransferAction.ADD) + clear(); + + load(files); + } + + + protected boolean accept(List files) { + for (File f : files) + if (!accept(f)) + return false; + + return true; + } + + + protected void load(List files) { + for (File file : files) { + load(file); + } + } + + + protected boolean accept(File file) { + return file.isFile() || file.isDirectory(); + } + + + protected void clear() { + + } + + + protected void load(File file) { + + } + + + public String getFileFilterDescription() { + return null; + } + +} diff --git a/source/net/sourceforge/filebot/ui/transfer/LoadAction.java b/source/net/sourceforge/filebot/ui/transfer/LoadAction.java index 5c822dfd..bf52b1e5 100644 --- a/source/net/sourceforge/filebot/ui/transfer/LoadAction.java +++ b/source/net/sourceforge/filebot/ui/transfer/LoadAction.java @@ -8,7 +8,7 @@ import javax.swing.AbstractAction; import javax.swing.JFileChooser; import net.sourceforge.filebot.resources.ResourceManager; -import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy; +import net.sourceforge.filebot.ui.transfer.TransferablePolicy.TransferAction; public class LoadAction extends AbstractAction { @@ -18,6 +18,7 @@ public class LoadAction extends AbstractAction { public LoadAction(TransferablePolicy transferablePolicy) { super("Load", ResourceManager.getIcon("action.load")); + this.transferablePolicy = transferablePolicy; } @@ -35,10 +36,14 @@ public class LoadAction extends AbstractAction { FileTransferable transferable = new FileTransferable(chooser.getSelectedFiles()); - boolean add = ((e.getModifiers() & ActionEvent.CTRL_MASK) != 0); + TransferAction action = TransferAction.PUT; + + // if CTRL was pressed when the button was clicked, assume ADD action (same as with dnd) + if ((e.getModifiers() & ActionEvent.CTRL_MASK) != 0) + action = TransferAction.ADD; if (transferablePolicy.accept(transferable)) - transferablePolicy.handleTransferable(transferable, add); + transferablePolicy.handleTransferable(transferable, action); } } diff --git a/source/net/sourceforge/filebot/ui/transfer/SaveAction.java b/source/net/sourceforge/filebot/ui/transfer/SaveAction.java index 038efa72..8388d57d 100644 --- a/source/net/sourceforge/filebot/ui/transfer/SaveAction.java +++ b/source/net/sourceforge/filebot/ui/transfer/SaveAction.java @@ -4,8 +4,12 @@ package net.sourceforge.filebot.ui.transfer; import java.awt.event.ActionEvent; import java.io.File; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.AbstractAction; +import javax.swing.JComponent; import javax.swing.JFileChooser; import net.sourceforge.filebot.FileBotUtil; @@ -14,22 +18,32 @@ import net.sourceforge.filebot.resources.ResourceManager; public class SaveAction extends AbstractAction { - protected Saveable saveable; + protected final FileExportHandler exportHandler; - public SaveAction(Saveable saveable) { + public SaveAction(FileExportHandler exportHandler) { super("Save as ...", ResourceManager.getIcon("action.save")); - this.saveable = saveable; + this.exportHandler = exportHandler; } - protected void save(File file) { - saveable.save(file); + protected SaveAction() { + this(null); + } + + + protected boolean canExport() { + return exportHandler.canExport(); + } + + + protected void export(File file) throws IOException { + exportHandler.export(file); } protected String getDefaultFileName() { - return saveable.getDefaultFileName(); + return exportHandler.getDefaultFileName(); } @@ -38,18 +52,8 @@ public class SaveAction extends AbstractAction { } - protected boolean isSaveable() { - return saveable.isSaveable(); - } - - - protected void setSaveable(Saveable saveable) { - this.saveable = saveable; - } - - - public void actionPerformed(ActionEvent e) { - if (!isSaveable()) + public void actionPerformed(ActionEvent evt) { + if (!canExport()) return; JFileChooser chooser = new JFileChooser(); @@ -58,10 +62,13 @@ public class SaveAction extends AbstractAction { chooser.setSelectedFile(new File(getDefaultFolder(), FileBotUtil.validateFileName(getDefaultFileName()))); - if (chooser.showSaveDialog(null) != JFileChooser.APPROVE_OPTION) + if (chooser.showSaveDialog((JComponent) evt.getSource()) != JFileChooser.APPROVE_OPTION) return; - save(chooser.getSelectedFile()); + try { + export(chooser.getSelectedFile()); + } catch (IOException e) { + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); + } } - } diff --git a/source/net/sourceforge/filebot/ui/transfer/Saveable.java b/source/net/sourceforge/filebot/ui/transfer/Saveable.java deleted file mode 100644 index ad1565a6..00000000 --- a/source/net/sourceforge/filebot/ui/transfer/Saveable.java +++ /dev/null @@ -1,17 +0,0 @@ - -package net.sourceforge.filebot.ui.transfer; - - -import java.io.File; - - -public interface Saveable { - - public abstract void save(File file); - - - public abstract boolean isSaveable(); - - - public abstract String getDefaultFileName(); -} diff --git a/source/net/sourceforge/filebot/ui/transfer/StringTransferablePolicy.java b/source/net/sourceforge/filebot/ui/transfer/StringTransferablePolicy.java new file mode 100644 index 00000000..5b600b3e --- /dev/null +++ b/source/net/sourceforge/filebot/ui/transfer/StringTransferablePolicy.java @@ -0,0 +1,47 @@ + +package net.sourceforge.filebot.ui.transfer; + + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; + + +public abstract class StringTransferablePolicy extends TransferablePolicy { + + @Override + public boolean accept(Transferable tr) { + return tr.isDataFlavorSupported(DataFlavor.stringFlavor); + } + + + @Override + public void handleTransferable(Transferable tr, TransferAction action) { + String string; + + try { + string = (String) tr.getTransferData(DataFlavor.stringFlavor); + } catch (UnsupportedFlavorException e) { + // should no happen + throw new RuntimeException(e); + } catch (IOException e) { + // should no happen + throw new RuntimeException(e); + } + + if (action != TransferAction.ADD) + clear(); + + load(string); + } + + + protected void clear() { + + } + + + protected abstract void load(String string); + +} diff --git a/source/net/sourceforge/filebot/ui/transfer/TransferablePolicy.java b/source/net/sourceforge/filebot/ui/transfer/TransferablePolicy.java new file mode 100644 index 00000000..13b91db3 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/transfer/TransferablePolicy.java @@ -0,0 +1,96 @@ + +package net.sourceforge.filebot.ui.transfer; + + +import java.awt.datatransfer.Transferable; +import java.awt.dnd.InvalidDnDOperationException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.TransferHandler; +import javax.swing.TransferHandler.TransferSupport; + + +public abstract class TransferablePolicy implements ImportHandler { + + public abstract boolean accept(Transferable tr); + + + public abstract void handleTransferable(Transferable tr, TransferAction action); + + + public static enum TransferAction { + PUT(TransferHandler.MOVE), + ADD(TransferHandler.COPY), + LINK(TransferHandler.LINK); + + private final int dndConstant; + + + private TransferAction(int dndConstant) { + this.dndConstant = dndConstant; + } + + + public int getDnDConstant() { + return dndConstant; + } + + + public static TransferAction fromDnDConstant(int dndConstant) { + for (TransferAction action : values()) { + if (dndConstant == action.dndConstant) + return action; + } + + throw new IllegalArgumentException("Unsupported dndConstant: " + dndConstant); + } + + } + + + @Override + public boolean canImport(TransferSupport support) { + if (support.isDrop()) + support.setShowDropLocation(false); + + try { + return accept(support.getTransferable()); + } catch (InvalidDnDOperationException e) { + // final drop may cause this exception because, the transfer data can only be accessed + // *after* the drop has been accepted, but canImport is called before that + + // just assume that the transferable will be accepted, accept will be called in importData again anyway + return true; + } + } + + + @Override + public boolean importData(TransferSupport support) { + Transferable transferable = support.getTransferable(); + + try { + if (accept(transferable)) { + handleTransferable(transferable, getTransferAction(support)); + return true; + } + } catch (Exception e) { + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.WARNING, e.toString(), e); + } + + // transferable was not accepted, or transfer failed + return false; + } + + + protected TransferAction getTransferAction(TransferSupport support) { + if (support.isDrop()) { + return TransferAction.fromDnDConstant(support.getDropAction()); + } + + // use PUT by default (e.g. clipboard transfers) + return TransferAction.PUT; + } + +} diff --git a/source/net/sourceforge/filebot/ui/transfer/TransferablePolicyFileFilter.java b/source/net/sourceforge/filebot/ui/transfer/TransferablePolicyFileFilter.java index c457f2ad..f0334026 100644 --- a/source/net/sourceforge/filebot/ui/transfer/TransferablePolicyFileFilter.java +++ b/source/net/sourceforge/filebot/ui/transfer/TransferablePolicyFileFilter.java @@ -6,9 +6,6 @@ import java.io.File; import javax.swing.filechooser.FileFilter; -import net.sourceforge.filebot.ui.transferablepolicies.FileTransferablePolicy; -import net.sourceforge.filebot.ui.transferablepolicies.CompositeTransferablePolicy; -import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy; public class TransferablePolicyFileFilter extends FileFilter { @@ -32,12 +29,10 @@ public class TransferablePolicyFileFilter extends FileFilter { @Override public String getDescription() { - if (transferablePolicy instanceof CompositeTransferablePolicy) { - CompositeTransferablePolicy multi = (CompositeTransferablePolicy) transferablePolicy; - return multi.getDescription(FileTransferablePolicy.class); + if (transferablePolicy instanceof FileTransferablePolicy) { + return ((FileTransferablePolicy) transferablePolicy).getFileFilterDescription(); } - return transferablePolicy.getDescription(); + return null; } - } diff --git a/source/net/sourceforge/filebot/ui/transfer/TransferablePolicyImportHandler.java b/source/net/sourceforge/filebot/ui/transfer/TransferablePolicyImportHandler.java deleted file mode 100644 index 3c4065f6..00000000 --- a/source/net/sourceforge/filebot/ui/transfer/TransferablePolicyImportHandler.java +++ /dev/null @@ -1,63 +0,0 @@ - -package net.sourceforge.filebot.ui.transfer; - - -import java.awt.datatransfer.Transferable; -import java.awt.dnd.InvalidDnDOperationException; -import java.util.logging.Level; -import java.util.logging.Logger; - -import javax.swing.TransferHandler; -import javax.swing.TransferHandler.TransferSupport; - -import net.sourceforge.filebot.ui.transferablepolicies.TransferablePolicy; - - -public class TransferablePolicyImportHandler implements ImportHandler { - - private final TransferablePolicy transferablePolicy; - - - public TransferablePolicyImportHandler(TransferablePolicy transferablePolicy) { - this.transferablePolicy = transferablePolicy; - } - - private boolean canImportCache = false; - - - @Override - public boolean canImport(TransferSupport support) { - if (support.isDrop()) - support.setShowDropLocation(false); - - Transferable t = support.getTransferable(); - - try { - canImportCache = transferablePolicy.accept(t); - } catch (InvalidDnDOperationException e) { - // for some reason the last transferable has no drop current - } - - return canImportCache; - } - - - @Override - public boolean importData(TransferSupport support) { - boolean add = false; - - if (support.isDrop() && (support.getDropAction() == TransferHandler.COPY)) - add = true; - - Transferable t = support.getTransferable(); - - try { - transferablePolicy.handleTransferable(t, add); - } catch (Exception e) { - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); - } - - return true; - } - -} diff --git a/source/net/sourceforge/filebot/web/AnidbClient.java b/source/net/sourceforge/filebot/web/AnidbClient.java index 444908ec..4d28e782 100644 --- a/source/net/sourceforge/filebot/web/AnidbClient.java +++ b/source/net/sourceforge/filebot/web/AnidbClient.java @@ -11,7 +11,6 @@ import java.net.URLEncoder; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.logging.Level; @@ -29,9 +28,7 @@ import org.xml.sax.SAXException; public class AnidbClient implements EpisodeListClient { - private final SearchResultCache searchResultCache = new SearchResultCache(); - - private final String host = "anidb.net"; + private static final String host = "anidb.net"; @Override @@ -48,9 +45,6 @@ public class AnidbClient implements EpisodeListClient { @Override public List search(String searchterm) throws IOException, SAXException { - if (searchResultCache.containsKey(searchterm)) { - return Collections.singletonList(searchResultCache.get(searchterm)); - } Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm)); @@ -91,8 +85,6 @@ public class AnidbClient implements EpisodeListClient { } } - searchResultCache.addAll(searchResults); - return searchResults; } @@ -122,7 +114,7 @@ public class AnidbClient implements EpisodeListClient { number = numberFormat.format(Integer.parseInt(number)); // no seasons for anime - episodes.add(new Episode(searchResult.getName(), null, number, title)); + episodes.add(new Episode(searchResult.getName(), number, title)); } catch (NumberFormatException ex) { // ignore node, episode is probably some kind of special (S1, S2, ...) } diff --git a/source/net/sourceforge/filebot/web/Episode.java b/source/net/sourceforge/filebot/web/Episode.java index 96cddf6d..4f32d72d 100644 --- a/source/net/sourceforge/filebot/web/Episode.java +++ b/source/net/sourceforge/filebot/web/Episode.java @@ -4,10 +4,10 @@ package net.sourceforge.filebot.web; public class Episode { - private String showName; - private String numberOfSeason; - private String numberOfEpisode; - private String title; + private final String showName; + private final String numberOfSeason; + private final String numberOfEpisode; + private final String title; public Episode(String showname, String numberOfSeason, String numberOfEpisode, String title) { diff --git a/source/net/sourceforge/filebot/web/HtmlUtil.java b/source/net/sourceforge/filebot/web/HtmlUtil.java index 00717e9d..eee35f62 100644 --- a/source/net/sourceforge/filebot/web/HtmlUtil.java +++ b/source/net/sourceforge/filebot/web/HtmlUtil.java @@ -11,6 +11,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; import java.util.Map; +import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; @@ -60,8 +61,8 @@ public class HtmlUtil { public static Document getHtmlDocument(URL url, Map requestHeaders) throws IOException, SAXException { URLConnection connection = url.openConnection(); - for (String key : requestHeaders.keySet()) { - connection.addRequestProperty(key, requestHeaders.get(key)); + for (Entry entry : requestHeaders.entrySet()) { + connection.addRequestProperty(entry.getKey(), entry.getValue()); } return getHtmlDocument(connection); diff --git a/source/net/sourceforge/filebot/web/OpenSubtitlesClient.java b/source/net/sourceforge/filebot/web/OpenSubtitlesClient.java index b622f45f..33f0586e 100644 --- a/source/net/sourceforge/filebot/web/OpenSubtitlesClient.java +++ b/source/net/sourceforge/filebot/web/OpenSubtitlesClient.java @@ -9,8 +9,6 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -21,23 +19,10 @@ import redstone.xmlrpc.XmlRpcFault; /** * Client for the OpenSubtitles XML-RPC API. - * */ public class OpenSubtitlesClient { - /** - * - * - * - * - * - * - * - * - * - *
Main server:http://www.opensubtitles.org/xml-rpc
Developing tests:http://dev.opensubtitles.org/xml-rpc
- */ - private final String url = "http://www.opensubtitles.org/xml-rpc"; + private static final String url = "http://www.opensubtitles.org/xml-rpc"; private final String useragent; @@ -60,7 +45,7 @@ public class OpenSubtitlesClient { /** - * Login as user and use english as language + * Login as user and use English as language * * @param username * @param password @@ -77,8 +62,9 @@ public class OpenSubtitlesClient { * * @param username username (blank for anonymous user) * @param password password (blank for anonymous user) - * @param language
ISO639 - * 2 letter codes as language and later communication will be done in this + * @param language ISO639 + * 2-letter codes as language and later communication will be done in this * language if applicable (error codes and so on). */ @SuppressWarnings("unchecked") @@ -116,7 +102,7 @@ public class OpenSubtitlesClient { /** - * Check status whether it is OK or not + * Check whether status is OK or not * * @param status status code and message (e.g. 200 OK, 401 Unauthorized, ...) * @throws XmlRpcFault thrown if status code is not OK @@ -139,9 +125,7 @@ public class OpenSubtitlesClient { XmlRpcClient rpc = new XmlRpcClient(url, false); return rpc.invoke(method, arguments); } catch (MalformedURLException e) { - // will never happen - Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, "Invalid xml-rpc url: " + url, e); - return null; + throw new RuntimeException(e); } } @@ -169,14 +153,15 @@ public class OpenSubtitlesClient { Map>> response = (Map>>) invoke("SearchSubtitles", token, searchList); - ArrayList subs = new ArrayList(); + List subs = new ArrayList(); - if (!(response.get("data") instanceof List)) - throw new XmlRpcException("Illegal response: " + response.toString()); - - // if there was an error data may not be a list - for (Map subtitle : response.get("data")) { - subs.add(new OpenSubtitlesSubtitleDescriptor(subtitle)); + try { + for (Map subtitle : response.get("data")) { + subs.add(new OpenSubtitlesSubtitleDescriptor(subtitle)); + } + } catch (ClassCastException e) { + // if the response is an error message, generic types won't match + throw new XmlRpcException("Illegal response: " + response.toString(), e); } return subs; diff --git a/source/net/sourceforge/filebot/web/OpenSubtitlesHasher.java b/source/net/sourceforge/filebot/web/OpenSubtitlesHasher.java index d1c2cfdb..6d9dd219 100644 --- a/source/net/sourceforge/filebot/web/OpenSubtitlesHasher.java +++ b/source/net/sourceforge/filebot/web/OpenSubtitlesHasher.java @@ -31,12 +31,14 @@ public class OpenSubtitlesHasher { FileChannel fileChannel = new FileInputStream(file).getChannel(); - long head = computeHashForChunk(fileChannel, 0, chunkSizeForFile); - long tail = computeHashForChunk(fileChannel, Math.max(size - HASH_CHUNK_SIZE, 0), chunkSizeForFile); - - fileChannel.close(); - - return String.format("%016x", size + head + tail); + try { + long head = computeHashForChunk(fileChannel, 0, chunkSizeForFile); + long tail = computeHashForChunk(fileChannel, Math.max(size - HASH_CHUNK_SIZE, 0), chunkSizeForFile); + + return String.format("%016x", size + head + tail); + } finally { + fileChannel.close(); + } } diff --git a/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleClient.java b/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleClient.java index 874872f1..ba9e2b81 100644 --- a/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleClient.java +++ b/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleClient.java @@ -19,7 +19,6 @@ import net.sourceforge.filebot.resources.ResourceManager; /** * {@link SubtitleClient} for OpenSubtitles. - * */ public class OpenSubtitlesSubtitleClient implements SubtitleClient { @@ -102,7 +101,7 @@ public class OpenSubtitlesSubtitleClient implements SubtitleClient { private class LogoutTimer { - private final long LOGOUT_DELAY = 12 * 60 * 1000; // 12 minutes + private static final long LOGOUT_DELAY = 12 * 60 * 1000; // 12 minutes private Timer daemon = null; private LogoutTimerTask currentTimerTask = null; diff --git a/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleDescriptor.java b/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleDescriptor.java index ff7b48cd..b9f3b77a 100644 --- a/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleDescriptor.java +++ b/source/net/sourceforge/filebot/web/OpenSubtitlesSubtitleDescriptor.java @@ -4,6 +4,7 @@ package net.sourceforge.filebot.web; import java.net.MalformedURLException; import java.net.URL; +import java.util.HashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -58,7 +59,7 @@ public class OpenSubtitlesSubtitleDescriptor implements SubtitleDescriptor { public OpenSubtitlesSubtitleDescriptor(Map properties) { - this.properties = properties; + this.properties = new HashMap(properties); } diff --git a/source/net/sourceforge/filebot/web/SearchResultCache.java b/source/net/sourceforge/filebot/web/SearchResultCache.java deleted file mode 100644 index 8a5c4d4a..00000000 --- a/source/net/sourceforge/filebot/web/SearchResultCache.java +++ /dev/null @@ -1,39 +0,0 @@ - -package net.sourceforge.filebot.web; - - -import java.util.concurrent.ConcurrentHashMap; - - -public class SearchResultCache { - - private final ConcurrentHashMap cache = new ConcurrentHashMap(); - - - public boolean containsKey(String name) { - return cache.containsKey(key(name)); - } - - - public SearchResult get(String name) { - return cache.get(key(name)); - } - - - public void add(SearchResult searchResult) { - cache.putIfAbsent(key(searchResult.getName()), searchResult); - } - - - public void addAll(Iterable searchResults) { - for (SearchResult searchResult : searchResults) { - add(searchResult); - } - } - - - private String key(String name) { - return name.toLowerCase(); - } - -} diff --git a/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java b/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java index a4ea9f36..10a6792d 100644 --- a/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java +++ b/source/net/sourceforge/filebot/web/SubsceneSubtitleClient.java @@ -9,7 +9,6 @@ import java.net.URI; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -34,12 +33,10 @@ import org.xml.sax.SAXException; public class SubsceneSubtitleClient implements SubtitleClient { - private final SearchResultCache searchResultCache = new SearchResultCache(); + private static final String host = "subscene.com"; private final Map languageFilterMap = new ConcurrentHashMap(50); - private final String host = "subscene.com"; - @Override public String getName() { @@ -55,9 +52,6 @@ public class SubsceneSubtitleClient implements SubtitleClient { @Override public List search(String searchterm) throws IOException, SAXException { - if (searchResultCache.containsKey(searchterm)) { - return Collections.singletonList(searchResultCache.get(searchterm)); - } Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm)); @@ -103,8 +97,6 @@ public class SubsceneSubtitleClient implements SubtitleClient { } } - searchResultCache.addAll(searchResults); - return searchResults; } @@ -224,7 +216,7 @@ public class SubsceneSubtitleClient implements SubtitleClient { Matcher matcher = hrefPattern.matcher(href); if (!matcher.matches()) - throw new IllegalArgumentException("Cannot extract download parameters: " + href); + throw new IllegalArgumentException("Cannot parse download parameters: " + href); String subtitleId = matcher.group(1); String typeId = matcher.group(2); diff --git a/source/net/sourceforge/filebot/web/TVDotComClient.java b/source/net/sourceforge/filebot/web/TVDotComClient.java index 767b1e83..1870b0d1 100644 --- a/source/net/sourceforge/filebot/web/TVDotComClient.java +++ b/source/net/sourceforge/filebot/web/TVDotComClient.java @@ -10,7 +10,6 @@ import java.net.URL; import java.net.URLEncoder; import java.text.NumberFormat; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.Callable; @@ -33,9 +32,7 @@ import org.xml.sax.SAXException; public class TVDotComClient implements EpisodeListClient { - private final SearchResultCache searchResultCache = new SearchResultCache(); - - private final String host = "www.tv.com"; + private static final String host = "www.tv.com"; @Override @@ -58,9 +55,6 @@ public class TVDotComClient implements EpisodeListClient { @Override public List search(String searchterm) throws IOException, SAXException { - if (searchResultCache.containsKey(searchterm)) { - return Collections.singletonList(searchResultCache.get(searchterm)); - } Document dom = HtmlUtil.getHtmlDocument(getSearchUrl(searchterm)); @@ -81,8 +75,6 @@ public class TVDotComClient implements EpisodeListClient { } } - searchResultCache.addAll(searchResults); - return searchResults; } @@ -99,7 +91,7 @@ public class TVDotComClient implements EpisodeListClient { List>> futures = new ArrayList>>(seasonCount); if (seasonCount > 1) { - // max. 12 threads so we don't get too many concurrent downloads + // max. 12 threads so we don't get too many concurrent connections ExecutorService executor = Executors.newFixedThreadPool(Math.min(seasonCount - 1, 12)); // we already have the document for season 1, start with season 2 @@ -111,7 +103,7 @@ public class TVDotComClient implements EpisodeListClient { executor.shutdown(); } - List episodes = new ArrayList(150); + List episodes = new ArrayList(25 * seasonCount); // get episode list from season 1 document episodes.addAll(getEpisodeList(searchResult, 1, dom)); diff --git a/source/net/sourceforge/filebot/web/TVRageClient.java b/source/net/sourceforge/filebot/web/TVRageClient.java index f2998a1f..3fd3c31d 100644 --- a/source/net/sourceforge/filebot/web/TVRageClient.java +++ b/source/net/sourceforge/filebot/web/TVRageClient.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.net.URI; import java.net.URLEncoder; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.swing.Icon; @@ -23,9 +22,7 @@ import org.xml.sax.SAXException; public class TVRageClient implements EpisodeListClient { - private final SearchResultCache searchResultCache = new SearchResultCache(); - - private final String host = "www.tvrage.com"; + private static final String host = "www.tvrage.com"; @Override @@ -48,9 +45,6 @@ public class TVRageClient implements EpisodeListClient { @Override public List search(String searchterm) throws SAXException, IOException, ParserConfigurationException { - if (searchResultCache.containsKey(searchterm)) { - return Collections.singletonList(searchResultCache.get(searchterm)); - } String searchUri = String.format("http://" + host + "/feeds/search.php?show=" + URLEncoder.encode(searchterm, "UTF-8")); @@ -68,8 +62,6 @@ public class TVRageClient implements EpisodeListClient { searchResults.add(new TVRageSearchResult(name, showid, link)); } - searchResultCache.addAll(searchResults); - return searchResults; } diff --git a/source/net/sourceforge/tuned/DownloadTask.java b/source/net/sourceforge/tuned/DownloadTask.java index 0d457fb7..171349e9 100644 --- a/source/net/sourceforge/tuned/DownloadTask.java +++ b/source/net/sourceforge/tuned/DownloadTask.java @@ -16,6 +16,7 @@ import java.nio.charset.Charset; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; @@ -35,7 +36,7 @@ public class DownloadTask extends SwingWorker { DONE } - private final int BUFFER_SIZE = 4 * 1024; + private static final int BUFFER_SIZE = 4 * 1024; private URL url; private ByteBuffer postdata; @@ -175,15 +176,15 @@ public class DownloadTask extends SwingWorker { int i = 0; - for (String key : parameters.keySet()) { + for (Entry entry : parameters.entrySet()) { if (i > 0) sb.append("&"); - sb.append(key); + sb.append(entry.getKey()); sb.append("="); try { - sb.append(URLEncoder.encode(parameters.get(key), "UTF-8")); + sb.append(URLEncoder.encode(entry.getValue(), "UTF-8")); } catch (UnsupportedEncodingException e) { // will never happen Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.SEVERE, e.toString(), e); diff --git a/source/net/sourceforge/tuned/FileUtil.java b/source/net/sourceforge/tuned/FileUtil.java index 0c022918..852e983e 100644 --- a/source/net/sourceforge/tuned/FileUtil.java +++ b/source/net/sourceforge/tuned/FileUtil.java @@ -16,9 +16,9 @@ public class FileUtil { public static String formatSize(long size) { if (size >= MEGA) - return String.format("%d MB", (double) size / MEGA); + return String.format("%d MB", size / MEGA); else if (size >= KILO) - return String.format("%d KB", (double) size / KILO); + return String.format("%d KB", size / KILO); else return String.format("%d Byte", size); } diff --git a/source/net/sourceforge/filebot/ListChangeSynchronizer.java b/source/net/sourceforge/tuned/ListChangeSynchronizer.java similarity index 94% rename from source/net/sourceforge/filebot/ListChangeSynchronizer.java rename to source/net/sourceforge/tuned/ListChangeSynchronizer.java index 9f7f6b56..6ca557cf 100644 --- a/source/net/sourceforge/filebot/ListChangeSynchronizer.java +++ b/source/net/sourceforge/tuned/ListChangeSynchronizer.java @@ -1,5 +1,5 @@ -package net.sourceforge.filebot; +package net.sourceforge.tuned; import java.util.List; @@ -9,7 +9,6 @@ import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventListener; -//TODO: testcase, class doc public class ListChangeSynchronizer implements ListEventListener { private final List target; diff --git a/source/net/sourceforge/tuned/MessageBus.java b/source/net/sourceforge/tuned/MessageBus.java index 1fdd218c..00f7dd90 100644 --- a/source/net/sourceforge/tuned/MessageBus.java +++ b/source/net/sourceforge/tuned/MessageBus.java @@ -19,19 +19,7 @@ public class MessageBus { return instance; } - private final Map> handlers = new HashMap>() { - - @Override - public List get(Object key) { - return super.get(key.toString().toLowerCase()); - } - - - @Override - public List put(String key, List value) { - return super.put(key.toLowerCase(), value); - } - }; + private final Map> handlers = new HashMap>(); private MessageBus() { @@ -40,11 +28,12 @@ public class MessageBus { public synchronized void addMessageHandler(String topic, MessageHandler handler) { - List list = handlers.get(topic); + + List list = handlers.get(topic.toLowerCase()); if (list == null) { list = new ArrayList(3); - handlers.put(topic, list); + handlers.put(topic.toLowerCase(), list); } list.add(handler); @@ -52,7 +41,7 @@ public class MessageBus { public synchronized void removeMessageHandler(String topic, MessageHandler handler) { - List list = handlers.get(topic); + List list = handlers.get(topic.toLowerCase()); if (list != null) { list.remove(handler); @@ -61,7 +50,7 @@ public class MessageBus { public synchronized MessageHandler[] getHandlers(String topic) { - List list = handlers.get(topic); + List list = handlers.get(topic.toLowerCase()); if (list == null) return new MessageHandler[0]; @@ -70,17 +59,15 @@ public class MessageBus { } - public void publish(final String topic, final String... messages) { - + public void publish(final String topic, final Object... messages) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - for (MessageHandler handler : getHandlers(topic)) { - handler.handle(topic, messages); + for (MessageHandler handler : getHandlers(topic.toLowerCase())) { + handler.handle(topic.toLowerCase(), messages); } } }); } - } diff --git a/source/net/sourceforge/tuned/MessageHandler.java b/source/net/sourceforge/tuned/MessageHandler.java index 9a9f2222..94ebfb25 100644 --- a/source/net/sourceforge/tuned/MessageHandler.java +++ b/source/net/sourceforge/tuned/MessageHandler.java @@ -2,10 +2,8 @@ package net.sourceforge.tuned; - - public interface MessageHandler { - public void handle(String topic, String... messages); + public void handle(String topic, Object... messages); } diff --git a/source/net/sourceforge/tuned/PreferencesList.java b/source/net/sourceforge/tuned/PreferencesList.java index 746705e3..0693f6ee 100644 --- a/source/net/sourceforge/tuned/PreferencesList.java +++ b/source/net/sourceforge/tuned/PreferencesList.java @@ -44,10 +44,14 @@ public class PreferencesList extends AbstractList { } - //TODO: assert invalid index @Override public void add(int index, T element) { - copy(index, index + 1, size() - index); + int size = size(); + + if (index > size) + throw new IndexOutOfBoundsException(String.format("Index: %d, Size: %d", index, size)); + + copy(index, index + 1, size - index); setImpl(index, element); } @@ -99,12 +103,6 @@ public class PreferencesList extends AbstractList { } - public void set(List data) { - clear(); - addAll(data); - } - - public static PreferencesList map(Preferences prefs, Class type) { return new PreferencesList(PreferencesMap.map(prefs, type)); } @@ -113,4 +111,5 @@ public class PreferencesList extends AbstractList { public static PreferencesList map(Preferences prefs, Adapter adapter) { return new PreferencesList(PreferencesMap.map(prefs, adapter)); } + } diff --git a/source/net/sourceforge/tuned/PreferencesMap.java b/source/net/sourceforge/tuned/PreferencesMap.java index 84b4e3b6..93cc22ad 100644 --- a/source/net/sourceforge/tuned/PreferencesMap.java +++ b/source/net/sourceforge/tuned/PreferencesMap.java @@ -81,12 +81,6 @@ public class PreferencesMap implements Map { } - public void set(Map data) { - clear(); - putAll(data); - } - - @Override public boolean containsKey(Object key) { if (key instanceof String) { @@ -134,8 +128,8 @@ public class PreferencesMap implements Map { @Override public void putAll(Map map) { - for (String key : map.keySet()) { - put(key, map.get(key)); + for (Map.Entry entry : map.entrySet()) { + put(entry.getKey(), entry.getValue()); } } diff --git a/source/net/sourceforge/tuned/TemporaryFolder.java b/source/net/sourceforge/tuned/TemporaryFolder.java index 741831b9..1cf45df0 100644 --- a/source/net/sourceforge/tuned/TemporaryFolder.java +++ b/source/net/sourceforge/tuned/TemporaryFolder.java @@ -19,11 +19,11 @@ public class TemporaryFolder { public static TemporaryFolder getFolder(String name) { synchronized (folders) { - TemporaryFolder folder = folders.get(name); + TemporaryFolder folder = folders.get(name.toLowerCase()); if (folder == null) { folder = new TemporaryFolder(new File(tmpdir, name)); - folders.put(name, folder); + folders.put(name.toLowerCase(), folder); } return folder; @@ -63,14 +63,12 @@ public class TemporaryFolder { * @throws IOException if an I/O error occurred */ public File createFile(String name) throws IOException { - if (!root.exists()) { - root.mkdir(); - } - File file = new File(root, name); + File file = new File(getFolder(), name); file.createNewFile(); return file; + } @@ -87,16 +85,12 @@ public class TemporaryFolder { * @see File#createTempFile(String, String) */ public File createFile(String prefix, String suffix) throws IOException { - if (!root.exists()) { - root.mkdir(); - } - - return File.createTempFile(prefix, suffix, root); + return File.createTempFile(prefix, suffix, getFolder()); } public boolean deleteFile(String name) { - return new File(root, name).delete(); + return new File(getFolder(), name).delete(); } @@ -106,20 +100,15 @@ public class TemporaryFolder { * @return the {@link File} object for this {@link TemporaryFolder} */ public File getFolder() { + if (!root.exists()) + root.mkdirs(); + return root; } public TemporaryFolder createFolder(String name) { - if (!root.exists()) { - root.mkdir(); - } - - TemporaryFolder folder = new TemporaryFolder(new File(root, name)); - - folder.root.mkdir(); - - return folder; + return new TemporaryFolder(new File(getFolder(), name)); } diff --git a/source/net/sourceforge/tuned/ui/ArrayListModel.java b/source/net/sourceforge/tuned/ui/ArrayListModel.java new file mode 100644 index 00000000..2eae9bf4 --- /dev/null +++ b/source/net/sourceforge/tuned/ui/ArrayListModel.java @@ -0,0 +1,45 @@ + +package net.sourceforge.tuned.ui; + + +import java.util.ArrayList; +import java.util.Collection; + +import javax.swing.ListModel; +import javax.swing.event.ListDataListener; + + +public class ArrayListModel implements ListModel { + + private final ArrayList data; + + + public ArrayListModel(Collection data) { + this.data = new ArrayList(data); + } + + + @Override + public Object getElementAt(int index) { + return data.get(index); + } + + + @Override + public int getSize() { + return data.size(); + } + + + @Override + public void addListDataListener(ListDataListener l) { + // ignore, model is unmodifiable + } + + + @Override + public void removeListDataListener(ListDataListener l) { + // ignore, model is unmodifiable + } + +} diff --git a/source/net/sourceforge/tuned/ui/FancyBorder.java b/source/net/sourceforge/tuned/ui/FancyBorder.java deleted file mode 100644 index aa63a090..00000000 --- a/source/net/sourceforge/tuned/ui/FancyBorder.java +++ /dev/null @@ -1,85 +0,0 @@ - -package net.sourceforge.tuned.ui; - - -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Component; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Insets; -import java.awt.RadialGradientPaint; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.MultipleGradientPaint.CycleMethod; -import java.awt.geom.Point2D; -import java.awt.geom.RoundRectangle2D; - -import javax.swing.border.Border; - - -public class FancyBorder implements Border { - - private int borderWidth; - - private float[] dist; - private Color[] colors; - - private float radius; - - - public FancyBorder(int width, Color... colors) { - this.borderWidth = width; - - this.dist = new float[colors.length]; - - for (int i = 0; i < colors.length; i++) { - this.dist[i] = (1.0f / colors.length) * i; - } - - this.colors = colors; - - this.radius = 100; - } - - - public FancyBorder(int width, float[] dist, Color[] colors, float radius) { - this.borderWidth = width; - this.dist = dist; - this.colors = colors; - this.radius = radius; - } - - - @Override - public Insets getBorderInsets(Component c) { - - int horizontalOffset = 8; - return new Insets(borderWidth, borderWidth + horizontalOffset, borderWidth, borderWidth + horizontalOffset); - } - - - @Override - public boolean isBorderOpaque() { - return false; - } - - - @Override - public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { - Graphics2D g2d = (Graphics2D) g; - - float arch = Math.min(width, height) / 2; - - Shape shape = new RoundRectangle2D.Float(x + borderWidth, y + borderWidth, width - borderWidth * 2, height - borderWidth * 2, arch, arch); - - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - Point2D center = new Point2D.Float(width, 0); - g2d.setPaint(new RadialGradientPaint(center, radius, dist, colors, CycleMethod.REFLECT)); - - g2d.setStroke(new BasicStroke(borderWidth)); - - g2d.draw(shape); - } -} diff --git a/source/net/sourceforge/tuned/ui/IconViewPanel.java b/source/net/sourceforge/tuned/ui/IconViewPanel.java index 268f783f..c8057ce2 100644 --- a/source/net/sourceforge/tuned/ui/IconViewPanel.java +++ b/source/net/sourceforge/tuned/ui/IconViewPanel.java @@ -13,10 +13,12 @@ import java.awt.Insets; import java.awt.Paint; import java.awt.geom.Rectangle2D; +import javax.swing.DefaultListModel; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.ListModel; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; @@ -24,8 +26,10 @@ import javax.swing.border.EmptyBorder; public class IconViewPanel extends JPanel { - private final JList list = new JList(new SimpleListModel()); - private final JLabel title = new JLabel(); + private final JList list = new JList(createModel()); + + private final JLabel titleLabel = new JLabel(); + private final JPanel headerPanel = new JPanel(new BorderLayout()); @@ -35,10 +39,10 @@ public class IconViewPanel extends JPanel { list.setLayoutOrientation(JList.HORIZONTAL_WRAP); list.setVisibleRowCount(-1); - title.setFont(title.getFont().deriveFont(Font.BOLD)); + titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD)); headerPanel.setOpaque(false); - headerPanel.add(title, BorderLayout.WEST); + headerPanel.add(titleLabel, BorderLayout.WEST); setBackground(list.getBackground()); @@ -53,13 +57,23 @@ public class IconViewPanel extends JPanel { } + protected ListModel createModel() { + return new DefaultListModel(); + } + + public JPanel getHeaderPanel() { return headerPanel; } public void setTitle(String text) { - title.setText(text); + titleLabel.setText(text); + } + + + public String getTitle() { + return titleLabel.getText(); } diff --git a/source/net/sourceforge/tuned/ui/ProgressDialog.java b/source/net/sourceforge/tuned/ui/ProgressDialog.java index 18e16135..59fa1e9e 100644 --- a/source/net/sourceforge/tuned/ui/ProgressDialog.java +++ b/source/net/sourceforge/tuned/ui/ProgressDialog.java @@ -93,7 +93,7 @@ public class ProgressDialog extends JDialog { setLocation(TunedUtil.getPreferredLocation(this)); // Shortcut Escape - TunedUtil.registerActionForKeystroke(c, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction); + TunedUtil.putActionForKeystroke(c, KeyStroke.getKeyStroke("released ESCAPE"), cancelAction); } diff --git a/source/net/sourceforge/tuned/ui/SelectButtonTextField.java b/source/net/sourceforge/tuned/ui/SelectButtonTextField.java index b6575e7d..460b6dcc 100644 --- a/source/net/sourceforge/tuned/ui/SelectButtonTextField.java +++ b/source/net/sourceforge/tuned/ui/SelectButtonTextField.java @@ -17,25 +17,25 @@ import javax.swing.KeyStroke; import javax.swing.border.Border; import javax.swing.plaf.ComboBoxUI; import javax.swing.plaf.basic.BasicComboBoxUI; +import javax.swing.text.JTextComponent; import net.sourceforge.filebot.resources.ResourceManager; public class SelectButtonTextField extends JPanel { - private SelectButton selectButton; + private SelectButton selectButton = new SelectButton(); private ComboBoxTextField editor = new ComboBoxTextField(); - private Color borderColor = new Color(0xA4A4A4); - public SelectButtonTextField() { setLayout(new BorderLayout(0, 0)); - selectButton = new SelectButton(); selectButton.addActionListener(textFieldFocusOnClick); + Color borderColor = new Color(0xA4A4A4); + Border lineBorder = BorderFactory.createLineBorder(borderColor, 1); Border matteBorder = BorderFactory.createMatteBorder(1, 0, 1, 1, borderColor); Border emptyBorder = BorderFactory.createEmptyBorder(0, 3, 0, 3); @@ -48,8 +48,8 @@ public class SelectButtonTextField extends JPanel { setPreferredSize(new Dimension(280, 22)); - TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift UP"), new SpinClientAction(-1)); - TunedUtil.registerActionForKeystroke(this, KeyStroke.getKeyStroke("shift DOWN"), new SpinClientAction(1)); + TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("shift UP"), new SpinClientAction(-1)); + TunedUtil.putActionForKeystroke(this, KeyStroke.getKeyStroke("shift DOWN"), new SpinClientAction(1)); } @@ -57,7 +57,7 @@ public class SelectButtonTextField extends JPanel { * Convenience method for getEditor().getSelectedItem().toString() */ public String getText() { - return getEditor().getSelectedItem().toString(); + return editor.getText(); } @@ -116,6 +116,28 @@ public class SelectButtonTextField extends JPanel { // don't reset the UI delegate if laf is changed, or we use our custom ui } + + public String getText() { + return ((TextFieldComboBoxUI) getUI()).getEditor().getText(); + } + + + @Override + public void actionPerformed(ActionEvent e) { + // TODO Auto-generated method stub + // super.actionPerformed(e); + // Object newItem = getEditor().getItem(); + // setPopupVisible(false); + // getModel().setSelectedItem(newItem); + // String oldCommand = getActionCommand(); + // setActionCommand("comboBoxEdited"); + // + //TODO sysout + System.out.println("combobox: " + e); + // for (ActionListener actionListener : getActionListeners()) { + // actionListener.actionPerformed(e); + // } + } } @@ -123,14 +145,48 @@ public class SelectButtonTextField extends JPanel { @Override protected JButton createArrowButton() { - JButton b = new JButton(ResourceManager.getIcon("action.list")); - - b.setContentAreaFilled(false); - b.setFocusable(false); - - return b; + return new JButton(ResourceManager.getIcon("action.list")); } + + @Override + public void configureArrowButton() { + super.configureArrowButton(); + + arrowButton.setContentAreaFilled(false); + arrowButton.setFocusable(false); + } + + + @Override + protected void configureEditor() { + editor.setEnabled(comboBox.isEnabled()); + editor.setFocusable(comboBox.isFocusable()); + editor.setFont(comboBox.getFont()); + + editor.addFocusListener(createFocusListener()); + } + + + public JTextComponent getEditor() { + return (JTextComponent) editor; + } + + // @Override + // protected FocusListener createFocusListener() { + // return new FocusHandler() { + // + // /** + // * Prevent action events from being fired on focusLost. + // */ + // @Override + // public void focusLost(FocusEvent e) { + // if (isPopupVisible(comboBox)) + // setPopupVisible(comboBox, false); + // } + // }; + // } + } } diff --git a/source/net/sourceforge/tuned/ui/SimpleLabelProvider.java b/source/net/sourceforge/tuned/ui/SimpleLabelProvider.java index b77021c1..a1a3b661 100644 --- a/source/net/sourceforge/tuned/ui/SimpleLabelProvider.java +++ b/source/net/sourceforge/tuned/ui/SimpleLabelProvider.java @@ -3,6 +3,7 @@ package net.sourceforge.tuned.ui; import java.lang.reflect.Method; +import java.util.Arrays; import javax.swing.Icon; @@ -15,11 +16,11 @@ import net.sourceforge.tuned.ExceptionUtil; public class SimpleLabelProvider implements LabelProvider { private final Method getIconMethod; - private final Method getNameMethod; + private final Method getTextMethod; /** - * Same as new SimpleLabelProvider<T>(T.class). + * Factory method for {@link #SimpleLabelProvider(Class)}. * * @return new LabelProvider */ @@ -29,13 +30,15 @@ public class SimpleLabelProvider implements LabelProvider { /** - * Create a new LabelProvider which will use the getName and - * getIcon method of the given class. + * Create a new LabelProvider which will use the getText, getName + * or toString method for text and the getIcon method for the + * icon. * - * @param type a class that has a getName and a getIcon method + * @param type a class that has one of the text methods and the icon method */ public SimpleLabelProvider(Class type) { - this(type, "getName", "getIcon"); + getTextMethod = findAnyMethod(type, "getText", "getName", "toString"); + getIconMethod = findAnyMethod(type, "getIcon"); } @@ -43,23 +46,32 @@ public class SimpleLabelProvider implements LabelProvider { * Create a new LabelProvider which will use a specified method of a given class * * @param type a class with the specified method - * @param getName a method name such as getName + * @param getText a method name such as getText * @param getIcon a method name such as getIcon */ - public SimpleLabelProvider(Class type, String getName, String getIcon) { - try { - getNameMethod = type.getMethod(getName); - getIconMethod = type.getMethod(getIcon); - } catch (Exception e) { - throw new RuntimeException(e); + public SimpleLabelProvider(Class type, String getText, String getIcon) { + getTextMethod = findAnyMethod(type, getText); + getIconMethod = findAnyMethod(type, getIcon); + } + + + private Method findAnyMethod(Class type, String... names) { + for (String name : names) { + try { + return type.getMethod(name); + } catch (NoSuchMethodException e) { + // try next method name + } } + + throw new IllegalArgumentException("Method not found: " + Arrays.toString(names)); } @Override public String getText(T value) { try { - return (String) getNameMethod.invoke(value); + return (String) getTextMethod.invoke(value); } catch (Exception e) { throw ExceptionUtil.asRuntimeException(e); } diff --git a/source/net/sourceforge/tuned/ui/SimpleListModel.java b/source/net/sourceforge/tuned/ui/SimpleListModel.java deleted file mode 100644 index bf416141..00000000 --- a/source/net/sourceforge/tuned/ui/SimpleListModel.java +++ /dev/null @@ -1,139 +0,0 @@ - -package net.sourceforge.tuned.ui; - - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import javax.swing.AbstractListModel; - - -public class SimpleListModel extends AbstractListModel { - - private final List list; - - - public SimpleListModel() { - list = Collections.synchronizedList(new ArrayList()); - } - - - public SimpleListModel(Collection collection) { - list = Collections.synchronizedList(new ArrayList(collection)); - } - - - @Override - public Object getElementAt(int index) { - return list.get(index); - } - - - @Override - public int getSize() { - return list.size(); - } - - - public void add(Object object) { - int index = list.size(); - list.add(object); - fireIntervalAdded(this, index, index); - } - - - public void add(int index, Object object) { - list.add(index, object); - fireIntervalAdded(this, index, index); - } - - - public void addAll(Collection c) { - int begin = list.size(); - list.addAll(c); - int end = list.size() - 1; - - if (end >= 0) { - fireIntervalAdded(this, begin, end); - } - } - - - public void clear() { - int end = list.size() - 1; - list.clear(); - - if (end >= 0) { - fireIntervalRemoved(this, 0, end); - } - } - - - public boolean contains(Object o) { - return list.contains(o); - } - - - public int indexOf(Object o) { - return list.indexOf(o); - } - - - public boolean isEmpty() { - return list.isEmpty(); - } - - - public int lastIndexOf(Object o) { - return list.lastIndexOf(o); - } - - - public Object remove(int index) { - Object object = list.remove(index); - - fireIntervalRemoved(this, index, index); - - return object; - } - - - public void remove(Object object) { - synchronized (list) { - remove(indexOf(object)); - } - } - - - public void sort() { - synchronized (list) { - Collections.sort(list, null); - } - - fireContentsChanged(this, 0, list.size() - 1); - } - - - public List getCopy() { - synchronized (list) { - return new ArrayList(list); - } - } - - - public void set(Collection c) { - int end = Math.max(list.size(), c.size()) - 1; - - synchronized (list) { - list.clear(); - list.addAll(c); - } - - if (end >= 0) { - fireContentsChanged(this, 0, end); - } - } - -} diff --git a/source/net/sourceforge/tuned/ui/TextCompletion.java b/source/net/sourceforge/tuned/ui/TextCompletion.java deleted file mode 100644 index 0757532f..00000000 --- a/source/net/sourceforge/tuned/ui/TextCompletion.java +++ /dev/null @@ -1,129 +0,0 @@ - -package net.sourceforge.tuned.ui; - - -import java.util.Collection; -import java.util.Collections; -import java.util.Set; -import java.util.TreeSet; - -import javax.swing.SwingUtilities; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; -import javax.swing.text.JTextComponent; - - -public class TextCompletion { - - private Set completionTerms = Collections.synchronizedSet(new TreeSet(String.CASE_INSENSITIVE_ORDER)); - - private int completionStartLength = 1; - - private JTextComponent component; - - - public TextCompletion(JTextComponent component) { - this.component = component; - } - - - public void hook() { - component.getDocument().addDocumentListener(documentListener); - } - - - public void unhook() { - component.getDocument().removeDocumentListener(documentListener); - } - - - public void addTerm(String term) { - completionTerms.add(term); - } - - - public void addTerms(Collection terms) { - completionTerms.addAll(terms); - } - - - public void removeTerm(String term) { - completionTerms.remove(term); - } - - - public void removeTerms(Collection terms) { - completionTerms.removeAll(terms); - } - - - public void setStartLength(int codeCompletionStartLength) { - this.completionStartLength = codeCompletionStartLength; - } - - - public Set getTerms() { - return completionTerms; - } - - - public int getStartLength() { - return completionStartLength; - } - - - private void complete() { - String text = component.getText(); - - if (text.length() < completionStartLength) - return; - - String completionTerm = findCompletionTerm(text); - - if (completionTerm == null) - return; - - component.setText(completionTerm); - component.select(text.length(), completionTerm.length()); - } - - - private String findCompletionTerm(String text) { - for (String completionTerm : completionTerms) { - if (text.length() >= completionTerm.length()) - continue; - - String compareTerm = completionTerm.substring(0, text.length()); - - if (text.equalsIgnoreCase(compareTerm)) - return completionTerm; - } - - return null; - } - - private final DocumentListener documentListener = new DocumentListener() { - - public void changedUpdate(DocumentEvent e) { - } - - - public void insertUpdate(DocumentEvent e) { - SwingUtilities.invokeLater(doComplete); - } - - - public void removeUpdate(DocumentEvent e) { - - } - - }; - - private final Runnable doComplete = new Runnable() { - - public void run() { - complete(); - } - }; - -} diff --git a/source/net/sourceforge/tuned/ui/TunedUtil.java b/source/net/sourceforge/tuned/ui/TunedUtil.java index c2ca2714..275c44a3 100644 --- a/source/net/sourceforge/tuned/ui/TunedUtil.java +++ b/source/net/sourceforge/tuned/ui/TunedUtil.java @@ -13,20 +13,24 @@ import java.awt.image.BufferedImage; import javax.swing.Action; import javax.swing.Icon; +import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; import javax.swing.Timer; public class TunedUtil { - private TunedUtil() { - // hide constructor + public static void checkEventDispatchThread() { + if (!SwingUtilities.isEventDispatchThread()) { + throw new IllegalStateException("Method must be accessed from the Swing Event Dispatch Thread, but was called on Thread \"" + Thread.currentThread().getName() + "\""); + } } - public static void registerActionForKeystroke(JComponent component, KeyStroke keystroke, Action action) { + public static void putActionForKeystroke(JComponent component, KeyStroke keystroke, Action action) { Integer key = action.hashCode(); component.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(keystroke, key); component.getActionMap().put(key, action); @@ -47,10 +51,9 @@ public class TunedUtil { public static Image getImage(Icon icon) { - //TODO uncomment - // if (icon instanceof ImageIcon) { - // return ((ImageIcon) icon).getImage(); - // } + if (icon instanceof ImageIcon) { + return ((ImageIcon) icon).getImage(); + } BufferedImage image = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB); @@ -78,4 +81,9 @@ public class TunedUtil { return timer; } + + private TunedUtil() { + // hide constructor + } + } diff --git a/source/net/sourceforge/tuned/ui/notification/Factor.java b/source/net/sourceforge/tuned/ui/notification/Factor.java index fd4036c6..67f09a2a 100644 --- a/source/net/sourceforge/tuned/ui/notification/Factor.java +++ b/source/net/sourceforge/tuned/ui/notification/Factor.java @@ -5,17 +5,12 @@ package net.sourceforge.tuned.ui.notification; import javax.swing.SwingConstants; -class Factor extends Object implements SwingConstants { +class Factor implements SwingConstants { - public double fx = 0; - public double fy = 0; + public final double fx; + public final double fy; - public Factor() { - - } - - public Factor(double fx, double fy) { this.fx = fx; this.fy = fy; diff --git a/source/net/sourceforge/tuned/ui/notification/NotificationManager.java b/source/net/sourceforge/tuned/ui/notification/NotificationManager.java index e3a49428..9a223474 100644 --- a/source/net/sourceforge/tuned/ui/notification/NotificationManager.java +++ b/source/net/sourceforge/tuned/ui/notification/NotificationManager.java @@ -6,13 +6,15 @@ package net.sourceforge.tuned.ui.notification; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import net.sourceforge.tuned.ui.TunedUtil; public class NotificationManager { - private NotificationLayout layout; + private final NotificationLayout layout; public NotificationManager() { @@ -21,45 +23,25 @@ public class NotificationManager { public NotificationManager(NotificationLayout layout) { - setLayoutManager(layout); - } - - - public void setLayoutManager(NotificationLayout layout) { this.layout = layout; } - public NotificationLayout getLayoutManager() { - return layout; - } - - public void show(NotificationWindow notification) { - if (layout == null) - return; + TunedUtil.checkEventDispatchThread(); - notification.addComponentListener(new RemoveListener(layout)); + notification.addWindowListener(new RemoveListener()); layout.add(notification); + notification.setVisible(true); } - private static class RemoveListener extends ComponentAdapter { + private class RemoveListener extends WindowAdapter { - private NotificationLayout layout; - - - public RemoveListener(NotificationLayout layout) { - this.layout = layout; - } - - @Override - public void componentHidden(ComponentEvent e) { - NotificationWindow window = (NotificationWindow) e.getSource(); - layout.remove(window); - window.dispose(); + public void windowClosing(WindowEvent e) { + layout.remove((NotificationWindow) e.getWindow()); } } diff --git a/source/net/sourceforge/tuned/ui/notification/NotificationWindow.java b/source/net/sourceforge/tuned/ui/notification/NotificationWindow.java index b0039b6e..388bbf79 100644 --- a/source/net/sourceforge/tuned/ui/notification/NotificationWindow.java +++ b/source/net/sourceforge/tuned/ui/notification/NotificationWindow.java @@ -12,6 +12,7 @@ import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.event.WindowEvent; import javax.swing.JWindow; import javax.swing.Timer; @@ -21,7 +22,7 @@ import net.sourceforge.tuned.ui.TunedUtil; public class NotificationWindow extends JWindow { - private int timeout; + private final int timeout; public NotificationWindow(Window owner, int timeout) { @@ -35,17 +36,12 @@ public class NotificationWindow extends JWindow { setAlwaysOnTop(true); - if (closeOnClick) + if (closeOnClick) { getGlassPane().addMouseListener(clickListener); + getGlassPane().setVisible(true); + } - getGlassPane().setVisible(true); - - addComponentListener(visibleListener); - } - - - public NotificationWindow(int timeout) { - this((Window) null, timeout); + addComponentListener(closeOnTimeout); } @@ -54,21 +50,23 @@ public class NotificationWindow extends JWindow { } - public NotificationWindow() { - this((Window) null, -1); - } - - public final void close() { + TunedUtil.checkEventDispatchThread(); + + // window events are not fired automatically, required for layout updates + processWindowEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); + setVisible(false); - // component hidden is not fired automatically + // component events are not fired automatically, used to cancel timeout timer processComponentEvent(new ComponentEvent(this, ComponentEvent.COMPONENT_HIDDEN)); + + dispose(); } - private ComponentListener visibleListener = new ComponentAdapter() { + private final ComponentListener closeOnTimeout = new ComponentAdapter() { - private Timer timer; + private Timer timer = null; @Override @@ -94,7 +92,7 @@ public class NotificationWindow extends JWindow { }; - private MouseAdapter clickListener = new MouseAdapter() { + private final MouseAdapter clickListener = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { diff --git a/source/net/sourceforge/tuned/ui/notification/SeparatorBorder.java b/source/net/sourceforge/tuned/ui/notification/SeparatorBorder.java index 4d704126..8c20b91c 100644 --- a/source/net/sourceforge/tuned/ui/notification/SeparatorBorder.java +++ b/source/net/sourceforge/tuned/ui/notification/SeparatorBorder.java @@ -29,7 +29,10 @@ public class SeparatorBorder extends AbstractBorder { public static enum Position { - TOP, BOTTOM, LEFT, RIGHT; + TOP, + BOTTOM, + LEFT, + RIGHT; public Rectangle2D getRectangle(RectangularShape shape, int borderWidth) { switch (this) { @@ -72,10 +75,6 @@ public class SeparatorBorder extends AbstractBorder { } - protected SeparatorBorder() { - } - - public SeparatorBorder(int height, Color color, Position position) { this(height, color, null, null, position); } diff --git a/test/net/sourceforge/filebot/ArgumentBeanTest.java b/test/net/sourceforge/filebot/ArgumentBeanTest.java new file mode 100644 index 00000000..3a89eff2 --- /dev/null +++ b/test/net/sourceforge/filebot/ArgumentBeanTest.java @@ -0,0 +1,62 @@ + +package net.sourceforge.filebot; + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.kohsuke.args4j.CmdLineException; +import org.kohsuke.args4j.CmdLineParser; + + +public class ArgumentBeanTest { + + @Test + public void clear() throws Exception { + ArgumentBean bean = parse("--sfv", "One Piece", "-clear"); + + assertTrue(bean.isClear()); + assertFalse(bean.isHelp()); + assertEquals("One Piece", bean.getSfvPanelFile().getName()); + } + + + @Test + public void noClear() throws Exception { + ArgumentBean bean = parse("-help", "--sfv", "One Piece"); + + assertTrue(bean.isHelp()); + assertFalse(bean.isClear()); + assertEquals("One Piece", bean.getSfvPanelFile().getName()); + } + + + @Test + public void oneArgument() throws Exception { + ArgumentBean bean = parse("--sfv", "One Piece.sfv"); + + assertFalse(bean.isClear()); + assertFalse(bean.isHelp()); + assertEquals("One Piece.sfv", bean.getSfvPanelFile().getName()); + } + + + @Test + public void mixedArguments() throws Exception { + ArgumentBean bean = parse("--list", "Twin Peaks.txt", "--sfv", "Death Note.sfv"); + + assertEquals("Twin Peaks.txt", bean.getListPanelFile().getName()); + assertEquals("Death Note.sfv", bean.getSfvPanelFile().getName()); + } + + + private static ArgumentBean parse(String... args) throws CmdLineException { + ArgumentBean bean = new ArgumentBean(); + + new CmdLineParser(bean).parseArgument(args); + + return bean; + } +} diff --git a/test/net/sourceforge/filebot/FileBotTestSuite.java b/test/net/sourceforge/filebot/FileBotTestSuite.java index 5aae87fc..dd73a3ac 100644 --- a/test/net/sourceforge/filebot/FileBotTestSuite.java +++ b/test/net/sourceforge/filebot/FileBotTestSuite.java @@ -13,7 +13,7 @@ import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) -@SuiteClasses( { MatcherTestSuite.class, WebTestSuite.class }) +@SuiteClasses( { MatcherTestSuite.class, WebTestSuite.class, ArgumentBeanTest.class }) public class FileBotTestSuite { public static Test suite() {