remove reader ui/tui
authorNiki Roo <niki@nikiroo.be>
Tue, 5 May 2020 16:21:24 +0000 (18:21 +0200)
committerNiki Roo <niki@nikiroo.be>
Tue, 5 May 2020 16:21:24 +0000 (18:21 +0200)
34 files changed:
configure.sh
src/be/nikiroo/fanfix/Instance.java
src/be/nikiroo/fanfix/Main.java
src/be/nikiroo/fanfix/bundles/Config.java
src/be/nikiroo/fanfix/reader/BasicReader.java
src/be/nikiroo/fanfix/reader/CliReader.java [moved from src/be/nikiroo/fanfix/reader/cli/CliReader.java with 72% similarity]
src/be/nikiroo/fanfix/reader/Reader.java [deleted file]
src/be/nikiroo/fanfix/reader/tui/ConfigItem.java [deleted file]
src/be/nikiroo/fanfix/reader/tui/ConfigItemString.java [deleted file]
src/be/nikiroo/fanfix/reader/tui/TOptionWindow.java [deleted file]
src/be/nikiroo/fanfix/reader/tui/TSimpleScrollableWindow.java [deleted file]
src/be/nikiroo/fanfix/reader/tui/TuiReader.java [deleted file]
src/be/nikiroo/fanfix/reader/tui/TuiReaderApplication.java [deleted file]
src/be/nikiroo/fanfix/reader/tui/TuiReaderMainWindow.java [deleted file]
src/be/nikiroo/fanfix/reader/tui/TuiReaderOptionWindow.java [deleted file]
src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReader.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderBookInfo.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderCoverImager.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderGroup.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderNavBar.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesPane.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchAction.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByPanel.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByTagPanel.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderViewer.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerPanel.java [deleted file]
src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerTextOutput.java [deleted file]

index 15ae18088ba8d961b401fa8187334898ebe17fad..1c1a529c9bcf3d4ec43686a94a21fc2cc29a8297 100755 (executable)
@@ -5,13 +5,6 @@ PREFIX=/usr/local
 PROGS="java javac jar make sed"
 
 IMG=be/nikiroo/utils/ui/ImageUtilsAwt
-CLI=be/nikiroo/fanfix/reader/cli/CliReader
-TUI=be/nikiroo/fanfix/reader/tui/TuiReader
-GUI=be/nikiroo/fanfix/reader/ui/GuiReader
-JIMG=
-JCLI=
-JTUI="-C bin/ jexer"
-JGUI=
 
 valid=true
 while [ "$*" != "" ]; do
@@ -31,15 +24,6 @@ while [ "$*" != "" ]; do
        --prefix) #=PATH        Change the prefix to the given path
                PREFIX="$val"
        ;;
-       --cli) #=no     Disable CLI support (System.out)
-               [ "$val" = no -o "$val" = false ] && CLI= && JCLI=
-       ;;
-       --tui) #=no     Enable TUI support (Jexer)
-               [ "$val" = no -o "$val" = false ] && TUI= && JTUI=
-       ;;
-       --gui) #=no     Disable GUI support (Swing)
-               [ "$val" = no -o "$val" = false ] && GUI= && JGUI=
-       ;;
        *)
                echo "Unsupported parameter: '$1'" >&2
                echo >&2
@@ -76,13 +60,12 @@ else
 fi;
 
 echo "MAIN = be/nikiroo/fanfix/Main" > Makefile
-echo "MORE = $CLI $TUI $GUI $IMG" >> Makefile
+echo "MORE = $IMG" >> Makefile
 echo "TEST = be/nikiroo/fanfix/test/Test" >> Makefile
 echo "TEST_PARAMS = $cols $ok $ko" >> Makefile
 echo "NAME = fanfix" >> Makefile
 echo "PREFIX = $PREFIX" >> Makefile
-echo "JAR_FLAGS += -C bin/ org $JCLI $JTUI $JGUI -C bin/ be -C ./ LICENSE -C ./ VERSION -C libs/ licenses" >> Makefile
-#echo "SJAR_FLAGS += -C src/ org -C src/ jexer -C src/ be -C ./ LICENSE -C ./ VERSION -C libs/ licenses" >> Makefile
+echo "JAR_FLAGS += -C bin/ org -C bin/ be -C ./ LICENSE -C ./ VERSION -C libs/ licenses" >> Makefile
 
 cat Makefile.base >> Makefile
 
index 5c00a0756e0aa2f484e2003135b9123d396257f0..c3c086fc9cd498ac3c61c84eebcf9e9bc831da1a 100644 (file)
@@ -284,9 +284,9 @@ public class Instance {
        }
 
        /**
-        * Get the (unique) {@link LocalLibrary} for the program.
+        * Get the (unique) {@link BasicLibrary} for the program.
         * 
-        * @return the {@link LocalLibrary}
+        * @return the {@link BasicLibrary}
         */
        public BasicLibrary getLibrary() {
                if (lib == null) {
@@ -296,6 +296,18 @@ public class Instance {
                return lib;
        }
 
+       /**
+        * Change the default {@link BasicLibrary} for this program.
+        * <p>
+        * Be careful.
+        * 
+        * @param lib
+        *            the new {@link BasicLibrary}
+        */
+       public void setLibrary(BasicLibrary lib) {
+               this.lib = lib;
+       }
+
        /**
         * Return the directory where to look for default cover pages.
         * 
index 961816a3f63e96afc4611489e5d83d85c9e2898a..8d72803aaad4b0c35b38bd676a4961b2da06c454 100644 (file)
@@ -22,8 +22,7 @@ import be.nikiroo.fanfix.library.RemoteLibraryServer;
 import be.nikiroo.fanfix.output.BasicOutput;
 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
 import be.nikiroo.fanfix.reader.BasicReader;
-import be.nikiroo.fanfix.reader.Reader;
-import be.nikiroo.fanfix.reader.Reader.ReaderType;
+import be.nikiroo.fanfix.reader.CliReader;
 import be.nikiroo.fanfix.searchable.BasicSearchable;
 import be.nikiroo.fanfix.supported.BasicSupport;
 import be.nikiroo.fanfix.supported.SupportType;
@@ -38,7 +37,7 @@ import be.nikiroo.utils.serial.server.ServerObject;
  */
 public class Main {
        private enum MainAction {
-               IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, SET_READER, START, VERSION, SERVER, STOP_SERVER, REMOTE, SET_SOURCE, SET_TITLE, SET_AUTHOR, SEARCH, SEARCH_TAG
+               IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, START, VERSION, SERVER, STOP_SERVER, REMOTE, SET_SOURCE, SET_TITLE, SET_AUTHOR, SEARCH, SEARCH_TAG
        }
 
        /**
@@ -76,8 +75,6 @@ public class Main {
         * <li>--set-source [id] [new source]: change the source of the given story</li>
         * <li>--set-title [id] [new title]: change the title of the given story</li>
         * <li>--set-author [id] [new author]: change the author of the given story</li>
-        * <li>--set-reader [reader type]: set the reader type to CLI, TUI or LOCAL
-        * for this command</li>
         * <li>--version: get the version of the program</li>
         * <li>--server: start the server mode (see config file for parameters)</li>
         * <li>--stop-server: stop the running server on this port if any</li>
@@ -314,10 +311,6 @@ public class Main {
                        case HELP:
                                exitCode = 255;
                                break;
-                       case SET_READER:
-                               exitCode = setReaderType(args[i]);
-                               action = MainAction.START;
-                               break;
                        case START:
                                exitCode = 255; // not supposed to be selected by user
                                break;
@@ -339,10 +332,11 @@ public class Main {
                                        port = Integer.parseInt(args[i]);
 
                                        BasicLibrary lib = new RemoteLibrary(key, host, port);
-                                       lib = new CacheLibrary(Instance.getInstance().getRemoteDir(host), lib,
+                                       lib = new CacheLibrary(
+                                                       Instance.getInstance().getRemoteDir(host), lib,
                                                        Instance.getInstance().getUiConfig());
 
-                                       BasicReader.setDefaultLibrary(lib);
+                                       Instance.getInstance().setLibrary(lib);
 
                                        action = MainAction.START;
                                } else {
@@ -413,11 +407,6 @@ public class Main {
                                updates.ok(); // we consider it read
                                break;
                        case LIST:
-                               if (BasicReader.getReader() == null) {
-                                       Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured"));
-                                       exitCode = 10;
-                                       break;
-                               }
                                exitCode = list(sourceString);
                                break;
                        case SET_SOURCE:
@@ -445,20 +434,46 @@ public class Main {
                                }
                                break;
                        case READ:
-                               if (BasicReader.getReader() == null) {
-                                       Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured"));
-                                       exitCode = 10;
+                               if (luid == null || luid.isEmpty()) {
+                                       syntax(false);
+                                       exitCode = 255;
                                        break;
                                }
-                               exitCode = read(luid, chapString, true);
+
+                               try {
+                                       BasicLibrary lib = Instance.getInstance().getLibrary();
+                                       exitCode = read(lib.getStory(luid, null), chapString);
+                               } catch (IOException e) {
+                                       Instance.getInstance().getTraceHandler()
+                                                       .error(new IOException("Failed to read book", e));
+                                       exitCode = 2;
+                               }
+
                                break;
                        case READ_URL:
-                               if (BasicReader.getReader() == null) {
-                                       Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured"));
-                                       exitCode = 10;
+                               if (urlString == null || urlString.isEmpty()) {
+                                       syntax(false);
+                                       exitCode = 255;
                                        break;
                                }
-                               exitCode = read(urlString, chapString, false);
+
+                               try {
+                                       BasicSupport support = BasicSupport
+                                                       .getSupport(BasicReader.getUrl(urlString));
+                                       if (support == null) {
+                                               Instance.getInstance().getTraceHandler()
+                                                               .error("URL not supported: " + urlString);
+                                               exitCode = 2;
+                                               break;
+                                       }
+
+                                       exitCode = read(support.process(null), chapString);
+                               } catch (IOException e) {
+                                       Instance.getInstance().getTraceHandler()
+                                                       .error(new IOException("Failed to read book", e));
+                                       exitCode = 2;
+                               }
+
                                break;
                        case SEARCH:
                                page = page == null ? 1 : page;
@@ -475,19 +490,13 @@ public class Main {
                                        break;
                                }
 
-                               if (BasicReader.getReader() == null) {
-                                       Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured"));
-                                       exitCode = 10;
-                                       break;
-                               }
-
                                try {
                                        if (searchOn == null) {
-                                               BasicReader.getReader().search(true);
+                                               new CliReader().listSearchables();
                                        } else if (search != null) {
 
-                                               BasicReader.getReader().search(searchOn, search, page,
-                                                               item, true);
+                                               new CliReader().searchBooksByKeyword(searchOn, search, page,
+                                                               item);
                                        } else {
                                                exitCode = 255;
                                        }
@@ -517,15 +526,9 @@ public class Main {
                                        break;
                                }
 
-                               if (BasicReader.getReader() == null) {
-                                       Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured"));
-                                       exitCode = 10;
-                                       break;
-                               }
-
                                try {
-                                       BasicReader.getReader().searchTag(searchOn, page, item,
-                                                       true, tags.toArray(new Integer[] {}));
+                                       new CliReader().searchBooksByTag(searchOn, page, item,
+                                                       tags.toArray(new Integer[] {}));
                                } catch (IOException e1) {
                                        Instance.getInstance().getTraceHandler().error(e1);
                                }
@@ -535,9 +538,6 @@ public class Main {
                                syntax(true);
                                exitCode = 0;
                                break;
-                       case SET_READER:
-                               exitCode = 255;
-                               break;
                        case VERSION:
                                System.out
                                                .println(String.format("Fanfix version %s"
@@ -547,13 +547,8 @@ public class Main {
                                updates.ok(); // we consider it read
                                break;
                        case START:
-                               if (BasicReader.getReader() == null) {
-                                       Instance.getInstance().getTraceHandler().error(new Exception("No reader type has been configured"));
-                                       exitCode = 10;
-                                       break;
-                               }
                                try {
-                                       BasicReader.getReader().browse(null);
+                                       new CliReader().listBooks(null);
                                } catch (IOException e) {
                                        Instance.getInstance().getTraceHandler().error(e);
                                        exitCode = 66;
@@ -684,9 +679,8 @@ public class Main {
         * @return the exit return code (0 = success)
         */
        private static int list(String source) {
-               BasicReader.setDefaultReaderType(ReaderType.CLI);
                try {
-                       BasicReader.getReader().browse(source);
+                       new CliReader().listBooks(source);
                } catch (IOException e) {
                        Instance.getInstance().getTraceHandler().error(e);
                        return 66;
@@ -699,41 +693,41 @@ public class Main {
         * Start the current reader for this {@link Story}.
         * 
         * @param story
-        *            the LUID of the {@link Story} in the {@link LocalLibrary}
-        *            <b>or</b> the {@link Story} {@link URL}
+        *            the story to read
         * @param chapString
         *            which {@link Chapter} to read (starting at 1), or NULL to get
         *            the {@link Story} description
-        * @param library
-        *            TRUE if the source is the {@link Story} LUID, FALSE if it is a
-        *            {@link URL}
         * 
         * @return the exit return code (0 = success)
         */
-       private static int read(String story, String chapString, boolean library) {
-               try {
-                       Reader reader = BasicReader.getReader();
-                       if (library) {
-                               reader.setMeta(story);
-                       } else {
-                               reader.setMeta(BasicReader.getUrl(story), null);
+       private static int read(Story story, String chapString) {
+               Integer chap = null;
+               if (chapString != null) {
+                       try {
+                               chap = Integer.parseInt(chapString);
+                       } catch (NumberFormatException e) {
+                               Instance.getInstance().getTraceHandler().error(new IOException(
+                                               "Chapter number cannot be parsed: " + chapString, e));
+                               return 2;
                        }
+               }
 
-                       if (chapString != null) {
-                               try {
-                                       reader.setChapter(Integer.parseInt(chapString));
-                                       reader.read(true);
-                               } catch (NumberFormatException e) {
-                                       Instance.getInstance().getTraceHandler()
-                                                       .error(new IOException("Chapter number cannot be parsed: " + chapString, e));
-                                       return 2;
+               if (story != null) {
+                       try {
+                               if (chap == null) {
+                                       new CliReader().listChapters(story);
+                               } else {
+                                       new CliReader().printChapter(story, chap);
                                }
-                       } else {
-                               reader.read(true);
+                       } catch (IOException e) {
+                               Instance.getInstance().getTraceHandler()
+                                               .error(new IOException("Failed to read book", e));
+                               return 2;
                        }
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-                       return 1;
+               } else {
+                       Instance.getInstance().getTraceHandler()
+                                       .error("Cannot find book: " + chapString);
+                       return 2;
                }
 
                return 0;
@@ -862,24 +856,4 @@ public class Main {
                        System.err.println(trans(StringId.ERR_SYNTAX));
                }
        }
-
-       /**
-        * Set the default reader type for this session only (it can be changed in
-        * the configuration file, too, but this value will override it).
-        * 
-        * @param readerTypeString
-        *            the type
-        */
-       private static int setReaderType(String readerTypeString) {
-               try {
-                       ReaderType readerType = ReaderType.valueOf(readerTypeString
-                                       .toUpperCase());
-                       BasicReader.setDefaultReaderType(readerType);
-                       return 0;
-               } catch (IllegalArgumentException e) {
-                       Instance.getInstance().getTraceHandler()
-                                       .error(new IOException("Unknown reader type: " + readerTypeString,      e));
-                       return 1;
-               }
-       }
 }
index fd27a83fba9179d01d1dd4955ffe7834906709e0..587851a2a00c6163cf5a8ff1eb93500bc09db03d 100644 (file)
@@ -16,9 +16,6 @@ public enum Config {
        @Meta(description = "The language to use for in the program (example: en-GB, fr-BE...) or nothing for default system language (can be overwritten with the variable $LANG)",//
        format = Format.LOCALE, list = { "en-GB", "fr-BE" })
        LANG, //
-       @Meta(description = "The default reader type to use to read stories:\nCLI = simple output to console\nTUI = a Text User Interface with menus and windows, based upon Jexer\nGUI = a GUI with locally stored files, based upon Swing", //
-       hidden = true, format = Format.FIXED_LIST, list = { "CLI", "GUI", "TUI" }, def = "GUI")
-       READER_TYPE, //
 
        @Meta(description = "File format options",//
        group = true)
index 7f79da3a8ec62b854b7bcdba62838a23779fcaf4..9ec08798e111e6f3325a080f2641d742f6a410cc 100644 (file)
@@ -11,171 +11,18 @@ import java.util.Map;
 import java.util.TreeMap;
 
 import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.bundles.Config;
 import be.nikiroo.fanfix.bundles.UiConfig;
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.data.Story;
 import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.fanfix.library.LocalLibrary;
-import be.nikiroo.fanfix.supported.BasicSupport;
-import be.nikiroo.utils.Progress;
 import be.nikiroo.utils.StringUtils;
-import be.nikiroo.utils.serial.SerialUtils;
 
 /**
  * The class that handles the different {@link Story} readers you can use.
- * <p>
- * All the readers should be accessed via {@link BasicReader#getReader()}.
  * 
  * @author niki
  */
-public abstract class BasicReader implements Reader {
-       private static BasicLibrary defaultLibrary = Instance.getInstance().getLibrary();
-       private static ReaderType defaultType = ReaderType.GUI;
-
-       private BasicLibrary lib;
-       private MetaData meta;
-       private Story story;
-       private int chapter;
-
-       /**
-        * Take the default reader type configuration from the config file.
-        */
-       static {
-               String typeString = Instance.getInstance().getConfig().getString(Config.READER_TYPE);
-               if (typeString != null && !typeString.isEmpty()) {
-                       try {
-                               ReaderType type = ReaderType.valueOf(typeString.toUpperCase());
-                               defaultType = type;
-                       } catch (IllegalArgumentException e) {
-                               // Do nothing
-                       }
-               }
-       }
-
-       @Override
-       public synchronized Story getStory(Progress pg) throws IOException {
-               if (story == null) {
-                       story = getLibrary().getStory(meta.getLuid(), pg);
-               }
-
-               return story;
-       }
-
-       @Override
-       public BasicLibrary getLibrary() {
-               if (lib == null) {
-                       lib = defaultLibrary;
-               }
-
-               return lib;
-       }
-
-       @Override
-       public void setLibrary(BasicLibrary lib) {
-               this.lib = lib;
-       }
-
-       @Override
-       public synchronized MetaData getMeta() {
-               return meta;
-       }
-
-       @Override
-       public synchronized void setMeta(MetaData meta) throws IOException {
-               setMeta(meta == null ? null : meta.getLuid()); // must check the library
-       }
-
-       @Override
-       public synchronized void setMeta(String luid) throws IOException {
-               story = null;
-               meta = getLibrary().getInfo(luid);
-
-               if (meta == null) {
-                       throw new IOException("Cannot retrieve story from library: " + luid);
-               }
-       }
-
-       @Override
-       public synchronized void setMeta(URL url, Progress pg) throws IOException {
-               BasicSupport support = BasicSupport.getSupport(url);
-               if (support == null) {
-                       throw new IOException("URL not supported: " + url.toString());
-               }
-
-               story = support.process(pg);
-               if (story == null) {
-                       throw new IOException(
-                                       "Cannot retrieve story from external source: "
-                                                       + url.toString());
-               }
-
-               meta = story.getMeta();
-       }
-
-       @Override
-       public int getChapter() {
-               return chapter;
-       }
-
-       @Override
-       public void setChapter(int chapter) {
-               this.chapter = chapter;
-       }
-
-       /**
-        * Return a new {@link BasicReader} ready for use if one is configured.
-        * <p>
-        * Can return NULL if none are configured.
-        * 
-        * @return a {@link BasicReader}, or NULL if none configured
-        */
-       public static Reader getReader() {
-               try {
-                       if (defaultType != null) {
-                               return (Reader) SerialUtils.createObject(defaultType
-                                               .getTypeName());
-                       }
-               } catch (Exception e) {
-                       Instance.getInstance().getTraceHandler()
-                                       .error(new Exception("Cannot create a reader of type: " + defaultType + " (Not compiled in?)", e));
-               }
-
-               return null;
-       }
-
-       /**
-        * The default {@link Reader.ReaderType} used when calling
-        * {@link BasicReader#getReader()}.
-        * 
-        * @return the default type
-        */
-       public static ReaderType getDefaultReaderType() {
-               return defaultType;
-       }
-
-       /**
-        * The default {@link Reader.ReaderType} used when calling
-        * {@link BasicReader#getReader()}.
-        * 
-        * @param defaultType
-        *            the new default type
-        */
-       public static void setDefaultReaderType(ReaderType defaultType) {
-               BasicReader.defaultType = defaultType;
-       }
-
-       /**
-        * Change the default {@link LocalLibrary} to open with the
-        * {@link BasicReader}s.
-        * 
-        * @param lib
-        *            the new {@link LocalLibrary}
-        */
-       public static void setDefaultLibrary(BasicLibrary lib) {
-               BasicReader.defaultLibrary = lib;
-       }
-
+public abstract class BasicReader {
        /**
         * Return an {@link URL} from this {@link String}, be it a file path or an
         * actual {@link URL}.
@@ -263,7 +110,6 @@ public abstract class BasicReader implements Reader {
         * @throws IOException
         *             in case of I/O error
         */
-       @Override
        public void openExternal(BasicLibrary lib, String luid, boolean sync)
                        throws IOException {
                MetaData meta = lib.getInfo(luid);
similarity index 72%
rename from src/be/nikiroo/fanfix/reader/cli/CliReader.java
rename to src/be/nikiroo/fanfix/reader/CliReader.java
index 235276c8b111d890ff878d5991e2cc9e79c54de8..96ca64405bf7ced80bbb8b9c334119352acc1399 100644 (file)
@@ -1,4 +1,4 @@
-package be.nikiroo.fanfix.reader.cli;
+package be.nikiroo.fanfix.reader;
 
 import java.io.IOException;
 import java.util.List;
@@ -9,10 +9,10 @@ import be.nikiroo.fanfix.data.Chapter;
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.data.Paragraph;
 import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.reader.BasicReader;
 import be.nikiroo.fanfix.searchable.BasicSearchable;
 import be.nikiroo.fanfix.searchable.SearchableTag;
 import be.nikiroo.fanfix.supported.SupportType;
+import be.nikiroo.utils.Image;
 import be.nikiroo.utils.StringUtils;
 
 /**
@@ -22,11 +22,27 @@ import be.nikiroo.utils.StringUtils;
  * 
  * @author niki
  */
-class CliReader extends BasicReader {
-       @Override
-       public void read(boolean sync) throws IOException {
-               MetaData meta = getMeta();
+public class CliReader extends BasicReader {
+       public void listBooks(String source) throws IOException {
+               List<MetaData> stories = Instance.getInstance().getLibrary().getList()
+                               .filter(source, null, null);
 
+               for (MetaData story : stories) {
+                       String author = "";
+                       if (story.getAuthor() != null && !story.getAuthor().isEmpty()) {
+                               author = " (" + story.getAuthor() + ")";
+                       }
+
+                       System.out.println(
+                                       story.getLuid() + ": " + story.getTitle() + author);
+               }
+       }
+
+       public void listChapters(Story story) throws IOException {
+               if (story == null || story.getMeta() == null) {
+                       throw new IOException("No story to read");
+               }
+               MetaData meta = story.getMeta();
                if (meta == null) {
                        throw new IOException("No story to read");
                }
@@ -49,57 +65,50 @@ class CliReader extends BasicReader {
                System.out.println(author);
                System.out.println("");
 
-               // TODO: progress?
-               for (Chapter chap : getStory(null)) {
+               for (Chapter chap : story) {
                        if (chap.getName() != null && !chap.getName().isEmpty()) {
-                               System.out.println(Instance.getInstance().getTrans().getString(StringId.CHAPTER_NAMED, chap.getNumber(),
+                               System.out.println(Instance.getInstance().getTrans().getString(
+                                               StringId.CHAPTER_NAMED, chap.getNumber(),
                                                chap.getName()));
                        } else {
-                               System.out.println(
-                                               Instance.getInstance().getTrans().getString(StringId.CHAPTER_UNNAMED, chap.getNumber()));
+                               System.out.println(Instance.getInstance().getTrans()
+                                               .getString(StringId.CHAPTER_UNNAMED, chap.getNumber()));
                        }
                }
        }
 
-       public void read(int chapter) throws IOException {
-               MetaData meta = getMeta();
-
+       public void printChapter(Story story, int chapter) throws IOException {
+               if (story == null || story.getMeta() == null) {
+                       throw new IOException("No story to read");
+               }
+               MetaData meta = story.getMeta();
                if (meta == null) {
                        throw new IOException("No story to read");
                }
 
-               // TODO: progress?
-               if (chapter > getStory(null).getChapters().size()) {
+               if (chapter <= 0 || chapter > story.getChapters().size()) {
                        System.err.println("Chapter " + chapter + ": no such chapter");
                } else {
-                       Chapter chap = getStory(null).getChapters().get(chapter - 1);
-                       System.out.println("Chapter " + chap.getNumber() + ": "
-                                       + chap.getName());
-
+                       Chapter chap = story.getChapters().get(chapter - 1);
+                       System.out.println(
+                                       "Chapter " + chap.getNumber() + ": " + chap.getName());
+                       System.out.println();
+                       
                        for (Paragraph para : chap) {
-                               System.out.println(para.getContent());
+                               Image img = para.getContentImage();
+                               if (img != null) {
+                                       String sz = StringUtils.formatNumber(img.getSize(), 1);
+                                       System.out.println("[Image: " + sz + "]");
+                               } else {
+                                       System.out.println(
+                                                       para.getContent() == null ? "" : para.getContent());
+                               }
                                System.out.println("");
                        }
                }
        }
 
-       @Override
-       public void browse(String source) throws IOException {
-               List<MetaData> stories = getLibrary().getList().filter(source, null, null);
-
-               for (MetaData story : stories) {
-                       String author = "";
-                       if (story.getAuthor() != null && !story.getAuthor().isEmpty()) {
-                               author = " (" + story.getAuthor() + ")";
-                       }
-
-                       System.out.println(story.getLuid() + ": " + story.getTitle()
-                                       + author);
-               }
-       }
-
-       @Override
-       public void search(boolean sync) throws IOException {
+       public void listSearchables() throws IOException {
                for (SupportType type : SupportType.values()) {
                        if (BasicSearchable.getSearchable(type) != null) {
                                System.out.println(type);
@@ -107,9 +116,8 @@ class CliReader extends BasicReader {
                }
        }
 
-       @Override
-       public void search(SupportType searchOn, String keywords, int page,
-                       int item, boolean sync) throws IOException {
+       public void searchBooksByKeyword(SupportType searchOn, String keywords,
+                       int page, int item) throws IOException {
                BasicSearchable search = BasicSearchable.getSearchable(searchOn);
 
                if (page == 0) {
@@ -118,8 +126,8 @@ class CliReader extends BasicReader {
                        List<MetaData> metas = search.search(keywords, page);
 
                        if (item == 0) {
-                               System.out.println("Page " + page + " of stories for: "
-                                               + keywords);
+                               System.out.println(
+                                               "Page " + page + " of stories for: " + keywords);
                                displayStories(metas);
                        } else {
                                // ! 1-based index !
@@ -133,9 +141,8 @@ class CliReader extends BasicReader {
                }
        }
 
-       @Override
-       public void searchTag(SupportType searchOn, int page, int item,
-                       boolean sync, Integer... tags) throws IOException {
+       public void searchBooksByTag(SupportType searchOn, int page, int item,
+                       Integer... tags) throws IOException {
 
                BasicSearchable search = BasicSearchable.getSearchable(searchOn);
                SearchableTag stag = search.getTag(tags);
@@ -178,8 +185,8 @@ class CliReader extends BasicReader {
                                                        displayTag(subtag);
                                                }
                                        } else {
-                                               System.out.println("Invalid item: only " + count
-                                                               + " items found");
+                                               System.out.println(
+                                                               "Invalid item: only " + count + " items found");
                                        }
                                } else {
                                        if (metas != null) {
diff --git a/src/be/nikiroo/fanfix/reader/Reader.java b/src/be/nikiroo/fanfix/reader/Reader.java
deleted file mode 100644 (file)
index 3ecf247..0000000
+++ /dev/null
@@ -1,267 +0,0 @@
-package be.nikiroo.fanfix.reader;
-
-import java.io.IOException;
-import java.net.URL;
-
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.fanfix.supported.SupportType;
-import be.nikiroo.utils.Progress;
-
-/**
- * A {@link Reader} is a class that will handle {@link Story} reading and
- * browsing.
- * 
- * @author niki
- */
-public interface Reader {
-       /**
-        * A type of {@link BasicReader}.
-        * 
-        * @author niki
-        */
-       public enum ReaderType {
-               /** Simple reader that outputs everything on the console */
-               CLI,
-               /** Reader that starts local programs to handle the stories */
-               GUI,
-               /** A text (UTF-8) reader with menu and text windows */
-               TUI,
-               /** A GUI reader implemented with the Android framework */
-               ANDROID,
-
-               ;
-
-               /**
-                * Return the full class name of a type that implements said
-                * {@link ReaderType}.
-                * 
-                * @return the class name
-                */
-               public String getTypeName() {
-                       String pkg = "be.nikiroo.fanfix.reader.";
-                       switch (this) {
-                       case CLI:
-                               return pkg + "cli.CliReader";
-                       case TUI:
-                               return pkg + "tui.TuiReader";
-                       case GUI:
-                               return pkg + "ui.GuiReader";
-                       case ANDROID:
-                               return pkg + "android.AndroidReader";
-                       }
-
-                       return null;
-               }
-       }
-
-       /**
-        * Return the current target {@link MetaData}.
-        * 
-        * @return the meta
-        */
-       public MetaData getMeta();
-
-       /**
-        * Return the current {@link Story} as described by the current
-        * {@link MetaData}.
-        * 
-        * @param pg
-        *            the optional progress
-        * 
-        * @return the {@link Story}
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        * 
-        */
-       public Story getStory(Progress pg) throws IOException;
-
-       /**
-        * The {@link BasicLibrary} to load the stories from (by default, takes the
-        * default {@link BasicLibrary}).
-        * 
-        * @return the {@link BasicLibrary}
-        */
-       public BasicLibrary getLibrary();
-
-       /**
-        * Change the {@link BasicLibrary} that will be managed by this
-        * {@link BasicReader}.
-        * 
-        * @param lib
-        *            the new {@link BasicLibrary}
-        */
-       public void setLibrary(BasicLibrary lib);
-
-       /**
-        * Set a {@link Story} from the current {@link BasicLibrary} into the
-        * {@link Reader}.
-        * 
-        * @param luid
-        *            the {@link Story} ID
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public void setMeta(String luid) throws IOException;
-
-       /**
-        * Set a {@link Story} from the current {@link BasicLibrary} into the
-        * {@link Reader}.
-        * 
-        * @param meta
-        *            the meta
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public void setMeta(MetaData meta) throws IOException;
-
-       /**
-        * Set an external {@link Story} into this {@link Reader}.
-        * 
-        * @param source
-        *            the {@link Story} {@link URL}
-        * @param pg
-        *            the optional progress reporter
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public void setMeta(URL source, Progress pg) throws IOException;
-
-       /**
-        * Start the {@link Story} Reading.
-        * 
-        * @param sync
-        *            execute the process synchronously (wait until it is terminated
-        *            before returning)
-        * 
-        * @throws IOException
-        *             in case of I/O error or if the {@link Story} was not
-        *             previously set
-        */
-       public void read(boolean sync) throws IOException;
-
-       /**
-        * The selected chapter to start reading at (starting at 1, 0 = description,
-        * -1 = none).
-        * 
-        * @return the chapter, or -1 for "no chapter"
-        */
-       public int getChapter();
-
-       /**
-        * The selected chapter to start reading at (starting at 1, 0 = description,
-        * -1 = none).
-        * 
-        * @param chapter
-        *            the chapter, or -1 for "no chapter"
-        */
-       public void setChapter(int chapter);
-
-       /**
-        * Start the reader in browse mode for the given source (or pass NULL for
-        * all sources).
-        * <p>
-        * Note that this must be a <b>synchronous</b> action.
-        * 
-        * @param source
-        *            the type of {@link Story} to take into account, or NULL for
-        *            all
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public void browse(String source) throws IOException;
-
-       /**
-        * Display all supports that allow search operations.
-        * 
-        * @param sync
-        *            execute the process synchronously (wait until it is terminated
-        *            before returning)
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public void search(boolean sync) throws IOException;
-
-       /**
-        * Search for the given terms and find stories that correspond if possible.
-        * 
-        * @param searchOn
-        *            the website to search on
-        * @param keywords
-        *            the words to search for (cannot be NULL)
-        * @param page
-        *            the page of results to show (0 = request the maximum number of
-        *            pages, pages start at 1)
-        * @param item
-        *            the item to select (0 = do not select a specific item but show
-        *            all the page, items start at 1)
-        * @param sync
-        *            execute the process synchronously (wait until it is terminated
-        *            before returning)
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public void search(SupportType searchOn, String keywords, int page,
-                       int item, boolean sync) throws IOException;
-
-       /**
-        * Search based upon a hierarchy of tags, or search for (sub)tags.
-        * <p>
-        * We use the tags <tt>DisplayName</tt>.
-        * <p>
-        * If no tag is given, the main tags will be shown.
-        * <p>
-        * If a non-leaf tag is given, the subtags will be shown.
-        * <p>
-        * If a leaf tag is given (or a full hierarchy ending with a leaf tag),
-        * stories will be shown.
-        * <p>
-        * You can select the story you want with the <tt>item</tt> number.
-        * 
-        * @param searchOn
-        *            the website to search on
-        * @param page
-        *            the page of results to show (0 = request the maximum number of
-        *            pages, pages <b>start at 1</b>)
-        * @param item
-        *            the item to select (0 = do not select a specific item but show
-        *            all the page, items <b>start at 1</b>)
-        * @param sync
-        *            execute the process synchronously (wait until it is terminated
-        *            before returning)
-        * @param tags
-        *            the tags indices to search for (this is a tag
-        *            <b>hierarchy</b>, <b>NOT</b> a multiple tags choice)
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public void searchTag(SupportType searchOn, int page, int item,
-                       boolean sync, Integer... tags) throws IOException;
-
-       /**
-        * Open the {@link Story} with an external reader (the program should be
-        * passed the main file associated with this {@link Story}).
-        * 
-        * @param lib
-        *            the {@link BasicLibrary} to select the {@link Story} from
-        * @param luid
-        *            the {@link Story} LUID
-        * @param sync
-        *            execute the process synchronously (wait until it is terminated
-        *            before returning)
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public void openExternal(BasicLibrary lib, String luid, boolean sync)
-                       throws IOException;
-}
diff --git a/src/be/nikiroo/fanfix/reader/tui/ConfigItem.java b/src/be/nikiroo/fanfix/reader/tui/ConfigItem.java
deleted file mode 100644 (file)
index 43e9fe8..0000000
+++ /dev/null
@@ -1,362 +0,0 @@
-package be.nikiroo.fanfix.reader.tui;
-
-import java.util.List;
-
-import jexer.TAction;
-import jexer.TButton;
-import jexer.TLabel;
-import jexer.TPanel;
-import jexer.TWidget;
-import be.nikiroo.utils.resources.Bundle;
-import be.nikiroo.utils.resources.MetaInfo;
-import be.nikiroo.utils.ui.ConfigItemBase;
-
-/**
- * A graphical item that reflect a configuration option from the given
- * {@link Bundle}.
- * <p>
- * This graphical item can be edited, and the result will be saved back into the
- * linked {@link MetaInfo}; you still have to save the {@link MetaInfo} should
- * you wish to, of course.
- * 
- * @author niki
- * 
- * @param <E>
- *            the type of {@link Bundle} to edit
- */
-public abstract class ConfigItem<E extends Enum<E>> extends TWidget {
-       /** The code base */
-       private final ConfigItemBase<TWidget, E> base;
-
-       /**
-        * Prepare a new {@link ConfigItem} instance, linked to the given
-        * {@link MetaInfo}.
-        * 
-        * @param parent
-        *            the parent widget
-        * @param info
-        *            the info
-        * @param autoDirtyHandling
-        *            TRUE to automatically manage the setDirty/Save operations,
-        *            FALSE if you want to do it yourself via
-        *            {@link ConfigItem#setDirtyItem(int)}
-        */
-       protected ConfigItem(TWidget parent, MetaInfo<E> info,
-                       boolean autoDirtyHandling) {
-               super(parent);
-
-               base = new ConfigItemBase<TWidget, E>(info, autoDirtyHandling) {
-                       @Override
-                       protected TWidget createEmptyField(int item) {
-                               return ConfigItem.this.createEmptyField(item);
-                       }
-
-                       @Override
-                       protected Object getFromInfo(int item) {
-                               return ConfigItem.this.getFromInfo(item);
-                       }
-
-                       @Override
-                       protected void setToInfo(Object value, int item) {
-                               ConfigItem.this.setToInfo(value, item);
-                       }
-
-                       @Override
-                       protected Object getFromField(int item) {
-                               return ConfigItem.this.getFromField(item);
-                       }
-
-                       @Override
-                       protected void setToField(Object value, int item) {
-                               ConfigItem.this.setToField(value, item);
-                       }
-
-                       @Override
-                       public TWidget createField(int item) {
-                               TWidget field = super.createField(item);
-
-                               // TODO: size?
-
-                               return field;
-                       }
-
-                       @Override
-                       public List<TWidget> reload() {
-                               List<TWidget> removed = base.reload();
-                               if (!removed.isEmpty()) {
-                                       for (TWidget c : removed) {
-                                               removeChild(c);
-                                       }
-                               }
-
-                               return removed;
-                       }
-               };
-       }
-
-       /**
-        * Create a new {@link ConfigItem} for the given {@link MetaInfo}.
-        * 
-        * @param nhgap
-        *            negative horisontal gap in pixel to use for the label, i.e.,
-        *            the step lock sized labels will start smaller by that amount
-        *            (the use case would be to align controls that start at a
-        *            different horisontal position)
-        */
-       public void init(int nhgap) {
-               if (getInfo().isArray()) {
-                       // TODO: width
-                       int size = getInfo().getListSize(false);
-                       final TPanel pane = new TPanel(this, 0, 0, 20, size + 2);
-                       final TWidget label = label(0, 0, nhgap);
-                       label.setParent(pane, false);
-                       setHeight(pane.getHeight());
-
-                       for (int i = 0; i < size; i++) {
-                               // TODO: minusPanel
-                               TWidget field = base.addItem(i, null);
-                               field.setParent(pane, false);
-                               field.setX(label.getWidth() + 1);
-                               field.setY(i);
-                       }
-
-                       // x, y
-                       final TButton add = new TButton(pane, "+", label.getWidth() + 1,
-                                       size + 1, null);
-                       TAction action = new TAction() {
-                               @Override
-                               public void DO() {
-                                       TWidget field = base.addItem(base.getFieldsSize(), null);
-                                       field.setParent(pane, false);
-                                       field.setX(label.getWidth() + 1);
-                                       field.setY(add.getY());
-                                       add.setY(add.getY() + 1);
-                               }
-                       };
-                       add.setAction(action);
-               } else {
-                       final TWidget label = label(0, 0, nhgap);
-
-                       TWidget field = base.createField(-1);
-                       field.setX(label.getWidth() + 1);
-                       field.setWidth(10); // TODO
-
-                       // TODO
-                       setWidth(30);
-                       setHeight(1);
-               }
-       }
-
-       /** The {@link MetaInfo} linked to the field. */
-       public MetaInfo<E> getInfo() {
-               return base.getInfo();
-       }
-
-       /**
-        * Retrieve the associated graphical component that was created with
-        * {@link ConfigItemBase#createEmptyField(int)}.
-        * 
-        * @param item
-        *            the item number to get for an array of values, or -1 to get
-        *            the whole value (has no effect if {@link MetaInfo#isArray()}
-        *            is FALSE)
-        * 
-        * @return the graphical component
-        */
-       protected TWidget getField(int item) {
-               return base.getField(item);
-       }
-
-       /**
-        * Manually specify that the given item is "dirty" and thus should be saved
-        * when asked.
-        * <p>
-        * Has no effect if the class is using automatic dirty handling (see
-        * {@link ConfigItemBase#ConfigItem(MetaInfo, boolean)}).
-        * 
-        * @param item
-        *            the item number to get for an array of values, or -1 to get
-        *            the whole value (has no effect if {@link MetaInfo#isArray()}
-        *            is FALSE)
-        */
-       protected void setDirtyItem(int item) {
-               base.setDirtyItem(item);
-       }
-
-       /**
-        * Check if the value changed since the last load/save into the linked
-        * {@link MetaInfo}.
-        * <p>
-        * Note that we consider NULL and an Empty {@link String} to be equals.
-        * 
-        * @param value
-        *            the value to test
-        * @param item
-        *            the item number to get for an array of values, or -1 to get
-        *            the whole value (has no effect if {@link MetaInfo#isArray()}
-        *            is FALSE)
-        * 
-        * @return TRUE if it has
-        */
-       protected boolean hasValueChanged(Object value, int item) {
-               return base.hasValueChanged(value, item);
-       }
-
-       /**
-        * Create an empty graphical component to be used later by
-        * {@link ConfigItem#createField(int)}.
-        * <p>
-        * Note that {@link ConfigItem#reload(int)} will be called after it was
-        * created by {@link ConfigItem#createField(int)}.
-        * 
-        * @param item
-        *            the item number to get for an array of values, or -1 to get
-        *            the whole value (has no effect if {@link MetaInfo#isArray()}
-        *            is FALSE)
-        * 
-        * @return the graphical component
-        */
-       abstract protected TWidget createEmptyField(int item);
-
-       /**
-        * Get the information from the {@link MetaInfo} in the subclass preferred
-        * format.
-        * 
-        * @param item
-        *            the item number to get for an array of values, or -1 to get
-        *            the whole value (has no effect if {@link MetaInfo#isArray()}
-        *            is FALSE)
-        * 
-        * @return the information in the subclass preferred format
-        */
-       abstract protected Object getFromInfo(int item);
-
-       /**
-        * Set the value to the {@link MetaInfo}.
-        * 
-        * @param value
-        *            the value in the subclass preferred format
-        * @param item
-        *            the item number to get for an array of values, or -1 to get
-        *            the whole value (has no effect if {@link MetaInfo#isArray()}
-        *            is FALSE)
-        */
-       abstract protected void setToInfo(Object value, int item);
-
-       /**
-        * The value present in the given item's related field in the subclass
-        * preferred format.
-        * 
-        * @param item
-        *            the item number to get for an array of values, or -1 to get
-        *            the whole value (has no effect if {@link MetaInfo#isArray()}
-        *            is FALSE)
-        * 
-        * @return the value present in the given item's related field in the
-        *         subclass preferred format
-        */
-       abstract protected Object getFromField(int item);
-
-       /**
-        * Set the value (in the subclass preferred format) into the field.
-        * 
-        * @param value
-        *            the value in the subclass preferred format
-        * @param item
-        *            the item number to get for an array of values, or -1 to get
-        *            the whole value (has no effect if {@link MetaInfo#isArray()}
-        *            is FALSE)
-        */
-       abstract protected void setToField(Object value, int item);
-
-       /**
-        * Create a label which width is constrained in lock steps.
-        * 
-        * @param x
-        *            the X position of the label
-        * @param y
-        *            the Y position of the label
-        * @param nhgap
-        *            negative horisontal gap in pixel to use for the label, i.e.,
-        *            the step lock sized labels will start smaller by that amount
-        *            (the use case would be to align controls that start at a
-        *            different horisontal position)
-        * 
-        * @return the label
-        */
-       protected TWidget label(int x, int y, int nhgap) {
-               // TODO: see Swing version for lock-step sizes
-               // TODO: see Swing version for help info-buttons
-
-               String lbl = getInfo().getName();
-               return new TLabel(this, lbl, x, y);
-       }
-
-       /**
-        * Create a new {@link ConfigItem} for the given {@link MetaInfo}.
-        * 
-        * @param <E>
-        *            the type of {@link Bundle} to edit
-        * 
-        * @param x
-        *            the X position of the item
-        * @param y
-        *            the Y position of the item
-        * @param parent
-        *            the parent widget to use for this one
-        * @param info
-        *            the {@link MetaInfo}
-        * @param nhgap
-        *            negative horisontal gap in pixel to use for the label, i.e.,
-        *            the step lock sized labels will start smaller by that amount
-        *            (the use case would be to align controls that start at a
-        *            different horisontal position)
-        * 
-        * @return the new {@link ConfigItem}
-        */
-       static public <E extends Enum<E>> ConfigItem<E> createItem(TWidget parent,
-                       int x, int y, MetaInfo<E> info, int nhgap) {
-
-               ConfigItem<E> configItem;
-               switch (info.getFormat()) {
-               // TODO
-               // case BOOLEAN:
-               // configItem = new ConfigItemBoolean<E>(info);
-               // break;
-               // case COLOR:
-               // configItem = new ConfigItemColor<E>(info);
-               // break;
-               // case FILE:
-               // configItem = new ConfigItemBrowse<E>(info, false);
-               // break;
-               // case DIRECTORY:
-               // configItem = new ConfigItemBrowse<E>(info, true);
-               // break;
-               // case COMBO_LIST:
-               // configItem = new ConfigItemCombobox<E>(info, true);
-               // break;
-               // case FIXED_LIST:
-               // configItem = new ConfigItemCombobox<E>(info, false);
-               // break;
-               // case INT:
-               // configItem = new ConfigItemInteger<E>(info);
-               // break;
-               // case PASSWORD:
-               // configItem = new ConfigItemPassword<E>(info);
-               // break;
-               // case LOCALE:
-               // configItem = new ConfigItemLocale<E>(info);
-               // break;
-               // case STRING:
-               default:
-                       configItem = new ConfigItemString<E>(parent, info);
-                       break;
-               }
-
-               configItem.init(nhgap);
-               configItem.setX(x);
-               configItem.setY(y);
-
-               return configItem;
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/tui/ConfigItemString.java b/src/be/nikiroo/fanfix/reader/tui/ConfigItemString.java
deleted file mode 100644 (file)
index b1057e9..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-package be.nikiroo.fanfix.reader.tui;
-
-import jexer.TField;
-import jexer.TWidget;
-import be.nikiroo.utils.resources.MetaInfo;
-
-class ConfigItemString<E extends Enum<E>> extends ConfigItem<E> {
-       /**
-        * Create a new {@link ConfigItemString} for the given {@link MetaInfo}.
-        * 
-        * @param info
-        *            the {@link MetaInfo}
-        */
-       public ConfigItemString(TWidget parent, MetaInfo<E> info) {
-               super(parent, info, true);
-       }
-
-       @Override
-       protected Object getFromField(int item) {
-               TField field = (TField) getField(item);
-               if (field != null) {
-                       return field.getText();
-               }
-
-               return null;
-       }
-
-       @Override
-       protected Object getFromInfo(int item) {
-               return getInfo().getString(item, false);
-       }
-
-       @Override
-       protected void setToField(Object value, int item) {
-               TField field = (TField) getField(item);
-               if (field != null) {
-                       field.setText(value == null ? "" : value.toString());
-               }
-       }
-
-       @Override
-       protected void setToInfo(Object value, int item) {
-               getInfo().setString((String) value, item);
-       }
-
-       @Override
-       protected TWidget createEmptyField(int item) {
-               return new TField(this, 0, 0, 1, false);
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/tui/TOptionWindow.java b/src/be/nikiroo/fanfix/reader/tui/TOptionWindow.java
deleted file mode 100644 (file)
index 4bb67de..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-package be.nikiroo.fanfix.reader.tui;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import jexer.TAction;
-import jexer.TApplication;
-import jexer.TPanel;
-import jexer.TWidget;
-import jexer.event.TCommandEvent;
-import be.nikiroo.utils.StringUtils;
-import be.nikiroo.utils.resources.Bundle;
-import be.nikiroo.utils.resources.MetaInfo;
-
-public class TOptionWindow<E extends Enum<E>> extends TSimpleScrollableWindow {
-       private List<MetaInfo<E>> items;
-
-       public TOptionWindow(TApplication parent, Class<E> type,
-                       final Bundle<E> bundle, String title) {
-               super(parent, title, 0, 0, CENTERED | RESIZABLE);
-
-               getMainPane().addLabel(title, 0, 0);
-
-               items = new ArrayList<MetaInfo<E>>();
-               List<MetaInfo<E>> groupedItems = MetaInfo.getItems(type, bundle);
-               int y = 2;
-               for (MetaInfo<E> item : groupedItems) {
-                       // will populate this.items
-                       y += addItem(getMainPane(), 5, y, item, 0).getHeight();
-               }
-
-               y++;
-
-               setRealHeight(y + 1);
-
-               getMainPane().addButton("Reset", 25, y, new TAction() {
-                       @Override
-                       public void DO() {
-                               for (MetaInfo<E> item : items) {
-                                       item.reload();
-                               }
-                       }
-               });
-
-               getMainPane().addButton("Default", 15, y, new TAction() {
-                       @Override
-                       public void DO() {
-                               Object snap = bundle.takeSnapshot();
-                               bundle.reload(true);
-                               for (MetaInfo<E> item : items) {
-                                       item.reload();
-                               }
-                               bundle.reload(false);
-                               bundle.restoreSnapshot(snap);
-                       }
-               });
-
-               getMainPane().addButton("Save", 1, y, new TAction() {
-                       @Override
-                       public void DO() {
-                               for (MetaInfo<E> item : items) {
-                                       item.save(true);
-                               }
-
-                               try {
-                                       bundle.updateFile();
-                               } catch (IOException e1) {
-                                       e1.printStackTrace();
-                               }
-                       }
-               });
-       }
-
-       private TWidget addItem(TWidget parent, int x, int y, MetaInfo<E> item,
-                       int nhgap) {
-               if (item.isGroup()) {
-                       // TODO: width
-                       int w = 80 - x;
-
-                       String name = item.getName();
-                       String info = item.getDescription();
-                       info = StringUtils.justifyTexts(info, w - 3); // -3 for borders
-
-                       final TPanel pane = new TPanel(parent, x, y, w, 1);
-                       pane.addLabel(name, 0, 0);
-
-                       int h = 0;
-                       if (!info.isEmpty()) {
-                               h += info.split("\n").length + 1; // +1 for scroll
-                               pane.addText(info + "\n", 0, 1, w, h);
-                       }
-
-                       // +1 for the title
-                       h++;
-
-                       int paneY = h; // for the info desc
-                       for (MetaInfo<E> subitem : item) {
-                               paneY += addItem(pane, 4, paneY, subitem, nhgap + 11)
-                                               .getHeight();
-                       }
-
-                       pane.setHeight(paneY);
-                       return pane;
-               }
-
-               items.add(item);
-               return ConfigItem.createItem(parent, x, y, item, nhgap);
-       }
-
-       @Override
-       public void onCommand(TCommandEvent command) {
-               if (command.getCmd().equals(TuiReaderApplication.CMD_EXIT)) {
-                       TuiReaderApplication.close(this);
-               } else {
-                       // Handle our own event if needed here
-                       super.onCommand(command);
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/tui/TSimpleScrollableWindow.java b/src/be/nikiroo/fanfix/reader/tui/TSimpleScrollableWindow.java
deleted file mode 100644 (file)
index 7b8f439..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-package be.nikiroo.fanfix.reader.tui;
-
-import jexer.TApplication;
-import jexer.THScroller;
-import jexer.TPanel;
-import jexer.TScrollableWindow;
-import jexer.TVScroller;
-import jexer.TWidget;
-import jexer.event.TMouseEvent;
-import jexer.event.TResizeEvent;
-
-public class TSimpleScrollableWindow extends TScrollableWindow {
-       protected TPanel mainPane;
-       private int prevHorizontal = -1;
-       private int prevVertical = -1;
-
-       public TSimpleScrollableWindow(TApplication application, String title,
-                       int width, int height) {
-               this(application, title, width, height, 0, 0, 0);
-       }
-
-       public TSimpleScrollableWindow(TApplication application, String title,
-                       int width, int height, int flags) {
-               this(application, title, width, height, flags, 0, 0);
-       }
-
-       // 0 = none (so, no scrollbar)
-       public TSimpleScrollableWindow(TApplication application, String title,
-                       int width, int height, int flags, int realWidth, int realHeight) {
-               super(application, title, width, height, flags);
-
-               mainPane = new TPanel(this, 0, 0, 1, 1) {
-                       @Override
-                       public void draw() {
-                               for (TWidget children : mainPane.getChildren()) {
-                                       int y = children.getY() + children.getHeight();
-                                       int x = children.getX() + children.getWidth();
-                                       boolean visible = (y > getVerticalValue())
-                                                       && (x > getHorizontalValue());
-                                       children.setVisible(visible);
-                               }
-                               super.draw();
-                       }
-               };
-
-               mainPane.setWidth(getWidth());
-               mainPane.setHeight(getHeight());
-
-               setRealWidth(realWidth);
-               setRealHeight(realHeight);
-               placeScrollbars();
-       }
-
-       /**
-        * The main pane on which you can add other widgets for this scrollable
-        * window.
-        * 
-        * @return the main pane
-        */
-       public TPanel getMainPane() {
-               return mainPane;
-       }
-
-       public void setRealWidth(int realWidth) {
-               if (realWidth <= 0) {
-                       if (hScroller != null) {
-                               hScroller.remove();
-                       }
-               } else {
-                       if (hScroller == null) {
-                               // size/position will be fixed by placeScrollbars()
-                               hScroller = new THScroller(this, 0, 0, 10);
-                       }
-                       setRightValue(realWidth);
-               }
-
-               reflowData();
-       }
-
-       public void setRealHeight(int realHeight) {
-               if (realHeight <= 0) {
-                       if (vScroller != null) {
-                               vScroller.remove();
-                       }
-               } else {
-                       if (vScroller == null) {
-                               // size/position will be fixed by placeScrollbars()
-                               vScroller = new TVScroller(this, 0, 0, 10);
-                       }
-                       setBottomValue(realHeight);
-               }
-
-               reflowData();
-       }
-
-       @Override
-       public void onResize(TResizeEvent event) {
-               super.onResize(event);
-               mainPane.setWidth(getWidth());
-               mainPane.setHeight(getHeight());
-               mainPane.onResize(event);
-       }
-
-       @Override
-       public void reflowData() {
-               super.reflowData();
-               reflowData(getHorizontalValue(), getVerticalValue());
-       }
-
-       protected void reflowData(int totalX, int totalY) {
-               super.reflowData();
-               mainPane.setX(-totalX);
-               mainPane.setY(-totalY);
-       }
-
-       @Override
-       public void onMouseUp(TMouseEvent mouse) {
-               super.onMouseUp(mouse);
-
-               // TODO: why? this should already be done by the scrollers
-               // it could also mean we do it twice if, somehow, it sometime works...
-               int mrx = mouse.getX();
-               int mry = mouse.getY();
-
-               int mx = mouse.getAbsoluteX();
-               int my = mouse.getAbsoluteY();
-
-               if (vScroller != null) {
-                       mouse.setX(mx - vScroller.getAbsoluteX());
-                       mouse.setY(my - vScroller.getAbsoluteY());
-                       vScroller.onMouseUp(mouse);
-               }
-               if (hScroller != null) {
-                       mouse.setX(mx - hScroller.getAbsoluteX());
-                       mouse.setY(my - hScroller.getAbsoluteY());
-                       hScroller.onMouseUp(mouse);
-               }
-
-               mouse.setX(mrx);
-               mouse.setY(mry);
-               //
-
-               if (prevHorizontal != getHorizontalValue()
-                               || prevVertical != getVerticalValue()) {
-                       prevHorizontal = getHorizontalValue();
-                       prevVertical = getVerticalValue();
-                       reflowData();
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReader.java b/src/be/nikiroo/fanfix/reader/tui/TuiReader.java
deleted file mode 100644 (file)
index ef5e71b..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-package be.nikiroo.fanfix.reader.tui;
-
-import java.io.IOException;
-
-import jexer.TApplication;
-import jexer.TApplication.BackendType;
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.reader.BasicReader;
-import be.nikiroo.fanfix.reader.Reader;
-import be.nikiroo.fanfix.supported.SupportType;
-
-/**
- * This {@link Reader}is based upon the TUI widget library 'jexer'
- * (https://github.com/klamonte/jexer/) and offer, as its name suggest, a Text
- * User Interface.
- * <p>
- * It is expected to be on par with the GUI version.
- * 
- * @author niki
- */
-class TuiReader extends BasicReader {
-       /**
-        * Will detect the backend to use.
-        * <p>
-        * Swing is the default backend on Windows and MacOS while evreything else
-        * will use XTERM unless explicitly overridden by <tt>jexer.Swing</tt> =
-        * <tt>true</tt> or <tt>false</tt>.
-        * 
-        * @return the backend to use
-        */
-       private static BackendType guessBackendType() {
-               // TODO: allow a config option to force one or the other?
-               TApplication.BackendType backendType = TApplication.BackendType.XTERM;
-               if (System.getProperty("os.name").startsWith("Windows")) {
-                       backendType = TApplication.BackendType.SWING;
-               }
-
-               if (System.getProperty("os.name").startsWith("Mac")) {
-                       backendType = TApplication.BackendType.SWING;
-               }
-
-               if (System.getProperty("jexer.Swing") != null) {
-                       if (System.getProperty("jexer.Swing", "false").equals("true")) {
-                               backendType = TApplication.BackendType.SWING;
-                       } else {
-                               backendType = TApplication.BackendType.XTERM;
-                       }
-               }
-
-               return backendType;
-       }
-
-       @Override
-       public void read(boolean sync) throws IOException {
-               // TODO
-               if (!sync) {
-                       // How could you do a not-sync in TUI mode?
-                       throw new java.lang.IllegalStateException(
-                                       "Async mode not implemented yet.");
-               }
-
-               try {
-                       TuiReaderApplication app = new TuiReaderApplication(this,
-                                       guessBackendType());
-                       app.run();
-               } catch (Exception e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-       }
-
-       @Override
-       public void browse(String source) {
-               try {
-                       TuiReaderApplication app = new TuiReaderApplication(this, source,
-                                       guessBackendType());
-                       app.run();
-               } catch (Exception e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-       }
-
-       @Override
-       public void search(boolean sync) throws IOException {
-               // TODO
-               if (sync) {
-                       throw new java.lang.IllegalStateException("Not implemented yet.");
-               }
-       }
-
-       @Override
-       public void search(SupportType searchOn, String keywords, int page,
-                       int item, boolean sync) {
-               // TODO
-               if (sync) {
-                       throw new java.lang.IllegalStateException("Not implemented yet.");
-               }
-       }
-
-       @Override
-       public void searchTag(SupportType searchOn, int page, int item,
-                       boolean sync, Integer... tags) {
-               // TODO
-               if (sync) {
-                       throw new java.lang.IllegalStateException("Not implemented yet.");
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReaderApplication.java b/src/be/nikiroo/fanfix/reader/tui/TuiReaderApplication.java
deleted file mode 100644 (file)
index f85f12f..0000000
+++ /dev/null
@@ -1,467 +0,0 @@
-package be.nikiroo.fanfix.reader.tui;
-
-import java.awt.Toolkit;
-import java.awt.datatransfer.DataFlavor;
-import java.io.IOException;
-import java.net.URL;
-import java.net.UnknownHostException;
-
-import jexer.TApplication;
-import jexer.TCommand;
-import jexer.TKeypress;
-import jexer.TMessageBox;
-import jexer.TMessageBox.Result;
-import jexer.TMessageBox.Type;
-import jexer.TStatusBar;
-import jexer.TWidget;
-import jexer.TWindow;
-import jexer.event.TCommandEvent;
-import jexer.event.TMenuEvent;
-import jexer.menu.TMenu;
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.fanfix.reader.BasicReader;
-import be.nikiroo.fanfix.reader.Reader;
-import be.nikiroo.fanfix.reader.tui.TuiReaderMainWindow.Mode;
-import be.nikiroo.fanfix.supported.SupportType;
-import be.nikiroo.utils.Progress;
-
-/**
- * Manages the TUI general mode and links and manages the {@link TWindow}s.
- * <p>
- * It will also enclose a {@link Reader} and simply handle the reading part
- * differently (it will create the required sub-windows and display them).
- * 
- * @author niki
- */
-class TuiReaderApplication extends TApplication implements Reader {
-       public static final int MENU_FILE_OPEN = 1025;
-       public static final int MENU_FILE_IMPORT_URL = 1026;
-       public static final int MENU_FILE_IMPORT_FILE = 1027;
-       public static final int MENU_FILE_EXPORT = 1028;
-       public static final int MENU_FILE_DELETE = 1029;
-       public static final int MENU_FILE_LIBRARY = 1030;
-       public static final int MENU_FILE_EXIT = 1031;
-       //
-       public static final int MENU_OPT_FANFIX = 1032;
-       public static final int MENU_OPT_TUI = 1033;
-       
-
-       public static final TCommand CMD_EXIT = new TCommand(MENU_FILE_EXIT) {
-       };
-
-       private Reader reader;
-       private TuiReaderMainWindow main;
-
-       // start reading if meta present
-       public TuiReaderApplication(Reader reader, BackendType backend)
-                       throws Exception {
-               super(backend);
-               init(reader);
-
-               if (getMeta() != null) {
-                       read(false);
-               }
-       }
-
-       public TuiReaderApplication(Reader reader, String source,
-                       TApplication.BackendType backend) throws Exception {
-               super(backend);
-               init(reader);
-               
-               showMain();
-               main.setMode(Mode.SOURCE, source);
-       }
-
-       @Override
-       public void read(boolean sync) throws IOException {
-               read(getStory(null), sync);
-       }
-
-       @Override
-       public MetaData getMeta() {
-               return reader.getMeta();
-       }
-
-       @Override
-       public Story getStory(Progress pg) throws IOException {
-               return reader.getStory(pg);
-       }
-
-       @Override
-       public BasicLibrary getLibrary() {
-               return reader.getLibrary();
-       }
-
-       @Override
-       public void setLibrary(BasicLibrary lib) {
-               reader.setLibrary(lib);
-       }
-
-       @Override
-       public void setMeta(MetaData meta) throws IOException {
-               reader.setMeta(meta);
-       }
-
-       @Override
-       public void setMeta(String luid) throws IOException {
-               reader.setMeta(luid);
-       }
-
-       @Override
-       public void setMeta(URL source, Progress pg) throws IOException {
-               reader.setMeta(source, pg);
-       }
-
-       @Override
-       public void browse(String source) {
-               try {
-                       reader.browse(source);
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-       }
-
-       @Override
-       public int getChapter() {
-               return reader.getChapter();
-       }
-
-       @Override
-       public void setChapter(int chapter) {
-               reader.setChapter(chapter);
-       }
-
-       @Override
-       public void search(boolean sync) throws IOException {
-               reader.search(sync);
-       }
-
-       @Override
-       public void search(SupportType searchOn, String keywords, int page,
-                       int item, boolean sync) throws IOException {
-               reader.search(searchOn, keywords, page, item, sync);
-       }
-
-       @Override
-       public void searchTag(SupportType searchOn, int page, int item,
-                       boolean sync, Integer... tags) throws IOException {
-               reader.searchTag(searchOn, page, item, sync, tags);
-       }
-
-       /**
-        * Open the given {@link Story} for reading. This may or may not start an
-        * external program to read said {@link Story}.
-        * 
-        * @param story
-        *            the {@link Story} to read
-        * @param sync
-        *            execute the process synchronously (wait until it is terminated
-        *            before returning)
-        * 
-        * @throws IOException
-        *             in case of I/O errors
-        */
-       public void read(Story story, boolean sync) throws IOException {
-               if (story == null) {
-                       throw new IOException("No story to read");
-               }
-
-               // TODO: open in editor + external option
-               if (!story.getMeta().isImageDocument()) {
-                       TWindow window = new TuiReaderStoryWindow(this, story, getChapter());
-                       window.maximize();
-               } else {
-                       try {
-                               openExternal(getLibrary(), story.getMeta().getLuid(), sync);
-                       } catch (IOException e) {
-                               messageBox("Error when trying to open the story",
-                                               e.getMessage(), TMessageBox.Type.OK);
-                       }
-               }
-       }
-
-       /**
-        * Set the default status bar when this window appear.
-        * <p>
-        * Some shortcuts are always visible, and will be put here.
-        * <p>
-        * Note that shortcuts placed this way on menu won't work unless the menu
-        * also implement them.
-        * 
-        * @param window
-        *            the new window or menu on screen
-        * @param description
-        *            the description to show on the status ba
-        */
-       public TStatusBar setStatusBar(TWindow window, String description) {
-               TStatusBar statusBar = window.newStatusBar(description);
-               statusBar.addShortcutKeypress(TKeypress.kbF10, CMD_EXIT, "Exit");
-               return statusBar;
-
-       }
-
-       private void showMain() {
-               if (main != null && main.isVisible()) {
-                       main.activate();
-               } else {
-                       if (main != null) {
-                               main.close();
-                       }
-                       main = new TuiReaderMainWindow(this);
-                       main.maximize();
-               }
-       }
-
-       private void init(Reader reader) {
-               this.reader = reader;
-
-               // TODO: traces/errors?
-               Instance.getInstance().setTraceHandler(null);
-
-               // Add the menus TODO: i18n
-               TMenu fileMenu = addMenu("&File");
-               fileMenu.addItem(MENU_FILE_OPEN, "&Open...");
-               fileMenu.addItem(MENU_FILE_EXPORT, "&Save as...");
-               fileMenu.addItem(MENU_FILE_DELETE, "&Delete...");
-               // TODO: Move to...
-               fileMenu.addSeparator();
-               fileMenu.addItem(MENU_FILE_IMPORT_URL, "Import &URL...");
-               fileMenu.addItem(MENU_FILE_IMPORT_FILE, "Import &file...");
-               fileMenu.addSeparator();
-               fileMenu.addItem(MENU_FILE_LIBRARY, "Lib&rary");
-               fileMenu.addSeparator();
-               fileMenu.addItem(MENU_FILE_EXIT, "E&xit");
-               
-               TMenu OptionsMenu = addMenu("&Options");
-               OptionsMenu.addItem(MENU_OPT_FANFIX, "&Fanfix Configuration");
-               OptionsMenu.addItem(MENU_OPT_TUI, "&UI Configuration");
-
-               setStatusBar(fileMenu, "File-management "
-                               + "commands (Open, Save, Print, etc.)");
-               
-
-               // TODO: Edit: re-download, delete
-
-               //
-
-               addWindowMenu();
-
-               getBackend().setTitle("Fanfix");
-       }
-
-       @Override
-       protected boolean onCommand(TCommandEvent command) {
-               if (command.getCmd().equals(TuiReaderMainWindow.CMD_SEARCH)) {
-                       messageBox("title", "caption");
-                       return true;
-               }
-               return super.onCommand(command);
-       }
-
-       @Override
-       protected boolean onMenu(TMenuEvent menu) {
-               // TODO: i18n
-               switch (menu.getId()) {
-               case MENU_FILE_EXIT:
-                       close(this);
-                       return true;
-               case MENU_FILE_OPEN:
-                       String openfile = null;
-                       try {
-                               openfile = fileOpenBox(".");
-                               reader.setMeta(BasicReader.getUrl(openfile), null);
-                               read(false);
-                       } catch (IOException e) {
-                               // TODO: i18n
-                               error("Fail to open file"
-                                               + (openfile == null ? "" : ": " + openfile),
-                                               "Import error", e);
-                       }
-
-                       return true;
-               case MENU_FILE_DELETE:
-                       String luid = null;
-                       String story = null;
-                       MetaData meta = null;
-                       if (main != null) {
-                               meta = main.getSelectedMeta();
-                       }
-                       if (meta != null) {
-                               luid = meta.getLuid();
-                               story = luid + ": " + meta.getTitle();
-                       }
-
-                       // TODO: i18n
-                       TMessageBox mbox = messageBox("Delete story", "Delete story \""
-                                       + story + "\"", Type.OKCANCEL);
-                       if (mbox.getResult() == Result.OK) {
-                               try {
-                                       reader.getLibrary().delete(luid);
-                                       if (main != null) {
-                                               main.refreshStories();
-                                       }
-                               } catch (IOException e) {
-                                       // TODO: i18n
-                                       error("Fail to delete the story: \"" + story + "\"",
-                                                       "Error", e);
-                               }
-                       }
-
-                       return true;
-               case MENU_FILE_IMPORT_URL:
-                       String clipboard = "";
-                       try {
-                               clipboard = ("" + Toolkit.getDefaultToolkit()
-                                               .getSystemClipboard().getData(DataFlavor.stringFlavor))
-                                               .trim();
-                       } catch (Exception e) {
-                               // No data will be handled
-                       }
-
-                       if (clipboard == null || !clipboard.startsWith("http")) {
-                               clipboard = "";
-                       }
-
-                       String url = inputBox("Import story", "URL to import", clipboard)
-                                       .getText();
-
-                       try {
-                               if (!imprt(url)) {
-                                       // TODO: i18n
-                                       error("URK not supported: " + url, "Import error");
-                               }
-                       } catch (IOException e) {
-                               // TODO: i18n
-                               error("Fail to import URL: " + url, "Import error", e);
-                       }
-
-                       return true;
-               case MENU_FILE_IMPORT_FILE:
-                       String filename = null;
-                       try {
-                               filename = fileOpenBox(".");
-                               if (!imprt(filename)) {
-                                       // TODO: i18n
-                                       error("File not supported: " + filename, "Import error");
-                               }
-                       } catch (IOException e) {
-                               // TODO: i18n
-                               error("Fail to import file"
-                                               + (filename == null ? "" : ": " + filename),
-                                               "Import error", e);
-                       }
-                       return true;
-               case MENU_FILE_LIBRARY:
-                       showMain();
-                       return true;
-                       
-               case MENU_OPT_FANFIX:
-                       new TuiReaderOptionWindow(this, false).maximize();
-                       return true;
-               
-               case MENU_OPT_TUI:
-                       new TuiReaderOptionWindow(this, true).maximize();
-                       return true;
-                       
-               }
-
-               return super.onMenu(menu);
-       }
-
-       /**
-        * Import the given url.
-        * <p>
-        * Can fail if the host is not supported.
-        * 
-        * @param url
-        * 
-        * @return TRUE in case of success, FALSE if the host is not supported
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       private boolean imprt(String url) throws IOException {
-               try {
-                       reader.getLibrary().imprt(BasicReader.getUrl(url), null);
-                       main.refreshStories();
-                       return true;
-               } catch (UnknownHostException e) {
-                       return false;
-               }
-       }
-
-       @Override
-       public void openExternal(BasicLibrary lib, String luid, boolean sync)
-                       throws IOException {
-               reader.openExternal(lib, luid, sync);
-       }
-
-       /**
-        * Display an error message and log it.
-        * 
-        * @param message
-        *            the message
-        * @param title
-        *            the title of the error message
-        */
-       private void error(String message, String title) {
-               error(message, title, null);
-       }
-
-       /**
-        * Display an error message and log it, including the linked
-        * {@link Exception}.
-        * 
-        * @param message
-        *            the message
-        * @param title
-        *            the title of the error message
-        * @param e
-        *            the exception to log if any (can be NULL)
-        */
-       private void error(String message, String title, Exception e) {
-               Instance.getInstance().getTraceHandler().error(title + ": " + message);
-               if (e != null) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-
-               if (e != null) {
-                       messageBox(title, message //
-                                       + "\n" + e.getMessage());
-               } else {
-                       messageBox(title, message);
-               }
-       }
-
-       /**
-        * Ask the user and, if confirmed, close the {@link TApplication} this
-        * {@link TWidget} is running on.
-        * <p>
-        * This should result in the program terminating.
-        * 
-        * @param widget
-        *            the {@link TWidget}
-        */
-       static public void close(TWidget widget) {
-               close(widget.getApplication());
-       }
-
-       /**
-        * Ask the user and, if confirmed, close the {@link TApplication}.
-        * <p>
-        * This should result in the program terminating.
-        * 
-        * @param app
-        *            the {@link TApplication}
-        */
-       static void close(TApplication app) {
-               // TODO: i18n
-               if (app.messageBox("Confirmation", "(TODO: i18n) Exit application?",
-                               TMessageBox.Type.YESNO).getResult() == TMessageBox.Result.YES) {
-                       app.exit();
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReaderMainWindow.java b/src/be/nikiroo/fanfix/reader/tui/TuiReaderMainWindow.java
deleted file mode 100644 (file)
index 2ee319a..0000000
+++ /dev/null
@@ -1,374 +0,0 @@
-package be.nikiroo.fanfix.reader.tui;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import jexer.TAction;
-import jexer.TComboBox;
-import jexer.TCommand;
-import jexer.TField;
-import jexer.TFileOpenBox.Type;
-import jexer.TKeypress;
-import jexer.TLabel;
-import jexer.TList;
-import jexer.TStatusBar;
-import jexer.TWindow;
-import jexer.event.TCommandEvent;
-import jexer.event.TKeypressEvent;
-import jexer.event.TMenuEvent;
-import jexer.event.TResizeEvent;
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.fanfix.output.BasicOutput.OutputType;
-import be.nikiroo.jexer.TSizeConstraint;
-
-/**
- * The library window, that will list all the (filtered) stories available in
- * this {@link BasicLibrary}.
- * 
- * @author niki
- */
-class TuiReaderMainWindow extends TWindow {
-       public static final int MENU_SEARCH = 1100;
-       public static final TCommand CMD_SEARCH = new TCommand(MENU_SEARCH) {
-       };
-
-       public enum Mode {
-               SOURCE, AUTHOR,
-       }
-
-       private TList list;
-       private List<MetaData> listKeys;
-       private List<String> listItems;
-       private TuiReaderApplication reader;
-
-       private Mode mode = Mode.SOURCE;
-       private String target = null;
-       private String filter = "";
-
-       private List<TSizeConstraint> sizeConstraints = new ArrayList<TSizeConstraint>();
-
-       // The 2 comboboxes used to select by source/author
-       private TComboBox selectTargetBox;
-       private TComboBox selectBox;
-
-       /**
-        * Create a new {@link TuiReaderMainWindow} without any stories in the list.
-        * 
-        * @param reader
-        *            the reader and main application
-        */
-       public TuiReaderMainWindow(TuiReaderApplication reader) {
-               // Construct a demo window. X and Y don't matter because it will be
-               // centred on screen.
-               super(reader, "Library", 0, 0, 60, 18, CENTERED | RESIZABLE);
-
-               this.reader = reader;
-
-               listKeys = new ArrayList<MetaData>();
-               listItems = new ArrayList<String>();
-
-               addList();
-               addSearch();
-               addSelect();
-
-               TStatusBar statusBar = reader.setStatusBar(this, "Library");
-               statusBar.addShortcutKeypress(TKeypress.kbCtrlF, CMD_SEARCH, "Search");
-
-               TSizeConstraint.resize(sizeConstraints);
-
-               // TODO: remove when not used anymore
-
-               // addLabel("Label (1,1)", 1, 1);
-               // addButton("&Button (35,1)", 35, 1, new TAction() {
-               // public void DO() {
-               // }
-               // });
-               // addCheckbox(1, 2, "Checky (1,2)", false);
-               // addProgressBar(1, 3, 30, 42);
-               // TRadioGroup groupy = addRadioGroup(1, 4, "Radio groupy");
-               // groupy.addRadioButton("Fanfan");
-               // groupy.addRadioButton("Tulipe");
-               // addField(1, 10, 20, false, "text not fixed.");
-               // addField(1, 11, 20, true, "text fixed.");
-               // addText("20x4 Text in (12,20)", 1, 12, 20, 4);
-               //
-               // TTreeView tree = addTreeView(30, 5, 20, 5);
-               // TTreeItem root = new TTreeItem(tree, "expended root", true);
-               // tree.setSelected(root); // needed to allow arrow navigation without
-               // // mouse-clicking before
-               //
-               // root.addChild("child");
-               // root.addChild("child 2").addChild("sub child");
-       }
-
-       private void addSearch() {
-               TLabel lblSearch = addLabel("Search: ", 0, 0);
-
-               TField search = new TField(this, 0, 0, 1, true) {
-                       @Override
-                       public void onKeypress(TKeypressEvent keypress) {
-                               super.onKeypress(keypress);
-                               TKeypress key = keypress.getKey();
-                               if (key.isFnKey() && key.getKeyCode() == TKeypress.ENTER) {
-                                       TuiReaderMainWindow.this.filter = getText();
-                                       TuiReaderMainWindow.this.refreshStories();
-                               }
-                       }
-               };
-
-               TSizeConstraint.setSize(sizeConstraints, lblSearch, 5, 1, null, null);
-               TSizeConstraint.setSize(sizeConstraints, search, 15, 1, -5, null);
-       }
-
-       private void addList() {
-               list = addList(listItems, 0, 0, 10, 10, new TAction() {
-                       @Override
-                       public void DO() {
-                               MetaData meta = getSelectedMeta();
-                               if (meta != null) {
-                                       readStory(meta);
-                               }
-                       }
-               });
-
-               TSizeConstraint.setSize(sizeConstraints, list, 0, 7, 0, 0);
-       }
-
-       private void addSelect() {
-               // TODO: i18n
-               final List<String> selects = new ArrayList<String>();
-               selects.add("(show all)");
-               selects.add("Sources");
-               selects.add("Author");
-
-               final List<String> selectTargets = new ArrayList<String>();
-               selectTargets.add("");
-
-               TLabel lblSelect = addLabel("Select: ", 0, 0);
-
-               TAction onSelect = new TAction() {
-                       @Override
-                       public void DO() {
-                               String smode = selectBox.getText();
-                               boolean showTarget;
-                               if (smode == null || smode.equals("(show all)")) {
-                                       showTarget = false;
-                               } else if (smode.equals("Sources")) {
-                                       selectTargets.clear();
-                                       selectTargets.add("(show all)");
-                                       try {
-                                               for (String source : reader.getLibrary().getSources()) {
-                                                       selectTargets.add(source);
-                                               }
-                                       } catch (IOException e) {
-                                               Instance.getInstance().getTraceHandler().error(e);
-                                       }
-
-                                       showTarget = true;
-                               } else {
-                                       selectTargets.clear();
-                                       selectTargets.add("(show all)");
-                                       try {
-                                               for (String author : reader.getLibrary().getAuthors()) {
-                                                       selectTargets.add(author);
-                                               }
-                                       } catch (IOException e) {
-                                               Instance.getInstance().getTraceHandler().error(e);
-                                       }
-
-                                       showTarget = true;
-                               }
-
-                               selectTargetBox.setVisible(showTarget);
-                               selectTargetBox.setEnabled(showTarget);
-                               if (showTarget) {
-                                       selectTargetBox.reflowData();
-                               }
-
-                               selectTargetBox.setText(selectTargets.get(0));
-                               if (showTarget) {
-                                       TuiReaderMainWindow.this.activate(selectTargetBox);
-                               } else {
-                                       TuiReaderMainWindow.this.activate(list);
-                               }
-                       }
-               };
-
-               selectBox = addComboBox(0, 0, 10, selects, 0, -1, onSelect);
-
-               selectTargetBox = addComboBox(0, 0, 0, selectTargets, 0, -1,
-                               new TAction() {
-                                       @Override
-                                       public void DO() {
-                                               if (selectTargetBox.getText().equals(
-                                                               selectTargets.get(0))) {
-                                                       setMode(mode, null);
-                                               } else {
-                                                       setMode(mode, selectTargetBox.getText());
-                                               }
-                                       }
-                               });
-
-               // Set defaults
-               onSelect.DO();
-
-               TSizeConstraint.setSize(sizeConstraints, lblSelect, 5, 3, null, null);
-               TSizeConstraint.setSize(sizeConstraints, selectBox, 15, 3, -5, null);
-               TSizeConstraint.setSize(sizeConstraints, selectTargetBox, 15, 4, -5,
-                               null);
-       }
-
-       @Override
-       public void onResize(TResizeEvent resize) {
-               super.onResize(resize);
-               TSizeConstraint.resize(sizeConstraints);
-       }
-
-       @Override
-       public void onClose() {
-               setVisible(false);
-               super.onClose();
-       }
-
-       /**
-        * Refresh the list of stories displayed in this library.
-        * <p>
-        * Will take the current settings into account (filter, source...).
-        */
-       public void refreshStories() {
-               List<MetaData> metas;
-
-               try {
-                       if (mode == Mode.SOURCE) {
-                               metas = reader.getLibrary().getList().filter(target, null, null);
-                       } else if (mode == Mode.AUTHOR) {
-                               metas = reader.getLibrary().getList().filter(null, target, null);
-                       } else {
-                               metas = reader.getLibrary().getList().getMetas();
-                       }
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-                       metas = new ArrayList<MetaData>();
-               }
-
-               setMetas(metas);
-       }
-
-       /**
-        * Change the author/source filter and display all stories matching this
-        * target.
-        * 
-        * @param mode
-        *            the new mode or NULL for no sorting
-        * @param target
-        *            the actual target for the given mode, or NULL for all of them
-        */
-       public void setMode(Mode mode, String target) {
-               this.mode = mode;
-               this.target = target;
-               refreshStories();
-       }
-
-       /**
-        * Update the list of stories displayed in this {@link TWindow}.
-        * <p>
-        * If a filter is set, only the stories which pass the filter will be
-        * displayed.
-        * 
-        * @param metas
-        *            the new list of stories to display
-        */
-       private void setMetas(List<MetaData> metas) {
-               listKeys.clear();
-               listItems.clear();
-
-               if (metas != null) {
-                       for (MetaData meta : metas) {
-                               String desc = desc(meta);
-                               if (filter.isEmpty()
-                                               || desc.toLowerCase().contains(filter.toLowerCase())) {
-                                       listKeys.add(meta);
-                                       listItems.add(desc);
-                               }
-                       }
-               }
-
-               list.setList(listItems);
-               if (listItems.size() > 0) {
-                       list.setSelectedIndex(0);
-               }
-       }
-
-       public MetaData getSelectedMeta() {
-               if (list.getSelectedIndex() >= 0) {
-                       return listKeys.get(list.getSelectedIndex());
-               }
-
-               return null;
-       }
-
-       public void readStory(MetaData meta) {
-               try {
-                       reader.setChapter(-1);
-                       reader.setMeta(meta);
-                       reader.read(false);
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-       }
-
-       private String desc(MetaData meta) {
-               return String.format("%5s: %s", meta.getLuid(), meta.getTitle());
-       }
-
-       @Override
-       public void onCommand(TCommandEvent command) {
-               if (command.getCmd().equals(TuiReaderApplication.CMD_EXIT)) {
-                       TuiReaderApplication.close(this);
-               } else {
-                       // Handle our own event if needed here
-                       super.onCommand(command);
-               }
-       }
-
-       @Override
-       public void onMenu(TMenuEvent menu) {
-               MetaData meta = getSelectedMeta();
-               if (meta != null) {
-                       switch (menu.getId()) {
-                       case TuiReaderApplication.MENU_FILE_OPEN:
-                               readStory(meta);
-
-                               return;
-                       case TuiReaderApplication.MENU_FILE_EXPORT:
-
-                               try {
-                                       // TODO: choose type, pg, error
-                                       OutputType outputType = OutputType.EPUB;
-                                       String path = fileOpenBox(".", Type.SAVE);
-                                       reader.getLibrary().export(meta.getLuid(), outputType,
-                                                       path, null);
-                               } catch (IOException e) {
-                                       // TODO
-                                       e.printStackTrace();
-                               }
-
-                               return;
-
-                       case -1:
-                               try {
-                                       reader.getLibrary().delete(meta.getLuid());
-                               } catch (IOException e) {
-                                       // TODO
-                               }
-
-                               return;
-                       }
-               }
-
-               super.onMenu(menu);
-       }
-}
\ No newline at end of file
diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReaderOptionWindow.java b/src/be/nikiroo/fanfix/reader/tui/TuiReaderOptionWindow.java
deleted file mode 100644 (file)
index 451eef6..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-package be.nikiroo.fanfix.reader.tui;
-
-import jexer.TStatusBar;
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.bundles.Config;
-import be.nikiroo.fanfix.bundles.UiConfig;
-
-class TuiReaderOptionWindow extends TOptionWindow {
-       public TuiReaderOptionWindow(TuiReaderApplication reader, boolean uiOptions) {
-               super(reader, uiOptions ? UiConfig.class : Config.class,
-                               uiOptions ? Instance.getInstance().getUiConfig() : Instance.getInstance().getConfig(), "Options");
-
-               TStatusBar statusBar = reader.setStatusBar(this, "Options");
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java b/src/be/nikiroo/fanfix/reader/tui/TuiReaderStoryWindow.java
deleted file mode 100644 (file)
index 4848ef8..0000000
+++ /dev/null
@@ -1,305 +0,0 @@
-package be.nikiroo.fanfix.reader.tui;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-import jexer.TAction;
-import jexer.TButton;
-import jexer.TLabel;
-import jexer.TText;
-import jexer.TWindow;
-import jexer.event.TCommandEvent;
-import jexer.event.TResizeEvent;
-import be.nikiroo.fanfix.data.Chapter;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Paragraph;
-import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.reader.BasicReader;
-import be.nikiroo.jexer.TSizeConstraint;
-import be.nikiroo.jexer.TTable;
-
-/**
- * This window will contain the {@link Story} in a readable format, with a
- * chapter browser.
- * 
- * @author niki
- */
-class TuiReaderStoryWindow extends TWindow {
-       private Story story;
-       private TLabel titleField;
-       private TText textField;
-       private TTable table;
-       private int chapter = -99; // invalid value
-       private List<TButton> navigationButtons;
-       private TLabel currentChapter;
-       private List<TSizeConstraint> sizeConstraints = new ArrayList<TSizeConstraint>();
-
-       // chapter: -1 for "none" (0 is desc)
-       public TuiReaderStoryWindow(TuiReaderApplication app, Story story,
-                       int chapter) {
-               super(app, desc(story.getMeta()), 0, 0, 60, 18, CENTERED | RESIZABLE);
-
-               this.story = story;
-
-               app.setStatusBar(this, desc(story.getMeta()));
-
-               // last = use window background
-               titleField = new TLabel(this, "    Title", 0, 1, "tlabel", false);
-               textField = new TText(this, "", 0, 0, 1, 1);
-               table = new TTable(this, 0, 0, 1, 1, null, null, Arrays.asList("Key",
-                               "Value"), true);
-
-               titleField.setEnabled(false);
-
-               navigationButtons = new ArrayList<TButton>(5);
-
-               navigationButtons.add(addButton("<<", 0, 0, new TAction() {
-                       @Override
-                       public void DO() {
-                               setChapter(-1);
-                       }
-               }));
-               navigationButtons.add(addButton("< ", 4, 0, new TAction() {
-                       @Override
-                       public void DO() {
-                               setChapter(TuiReaderStoryWindow.this.chapter - 1);
-                       }
-               }));
-               navigationButtons.add(addButton("> ", 7, 0, new TAction() {
-                       @Override
-                       public void DO() {
-                               setChapter(TuiReaderStoryWindow.this.chapter + 1);
-                       }
-               }));
-               navigationButtons.add(addButton(">>", 10, 0, new TAction() {
-                       @Override
-                       public void DO() {
-                               setChapter(getStory().getChapters().size());
-                       }
-               }));
-
-               navigationButtons.get(0).setEnabled(false);
-               navigationButtons.get(1).setEnabled(false);
-
-               currentChapter = addLabel("", 0, 0);
-
-               TSizeConstraint.setSize(sizeConstraints, textField, 1, 3, -1, -1);
-               TSizeConstraint.setSize(sizeConstraints, table, 0, 3, 0, -1);
-               TSizeConstraint.setSize(sizeConstraints, currentChapter, 14, -3, -1,
-                               null);
-
-               for (TButton navigationButton : navigationButtons) {
-                       navigationButton.setShadowColor(null);
-                       // navigationButton.setEmptyBorders(false);
-                       TSizeConstraint.setSize(sizeConstraints, navigationButton, null,
-                                       -3, null, null);
-               }
-
-               onResize(null);
-
-               setChapter(chapter);
-       }
-
-       @Override
-       public void onResize(TResizeEvent resize) {
-               if (resize != null) {
-                       super.onResize(resize);
-               }
-
-               // TODO: find out why TText and TTable does not behave the same way
-               // (offset of 2 for height and width)
-
-               TSizeConstraint.resize(sizeConstraints);
-
-               // Improve the disposition of the scrollbars
-               textField.getVerticalScroller().setX(textField.getWidth());
-               textField.getVerticalScroller().setHeight(textField.getHeight());
-               textField.getHorizontalScroller().setX(-1);
-               textField.getHorizontalScroller().setWidth(textField.getWidth() + 1);
-
-               setCurrentChapterText();
-       }
-
-       /**
-        * Display the current chapter in the window, or the {@link Story} info
-        * page.
-        * 
-        * @param chapter
-        *            the chapter (including "0" which is the description) or "-1"
-        *            to display the info page instead
-        */
-       private void setChapter(int chapter) {
-               if (chapter > getStory().getChapters().size()) {
-                       chapter = getStory().getChapters().size();
-               }
-
-               if (chapter != this.chapter) {
-                       this.chapter = chapter;
-
-                       int max = getStory().getChapters().size();
-                       navigationButtons.get(0).setEnabled(chapter > -1);
-                       navigationButtons.get(1).setEnabled(chapter > -1);
-                       navigationButtons.get(2).setEnabled(chapter < max);
-                       navigationButtons.get(3).setEnabled(chapter < max);
-
-                       if (chapter < 0) {
-                               displayInfoPage();
-                       } else {
-                               displayChapterPage();
-                       }
-               }
-
-               setCurrentChapterText();
-       }
-
-       /**
-        * Append the info page about the current {@link Story}.
-        * 
-        * @param builder
-        *            the builder to append to
-        */
-       private void displayInfoPage() {
-               textField.setVisible(false);
-               table.setVisible(true);
-               textField.setEnabled(false);
-               table.setEnabled(true);
-
-               MetaData meta = getStory().getMeta();
-
-               setCurrentTitle(meta.getTitle());
-
-               Map<String, String> metaDesc = BasicReader.getMetaDesc(meta);
-               String[][] metaDescObj = new String[metaDesc.size()][2];
-               int i = 0;
-               for (String key : metaDesc.keySet()) {
-                       metaDescObj[i][0] = " " + key;
-                       metaDescObj[i][1] = metaDesc.get(key);
-                       i++;
-               }
-
-               table.setRowData(metaDescObj);
-               table.setHeaders(Arrays.asList("key", "value"), false);
-               table.toTop();
-       }
-
-       /**
-        * Append the current chapter.
-        * 
-        * @param builder
-        *            the builder to append to
-        */
-       private void displayChapterPage() {
-               table.setVisible(false);
-               textField.setVisible(true);
-               table.setEnabled(false);
-               textField.setEnabled(true);
-
-               StringBuilder builder = new StringBuilder();
-
-               Chapter chap = null;
-               if (chapter == 0) {
-                       chap = getStory().getMeta().getResume();
-               } else if (chapter > 0) {
-                       chap = getStory().getChapters().get(chapter - 1);
-               }
-
-               // TODO: i18n
-               String chapName = chap == null ? "[No RESUME]" : chap.getName();
-               setCurrentTitle(String.format("Chapter %d: %s", chapter, chapName));
-
-               if (chap != null) {
-                       for (Paragraph para : chap) {
-                               if (para.getType() == ParagraphType.BREAK) {
-                                       builder.append("\n");
-                               }
-                               builder.append(para.getContent()).append("\n");
-                               if (para.getType() == ParagraphType.BREAK) {
-                                       builder.append("\n");
-                               }
-                       }
-               }
-
-               setText(builder.toString());
-       }
-
-       private Story getStory() {
-               return story;
-       }
-
-       /**
-        * Display the given text on the window.
-        * 
-        * @param text
-        *            the text to display
-        */
-       private void setText(String text) {
-               textField.setText(text);
-               textField.reflowData();
-               textField.toTop();
-       }
-
-       /**
-        * Set the current chapter area to the correct value.
-        */
-       private void setCurrentChapterText() {
-               String name;
-               if (chapter < 0) {
-                       name = " " + getStory().getMeta().getTitle();
-               } else if (chapter == 0) {
-                       Chapter resume = getStory().getMeta().getResume();
-                       if (resume != null) {
-                               name = String.format(" %s", resume.getName());
-                       } else {
-                               // TODO: i18n
-                               name = "[No RESUME]";
-                       }
-               } else {
-                       int max = getStory().getChapters().size();
-                       Chapter chap = getStory().getChapters().get(chapter - 1);
-                       name = String.format(" %d/%d: %s", chapter, max, chap.getName());
-               }
-
-               int width = getWidth() - currentChapter.getX();
-               name = String.format("%-" + width + "s", name);
-               if (name.length() > width) {
-                       name = name.substring(0, width);
-               }
-
-               currentChapter.setLabel(name);
-       }
-
-       /**
-        * Set the current title in-window.
-        * 
-        * @param title
-        *            the new title
-        */
-       private void setCurrentTitle(String title) {
-               String pad = "";
-               if (title.length() < getWidth()) {
-                       int padSize = (getWidth() - title.length()) / 2;
-                       pad = String.format("%" + padSize + "s", "");
-               }
-
-               title = pad + title + pad;
-               titleField.setWidth(title.length());
-               titleField.setLabel(title);
-       }
-
-       private static String desc(MetaData meta) {
-               return String.format("%s: %s", meta.getLuid(), meta.getTitle());
-       }
-
-       @Override
-       public void onCommand(TCommandEvent command) {
-               if (command.getCmd().equals(TuiReaderApplication.CMD_EXIT)) {
-                       TuiReaderApplication.close(this);
-               } else {
-                       // Handle our own event if needed here
-                       super.onCommand(command);
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReader.java b/src/be/nikiroo/fanfix/reader/ui/GuiReader.java
deleted file mode 100644 (file)
index a02cc84..0000000
+++ /dev/null
@@ -1,499 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.Desktop;
-import java.awt.EventQueue;
-import java.awt.event.WindowAdapter;
-import java.awt.event.WindowEvent;
-import java.io.File;
-import java.io.IOException;
-import java.net.URISyntaxException;
-
-import javax.swing.JEditorPane;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JOptionPane;
-import javax.swing.event.HyperlinkEvent;
-import javax.swing.event.HyperlinkListener;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.VersionCheck;
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.bundles.UiConfig;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.fanfix.library.CacheLibrary;
-import be.nikiroo.fanfix.reader.BasicReader;
-import be.nikiroo.fanfix.reader.Reader;
-import be.nikiroo.fanfix.searchable.BasicSearchable;
-import be.nikiroo.fanfix.searchable.SearchableTag;
-import be.nikiroo.fanfix.supported.SupportType;
-import be.nikiroo.utils.Progress;
-import be.nikiroo.utils.Version;
-import be.nikiroo.utils.ui.UIUtils;
-
-/**
- * This class implements a graphical {@link Reader} using the Swing library from
- * Java.
- * <p>
- * It can thus be themed to look native-like, or metal-like, or use any other
- * theme you may want to try.
- * <p>
- * We actually try to enable native look-alike mode on start.
- * 
- * @author niki
- */
-class GuiReader extends BasicReader {
-       static private boolean nativeLookLoaded;
-
-       private CacheLibrary cacheLib;
-
-       private File cacheDir;
-
-       /**
-        * Create a new graphical {@link Reader}.
-        * 
-        * @throws IOException
-        *             in case of I/O errors
-        */
-       public GuiReader() throws IOException {
-               // TODO: allow different themes?
-               if (!nativeLookLoaded) {
-                       UIUtils.setLookAndFeel();
-                       nativeLookLoaded = true;
-               }
-
-               cacheDir = Instance.getInstance().getReaderDir();
-               cacheDir.mkdirs();
-               if (!cacheDir.exists()) {
-                       throw new IOException(
-                                       "Cannote create cache directory for local reader: "
-                                                       + cacheDir);
-               }
-       }
-
-       @Override
-       public synchronized BasicLibrary getLibrary() {
-               if (cacheLib == null) {
-                       BasicLibrary lib = super.getLibrary();
-                       if (lib instanceof CacheLibrary) {
-                               cacheLib = (CacheLibrary) lib;
-                       } else {
-                               cacheLib = new CacheLibrary(cacheDir, lib, Instance.getInstance().getUiConfig());
-                       }
-               }
-
-               return cacheLib;
-       }
-
-       @Override
-       public void read(boolean sync) throws IOException {
-               MetaData meta = getMeta();
-
-               if (meta == null) {
-                       throw new IOException("No story to read");
-               }
-
-               read(meta.getLuid(), sync, null);
-       }
-
-       /**
-        * Check if the {@link Story} denoted by this Library UID is present in the
-        * {@link GuiReader} cache.
-        * 
-        * @param luid
-        *            the Library UID
-        * 
-        * @return TRUE if it is
-        */
-       public boolean isCached(String luid) {
-               return cacheLib.isCached(luid);
-       }
-
-       @Override
-       public void browse(String type) {
-               final Boolean[] done = new Boolean[] { false };
-
-               // TODO: improve presentation of update message
-               final VersionCheck updates = VersionCheck.check();
-               StringBuilder builder = new StringBuilder();
-
-               final JEditorPane updateMessage = new JEditorPane("text/html", "");
-               if (updates.isNewVersionAvailable()) {
-                       builder.append(trans(StringIdGui.NEW_VERSION_AVAILABLE,
-                                       "<span style='color: blue;'>https://github.com/nikiroo/fanfix/releases</span>"));
-                       builder.append("<br>");
-                       builder.append("<br>");
-                       for (Version v : updates.getNewer()) {
-                               builder.append("\t<b>"
-                                               + trans(StringIdGui.NEW_VERSION_VERSION, v.toString())
-                                               + "</b>");
-                               builder.append("<br>");
-                               builder.append("<ul>");
-                               for (String item : updates.getChanges().get(v)) {
-                                       builder.append("<li>" + item + "</li>");
-                               }
-                               builder.append("</ul>");
-                       }
-
-                       // html content
-                       updateMessage.setText("<html><body>" //
-                                       + builder//
-                                       + "</body></html>");
-
-                       // handle link events
-                       updateMessage.addHyperlinkListener(new HyperlinkListener() {
-                               @Override
-                               public void hyperlinkUpdate(HyperlinkEvent e) {
-                                       if (e.getEventType().equals(
-                                                       HyperlinkEvent.EventType.ACTIVATED))
-                                               try {
-                                                       Desktop.getDesktop().browse(e.getURL().toURI());
-                                               } catch (IOException ee) {
-                                                       Instance.getInstance().getTraceHandler().error(ee);
-                                               } catch (URISyntaxException ee) {
-                                                       Instance.getInstance().getTraceHandler().error(ee);
-                                               }
-                               }
-                       });
-                       updateMessage.setEditable(false);
-                       updateMessage.setBackground(new JLabel().getBackground());
-               }
-
-               final String typeFinal = type;
-               EventQueue.invokeLater(new Runnable() {
-                       @Override
-                       public void run() {
-                               if (updates.isNewVersionAvailable()) {
-                                       int rep = JOptionPane.showConfirmDialog(null,
-                                                       updateMessage,
-                                                       trans(StringIdGui.NEW_VERSION_TITLE),
-                                                       JOptionPane.OK_CANCEL_OPTION);
-                                       if (rep == JOptionPane.OK_OPTION) {
-                                               updates.ok();
-                                       } else {
-                                               updates.ignore();
-                                       }
-                               }
-
-                               new Thread(new Runnable() {
-                                       @Override
-                                       public void run() {
-                                               try {
-                                                       GuiReaderFrame gui = new GuiReaderFrame(
-                                                                       GuiReader.this, typeFinal);
-                                                       sync(gui);
-                                               } catch (Exception e) {
-                                                       Instance.getInstance().getTraceHandler().error(e);
-                                               } finally {
-                                                       done[0] = true;
-                                               }
-
-                                       }
-                               }).start();
-                       }
-               });
-
-               // This action must be synchronous, so wait until the frame is closed
-               while (!done[0]) {
-                       try {
-                               Thread.sleep(100);
-                       } catch (InterruptedException e) {
-                       }
-               }
-       }
-
-       @Override
-       public void start(File target, String program, boolean sync)
-                       throws IOException {
-
-               boolean handled = false;
-               if (program == null && !sync) {
-                       try {
-                               Desktop.getDesktop().browse(target.toURI());
-                               handled = true;
-                       } catch (UnsupportedOperationException e) {
-                       }
-               }
-
-               if (!handled) {
-                       super.start(target, program, sync);
-               }
-       }
-
-       @Override
-       public void search(boolean sync) throws IOException {
-               GuiReaderSearchFrame search = new GuiReaderSearchFrame(this);
-               if (sync) {
-                       sync(search);
-               } else {
-                       search.setVisible(true);
-               }
-       }
-
-       @Override
-       public void search(SupportType searchOn, String keywords, int page,
-                       int item, boolean sync) {
-               GuiReaderSearchFrame search = new GuiReaderSearchFrame(this);
-               search.search(searchOn, keywords, page, item);
-               if (sync) {
-                       sync(search);
-               } else {
-                       search.setVisible(true);
-               }
-       }
-
-       @Override
-       public void searchTag(final SupportType searchOn, final int page,
-                       final int item, final boolean sync, final Integer... tags) {
-
-               final GuiReaderSearchFrame search = new GuiReaderSearchFrame(this);
-
-               final BasicSearchable searchable = BasicSearchable
-                               .getSearchable(searchOn);
-
-               Runnable action = new Runnable() {
-                       @Override
-                       public void run() {
-                               SearchableTag tag = null;
-                               try {
-                                       tag = searchable.getTag(tags);
-                               } catch (IOException e) {
-                                       Instance.getInstance().getTraceHandler().error(e);
-                               }
-
-                               search.searchTag(searchOn, page, item, tag);
-
-                               if (sync) {
-                                       sync(search);
-                               } else {
-                                       search.setVisible(true);
-                               }
-                       }
-               };
-
-               if (sync) {
-                       action.run();
-               } else {
-                       new Thread(action).start();
-               }
-       }
-
-       /**
-        * Delete the {@link Story} from the cache if it is present, but <b>NOT</b>
-        * from the main library.
-        * <p>
-        * The next time we try to retrieve the {@link Story}, it may be required to
-        * cache it again.
-        * 
-        * @param luid
-        *            the luid of the {@link Story}
-        */
-       void clearLocalReaderCache(String luid) {
-               try {
-                       cacheLib.clearFromCache(luid);
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-       }
-
-       /**
-        * Forward the delete operation to the main library.
-        * <p>
-        * The {@link Story} will be deleted from the main library as well as the
-        * cache if present.
-        * 
-        * @param luid
-        *            the {@link Story} to delete
-        */
-       void delete(String luid) {
-               try {
-                       cacheLib.delete(luid);
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-       }
-
-       /**
-        * "Open" the given {@link Story}. It usually involves starting an external
-        * program adapted to the given file type.
-        * 
-        * @param luid
-        *            the luid of the {@link Story} to open
-        * @param sync
-        *            execute the process synchronously (wait until it is terminated
-        *            before returning)
-        * @param pg
-        *            the optional progress (we may need to prepare the
-        *            {@link Story} for reading
-        * 
-        * @throws IOException
-        *             in case of I/O errors
-        */
-       void read(String luid, boolean sync, Progress pg) throws IOException {
-               MetaData meta = cacheLib.getInfo(luid);
-
-               boolean textInternal = Instance.getInstance().getUiConfig()
-                               .getBoolean(UiConfig.NON_IMAGES_DOCUMENT_USE_INTERNAL_READER, true);
-               boolean imageInternal = Instance.getInstance().getUiConfig()
-                               .getBoolean(UiConfig.IMAGES_DOCUMENT_USE_INTERNAL_READER, true);
-
-               boolean useInternalViewer = true;
-               if (meta.isImageDocument() && !imageInternal) {
-                       useInternalViewer = false;
-               }
-               if (!meta.isImageDocument() && !textInternal) {
-                       useInternalViewer = false;
-               }
-
-               if (useInternalViewer) {
-                       GuiReaderViewer viewer = new GuiReaderViewer(cacheLib,
-                                       cacheLib.getStory(luid, null));
-                       if (sync) {
-                               sync(viewer);
-                       } else {
-                               viewer.setVisible(true);
-                       }
-               } else {
-                       File file = cacheLib.getFile(luid, pg);
-                       openExternal(meta, file, sync);
-               }
-       }
-
-
-       /**
-        * "Prefetch" the given {@link Story}.
-        * <p>
-        * Synchronous method.
-        * 
-        * @param luid
-        *            the luid of the {@link Story} to prefetch
-        * @param pg
-        *            the optional progress (we may need to prepare the
-        *            {@link Story} for reading
-        * 
-        * @throws IOException
-        *             in case of I/O errors
-        */
-       void prefetch(String luid, Progress pg) throws IOException {
-               cacheLib.getFile(luid, pg);
-       }
-       /**
-        * Change the source of the given {@link Story} (the source is the main
-        * information used to group the stories together).
-        * <p>
-        * In other words, <b>move</b> the {@link Story} into other source.
-        * <p>
-        * The source can be a new one, it needs not exist before hand.
-        * 
-        * @param luid
-        *            the luid of the {@link Story} to move
-        * @param newSource
-        *            the new source
-        */
-       void changeSource(String luid, String newSource) {
-               try {
-                       cacheLib.changeSource(luid, newSource, null);
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-       }
-
-       /**
-        * Change the title of the given {@link Story}.
-        * 
-        * @param luid
-        *            the luid of the {@link Story} to change
-        * @param newTitle
-        *            the new title
-        */
-       void changeTitle(String luid, String newTitle) {
-               try {
-                       cacheLib.changeTitle(luid, newTitle, null);
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-       }
-
-       /**
-        * Change the author of the given {@link Story}.
-        * <p>
-        * The author can be a new one, it needs not exist before hand.
-        * 
-        * @param luid
-        *            the luid of the {@link Story} to change
-        * @param newAuthor
-        *            the new author
-        */
-       void changeAuthor(String luid, String newAuthor) {
-               try {
-                       cacheLib.changeAuthor(luid, newAuthor, null);
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-       }
-
-       /**
-        * Simple shortcut method to call {link Instance#getTransGui()#getString()}.
-        * 
-        * @param id
-        *            the ID to translate
-        * 
-        * @return the translated result
-        */
-       static String trans(StringIdGui id, Object... params) {
-               return Instance.getInstance().getTransGui().getString(id, params);
-       }
-
-       /**
-        * Start a frame and wait until it is closed before returning.
-        * 
-        * @param frame
-        *            the frame to start
-        */
-       static private void sync(final JFrame frame) {
-               if (EventQueue.isDispatchThread()) {
-                       throw new IllegalStateException(
-                                       "Cannot call a sync method in the dispatch thread");
-               }
-
-               final Boolean[] done = new Boolean[] { false };
-               try {
-                       Runnable run = new Runnable() {
-                               @Override
-                               public void run() {
-                                       try {
-                                               frame.addWindowListener(new WindowAdapter() {
-                                                       @Override
-                                                       public void windowClosing(WindowEvent e) {
-                                                               super.windowClosing(e);
-                                                               done[0] = true;
-                                                       }
-                                               });
-
-                                               frame.setVisible(true);
-                                       } catch (Exception e) {
-                                               done[0] = true;
-                                       }
-                               }
-                       };
-
-                       if (EventQueue.isDispatchThread()) {
-                               run.run();
-                       } else {
-                               EventQueue.invokeLater(run);
-                       }
-               } catch (Exception e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-                       done[0] = true;
-               }
-
-               // This action must be synchronous, so wait until the frame is closed
-               while (!done[0]) {
-                       try {
-                               Thread.sleep(100);
-                       } catch (InterruptedException e) {
-                       }
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderBook.java
deleted file mode 100644 (file)
index 73ccdaa..0000000
+++ /dev/null
@@ -1,339 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Graphics;
-import java.awt.event.MouseEvent;
-import java.awt.event.MouseListener;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.EventListener;
-import java.util.List;
-
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.reader.Reader;
-
-/**
- * A book item presented in a {@link GuiReaderFrame}.
- * <p>
- * Can be a story, or a comic or... a group.
- * 
- * @author niki
- */
-class GuiReaderBook extends JPanel {
-       /**
-        * Action on a book item.
-        * 
-        * @author niki
-        */
-       interface BookActionListener extends EventListener {
-               /**
-                * The book was selected (single click).
-                * 
-                * @param book
-                *            the {@link GuiReaderBook} itself
-                */
-               public void select(GuiReaderBook book);
-
-               /**
-                * The book was double-clicked.
-                * 
-                * @param book
-                *            the {@link GuiReaderBook} itself
-                */
-               public void action(GuiReaderBook book);
-
-               /**
-                * A popup menu was requested for this {@link GuiReaderBook}.
-                * 
-                * @param book
-                *            the {@link GuiReaderBook} itself
-                * @param target
-                *            the target component for the popup
-                * @param x
-                *            the X position of the click/request (in case of popup
-                *            request from the keyboard, the center of the target is
-                *            selected as point of reference)
-                * @param y
-                *            the Y position of the click/request (in case of popup
-                *            request from the keyboard, the center of the target is
-                *            selected as point of reference)
-                */
-               public void popupRequested(GuiReaderBook book, Component target, int x,
-                               int y);
-       }
-
-       private static final long serialVersionUID = 1L;
-
-       private static final String AUTHOR_COLOR = "#888888";
-       private static final long doubleClickDelay = 200; // in ms
-
-       private JLabel icon;
-       private JLabel title;
-       private boolean selected;
-       private boolean hovered;
-       private Date lastClick;
-
-       private List<BookActionListener> listeners;
-       private GuiReaderBookInfo info;
-       private boolean cached;
-       private boolean seeWordCount;
-
-       /**
-        * Create a new {@link GuiReaderBook} item for the given {@link Story}.
-        * 
-        * @param reader
-        *            the associated reader
-        * @param info
-        *            the information about the story to represent
-        * @param cached
-        *            TRUE if it is locally cached
-        * @param seeWordCount
-        *            TRUE to see word counts, FALSE to see authors
-        */
-       public GuiReaderBook(Reader reader, GuiReaderBookInfo info, boolean cached,
-                       boolean seeWordCount) {
-               this.info = info;
-               this.cached = cached;
-               this.seeWordCount = seeWordCount;
-
-               icon = new JLabel(GuiReaderCoverImager.generateCoverIcon(
-                               reader.getLibrary(), info));
-
-               title = new JLabel();
-               updateTitle();
-
-               setLayout(new BorderLayout(10, 10));
-               add(icon, BorderLayout.CENTER);
-               add(title, BorderLayout.SOUTH);
-
-               setupListeners();
-       }
-
-       /**
-        * The book current selection state.
-        * 
-        * @return the selection state
-        */
-       public boolean isSelected() {
-               return selected;
-       }
-
-       /**
-        * The book current selection state.
-        * <p>
-        * Setting this value to true can cause a "select" action to occur if the
-        * previous state was "unselected".
-        * 
-        * @param selected
-        *            TRUE if it is selected
-        */
-       public void setSelected(boolean selected) {
-               if (this.selected != selected) {
-                       this.selected = selected;
-                       repaint();
-
-                       if (selected) {
-                               select();
-                       }
-               }
-       }
-
-       /**
-        * The item mouse-hover state.
-        * 
-        * @return TRUE if it is mouse-hovered
-        */
-       public boolean isHovered() {
-               return this.hovered;
-       }
-
-       /**
-        * The item mouse-hover state.
-        * 
-        * @param hovered
-        *            TRUE if it is mouse-hovered
-        */
-       public void setHovered(boolean hovered) {
-               if (this.hovered != hovered) {
-                       this.hovered = hovered;
-                       repaint();
-               }
-       }
-
-       /**
-        * Setup the mouse listener that will activate {@link BookActionListener}
-        * events.
-        */
-       private void setupListeners() {
-               listeners = new ArrayList<GuiReaderBook.BookActionListener>();
-               addMouseListener(new MouseListener() {
-                       @Override
-                       public void mouseReleased(MouseEvent e) {
-                               if (isEnabled() && e.isPopupTrigger()) {
-                                       popup(e);
-                               }
-                       }
-
-                       @Override
-                       public void mousePressed(MouseEvent e) {
-                               if (isEnabled() && e.isPopupTrigger()) {
-                                       popup(e);
-                               }
-                       }
-
-                       @Override
-                       public void mouseExited(MouseEvent e) {
-                               setHovered(false);
-                       }
-
-                       @Override
-                       public void mouseEntered(MouseEvent e) {
-                               setHovered(true);
-                       }
-
-                       @Override
-                       public void mouseClicked(MouseEvent e) {
-                               if (isEnabled()) {
-                                       Date now = new Date();
-                                       if (lastClick != null
-                                                       && now.getTime() - lastClick.getTime() < doubleClickDelay) {
-                                               click(true);
-                                       } else {
-                                               click(false);
-                                       }
-
-                                       lastClick = now;
-                                       e.consume();
-                               }
-                       }
-
-                       private void click(boolean doubleClick) {
-                               if (doubleClick) {
-                                       action();
-                               } else {
-                                       select();
-                               }
-                       }
-
-                       private void popup(MouseEvent e) {
-                               GuiReaderBook.this
-                                               .popup(GuiReaderBook.this, e.getX(), e.getY());
-                               e.consume();
-                       }
-               });
-       }
-
-       /**
-        * Add a new {@link BookActionListener} on this item.
-        * 
-        * @param listener
-        *            the listener
-        */
-       public void addActionListener(BookActionListener listener) {
-               listeners.add(listener);
-       }
-
-       /**
-        * Cause an action to occur on this {@link GuiReaderBook}.
-        */
-       public void action() {
-               for (BookActionListener listener : listeners) {
-                       listener.action(GuiReaderBook.this);
-               }
-       }
-
-       /**
-        * Cause a select event on this {@link GuiReaderBook}.
-        * <p>
-        * Have a look at {@link GuiReaderBook#setSelected(boolean)}.
-        */
-       private void select() {
-               for (BookActionListener listener : listeners) {
-                       listener.select(GuiReaderBook.this);
-               }
-       }
-
-       /**
-        * Request a popup.
-        * 
-        * @param target
-        *            the target component for the popup
-        * @param x
-        *            the X position of the click/request (in case of popup request
-        *            from the keyboard, the center of the target should be selected
-        *            as point of reference)
-        * @param y
-        *            the Y position of the click/request (in case of popup request
-        *            from the keyboard, the center of the target should be selected
-        *            as point of reference)
-        */
-       public void popup(Component target, int x, int y) {
-               for (BookActionListener listener : listeners) {
-                       listener.select((GuiReaderBook.this));
-                       listener.popupRequested(GuiReaderBook.this, target, x, y);
-               }
-       }
-
-       /**
-        * The information about the book represented by this item.
-        * 
-        * @return the meta
-        */
-       public GuiReaderBookInfo getInfo() {
-               return info;
-       }
-
-       /**
-        * This item {@link GuiReader} library cache state.
-        * 
-        * @return TRUE if it is present in the {@link GuiReader} cache
-        */
-       public boolean isCached() {
-               return cached;
-       }
-
-       /**
-        * This item {@link GuiReader} library cache state.
-        * 
-        * @param cached
-        *            TRUE if it is present in the {@link GuiReader} cache
-        */
-       public void setCached(boolean cached) {
-               if (this.cached != cached) {
-                       this.cached = cached;
-                       repaint();
-               }
-       }
-
-       /**
-        * Update the title, paint the item, then call
-        * {@link GuiReaderCoverImager#paintOverlay(Graphics, boolean, boolean, boolean, boolean)}
-        * .
-        */
-       @Override
-       public void paint(Graphics g) {
-               updateTitle();
-               super.paint(g);
-               GuiReaderCoverImager.paintOverlay(g, isEnabled(), isSelected(),
-                               isHovered(), isCached());
-       }
-
-       /**
-        * Update the title with the currently registered information.
-        */
-       private void updateTitle() {
-               String optSecondary = info.getSecondaryInfo(seeWordCount);
-               title.setText(String
-                               .format("<html>"
-                                               + "<body style='width: %d px; height: %d px; text-align: center'>"
-                                               + "%s" + "<br>" + "<span style='color: %s;'>" + "%s"
-                                               + "</span>" + "</body>" + "</html>",
-                                               GuiReaderCoverImager.TEXT_WIDTH,
-                                               GuiReaderCoverImager.TEXT_HEIGHT, info.getMainInfo(),
-                                               AUTHOR_COLOR, optSecondary));
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderBookInfo.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderBookInfo.java
deleted file mode 100644 (file)
index 3cef8cf..0000000
+++ /dev/null
@@ -1,258 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.io.IOException;
-
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.utils.Image;
-import be.nikiroo.utils.StringUtils;
-
-/**
- * Some meta information related to a "book" (which can either be a
- * {@link Story}, a fake-story grouping some authors or a fake-story grouping
- * some sources/types).
- * 
- * @author niki
- */
-public class GuiReaderBookInfo {
-       /**
-        * The type of {@link GuiReaderBook} (i.e., related to a story or to something else that
-        * can encompass stories).
-        * 
-        * @author niki
-        */
-       public enum Type {
-               /** A normal story, which can be "read". */
-               STORY,
-               /**
-                * A special, empty story that represents a source/type common to one or
-                * more normal stories.
-                */
-               SOURCE,
-               /** A special, empty story that represents an author. */
-               AUTHOR
-       }
-
-       private Type type;
-       private String id;
-       private String value;
-       private String count;
-
-       private MetaData meta;
-
-       /**
-        * For private use, see the "fromXXX" constructors instead for public use.
-        * 
-        * @param type
-        *            the type of book
-        * @param id
-        *            the main id, which must uniquely identify this book and will
-        *            be used as a unique ID later on
-        * @param value
-        *            the main value to show (see
-        *            {@link GuiReaderBookInfo#getMainInfo()})
-        */
-       private GuiReaderBookInfo(Type type, String id, String value) {
-               this.type = type;
-               this.id = id;
-               this.value = value;
-       }
-       
-       /**
-        * The type of {@link GuiReaderBookInfo}.
-        * 
-        * @return the type
-        */
-       public Type getType() {
-               return type;
-       }
-
-       /**
-        * Get the main info to display for this book (a title, an author, a
-        * source/type name...).
-        * <p>
-        * Note that when {@link MetaData} about the book are present, the title
-        * inside is returned instead of the actual value (that way, we can update
-        * the {@link MetaData} and see the changes here).
-        * 
-        * @return the main info, usually the title
-        */
-       public String getMainInfo() {
-               if (meta != null) {
-                       return meta.getTitle();
-               }
-
-               return value;
-       }
-
-       /**
-        * Get the secondary info, of the given type.
-        * 
-        * @param seeCount
-        *            TRUE for word/image/story count, FALSE for author name
-        * 
-        * @return the secondary info
-        */
-       public String getSecondaryInfo(boolean seeCount) {
-               String author = meta == null ? null : meta.getAuthor();
-               String secondaryInfo = seeCount ? count : author;
-
-               if (secondaryInfo != null && !secondaryInfo.trim().isEmpty()) {
-                       secondaryInfo = "(" + secondaryInfo + ")";
-               } else {
-                       secondaryInfo = "";
-               }
-
-               return secondaryInfo;
-       }
-
-       /**
-        * A unique ID for this {@link GuiReaderBookInfo}.
-        * 
-        * @return the unique ID
-        */
-       public String getId() {
-               return id;
-       }
-
-       /**
-        * The {@link MetaData} associated with this book, if this book is a
-        * {@link Story}.
-        * <p>
-        * Can be NULL for non-story books (authors or sources/types).
-        * 
-        * @return the {@link MetaData} or NULL
-        */
-       public MetaData getMeta() {
-               return meta;
-       }
-
-       /**
-        * Get the base image to use to represent this book.
-        * <p>
-        * The image is <b>NOT</b> resized in any way, this is the original version.
-        * <p>
-        * It can be NULL if no image can be found for this book.
-        * 
-        * @param lib
-        *            the {@link BasicLibrary} to use to fetch the image
-        * 
-        * @return the base image
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public Image getBaseImage(BasicLibrary lib) throws IOException {
-               switch (type) {
-               case STORY:
-                       if (meta.getCover() != null) {
-                               return meta.getCover();
-                       }
-
-                       if (meta.getLuid() != null) {
-                               return lib.getCover(meta.getLuid());
-                       }
-
-                       return null;
-               case SOURCE:
-                       return lib.getSourceCover(value);
-               case AUTHOR:
-                       return lib.getAuthorCover(value);
-               }
-
-               return null;
-       }
-
-       /**
-        * Create a new book describing the given {@link Story}.
-        * 
-        * @param meta
-        *            the {@link MetaData} representing the {@link Story}
-        * 
-        * @return the book
-        */
-       static public GuiReaderBookInfo fromMeta(MetaData meta) {
-               String uid = meta.getUuid();
-               if (uid == null || uid.trim().isEmpty()) {
-                       uid = meta.getLuid();
-               }
-               if (uid == null || uid.trim().isEmpty()) {
-                       uid = meta.getUrl();
-               }
-
-               GuiReaderBookInfo info = new GuiReaderBookInfo(Type.STORY, uid,
-                               meta.getTitle());
-
-               info.meta = meta;
-               info.count = StringUtils.formatNumber(meta.getWords());
-               if (!info.count.isEmpty()) {
-                       info.count = GuiReader.trans(
-                                       meta.isImageDocument() ? StringIdGui.BOOK_COUNT_IMAGES
-                                                       : StringIdGui.BOOK_COUNT_WORDS, info.count);
-               }
-
-               return info;
-       }
-
-       /**
-        * Create a new book describing the given source/type.
-        * 
-        * @param lib
-        *            the {@link BasicLibrary} to use to retrieve some more
-        *            information about the source
-        * @param source
-        *            the source name
-        * 
-        * @return the book
-        */
-       static public GuiReaderBookInfo fromSource(BasicLibrary lib, String source) {
-               GuiReaderBookInfo info = new GuiReaderBookInfo(Type.SOURCE, "source_"
-                               + source, source);
-
-               int size = 0;
-               try {
-                       size = lib.getList().filter(source, null, null).size();
-               } catch (IOException e) {
-               }
-
-               info.count = StringUtils.formatNumber(size);
-               if (!info.count.isEmpty()) {
-                       info.count = GuiReader.trans(StringIdGui.BOOK_COUNT_STORIES,
-                                       info.count);
-               }
-
-               return info;
-       }
-
-       /**
-        * Create a new book describing the given author.
-        * 
-        * @param lib
-        *            the {@link BasicLibrary} to use to retrieve some more
-        *            information about the author
-        * @param author
-        *            the author name
-        * 
-        * @return the book
-        */
-       static public GuiReaderBookInfo fromAuthor(BasicLibrary lib, String author) {
-               GuiReaderBookInfo info = new GuiReaderBookInfo(Type.AUTHOR, "author_"
-                               + author, author);
-
-               int size = 0;
-               try {
-                       size = lib.getList().filter(null, author, null).size();
-               } catch (IOException e) {
-               }
-
-               info.count = StringUtils.formatNumber(size);
-               if (!info.count.isEmpty()) {
-                       info.count = GuiReader.trans(StringIdGui.BOOK_COUNT_STORIES,
-                                       info.count);
-               }
-
-               return info;
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderCoverImager.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderCoverImager.java
deleted file mode 100644 (file)
index 8d5aaeb..0000000
+++ /dev/null
@@ -1,244 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.Color;
-import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.Polygon;
-import java.awt.Rectangle;
-import java.awt.image.BufferedImage;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-
-import javax.imageio.ImageIO;
-import javax.swing.ImageIcon;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.utils.Image;
-import be.nikiroo.utils.ui.ImageUtilsAwt;
-import be.nikiroo.utils.ui.UIUtils;
-
-/**
- * This class can create a cover icon ready to use for the graphical
- * application.
- * 
- * @author niki
- */
-class GuiReaderCoverImager {
-
-       // TODO: export some of the configuration options?
-       private static final int COVER_WIDTH = 100;
-       private static final int COVER_HEIGHT = 150;
-       private static final int SPINE_WIDTH = 5;
-       private static final int SPINE_HEIGHT = 5;
-       private static final int HOFFSET = 20;
-       private static final Color SPINE_COLOR_BOTTOM = new Color(180, 180, 180);
-       private static final Color SPINE_COLOR_RIGHT = new Color(100, 100, 100);
-       private static final Color BORDER = Color.black;
-
-       public static final int TEXT_HEIGHT = 50;
-       public static final int TEXT_WIDTH = COVER_WIDTH + 40;
-
-       //
-
-       /**
-        * Draw a partially transparent overlay if needed depending upon the
-        * selection and mouse-hover states on top of the normal component, as well
-        * as a possible "cached" icon if the item is cached.
-        * 
-        * @param g
-        *            the {@link Graphics} to paint onto
-        * @param enabled
-        *            draw an enabled overlay
-        * @param selected
-        *            draw a selected overlay
-        * @param hovered
-        *            draw a hovered overlay
-        * @param cached
-        *            draw a cached overlay
-        */
-       static public void paintOverlay(Graphics g, boolean enabled,
-                       boolean selected, boolean hovered, boolean cached) {
-               Rectangle clip = g.getClipBounds();
-               if (clip.getWidth() <= 0 || clip.getHeight() <= 0) {
-                       return;
-               }
-
-               int h = COVER_HEIGHT;
-               int w = COVER_WIDTH;
-               int xOffset = (TEXT_WIDTH - COVER_WIDTH) - 1;
-               int yOffset = HOFFSET;
-
-               if (BORDER != null) {
-                       if (BORDER != null) {
-                               g.setColor(BORDER);
-                               g.drawRect(xOffset, yOffset, COVER_WIDTH, COVER_HEIGHT);
-                       }
-
-                       xOffset++;
-                       yOffset++;
-               }
-
-               int[] xs = new int[] { xOffset, xOffset + SPINE_WIDTH,
-                               xOffset + w + SPINE_WIDTH, xOffset + w };
-               int[] ys = new int[] { yOffset + h, yOffset + h + SPINE_HEIGHT,
-                               yOffset + h + SPINE_HEIGHT, yOffset + h };
-               g.setColor(SPINE_COLOR_BOTTOM);
-               g.fillPolygon(new Polygon(xs, ys, xs.length));
-               xs = new int[] { xOffset + w, xOffset + w + SPINE_WIDTH,
-                               xOffset + w + SPINE_WIDTH, xOffset + w };
-               ys = new int[] { yOffset, yOffset + SPINE_HEIGHT,
-                               yOffset + h + SPINE_HEIGHT, yOffset + h };
-               g.setColor(SPINE_COLOR_RIGHT);
-               g.fillPolygon(new Polygon(xs, ys, xs.length));
-
-               Color color = new Color(255, 255, 255, 0);
-               if (!enabled) {
-               } else if (selected && !hovered) {
-                       color = new Color(80, 80, 100, 40);
-               } else if (!selected && hovered) {
-                       color = new Color(230, 230, 255, 100);
-               } else if (selected && hovered) {
-                       color = new Color(200, 200, 255, 100);
-               }
-
-               g.setColor(color);
-               g.fillRect(clip.x, clip.y, clip.width, clip.height);
-
-               if (cached) {
-                       UIUtils.drawEllipse3D(g, Color.green.darker(), COVER_WIDTH
-                                       + HOFFSET + 30, 10, 20, 20);
-               }
-       }
-
-       /**
-        * Generate a cover icon based upon the given {@link MetaData}.
-        * 
-        * @param lib
-        *            the library the meta comes from
-        * @param meta
-        *            the {@link MetaData}
-        * 
-        * @return the icon
-        */
-       static public ImageIcon generateCoverIcon(BasicLibrary lib, MetaData meta) {
-               return generateCoverIcon(lib, GuiReaderBookInfo.fromMeta(meta));
-       }
-
-       /**
-        * The width of a cover image.
-        * 
-        * @return the width
-        */
-       static public int getCoverWidth() {
-               return SPINE_WIDTH + COVER_WIDTH;
-       }
-
-       /**
-        * The height of a cover image.
-        * 
-        * @return the height
-        */
-       static public int getCoverHeight() {
-               return COVER_HEIGHT + HOFFSET;
-       }
-
-       /**
-        * Generate a cover icon based upon the given {@link GuiReaderBookInfo}.
-        * 
-        * @param lib
-        *            the library the meta comes from
-        * @param info
-        *            the {@link GuiReaderBookInfo}
-        * 
-        * @return the icon
-        */
-       static public ImageIcon generateCoverIcon(BasicLibrary lib,
-                       GuiReaderBookInfo info) {
-               BufferedImage resizedImage = null;
-               String id = getIconId(info);
-
-               InputStream in = Instance.getInstance().getCache().getFromCache(id);
-               if (in != null) {
-                       try {
-                               resizedImage = ImageUtilsAwt.fromImage(new Image(in));
-                               in.close();
-                               in = null;
-                       } catch (IOException e) {
-                               Instance.getInstance().getTraceHandler().error(e);
-                       }
-               }
-
-               if (resizedImage == null) {
-                       try {
-                               Image cover = info.getBaseImage(lib);
-                               resizedImage = new BufferedImage(getCoverWidth(),
-                                               getCoverHeight(), BufferedImage.TYPE_4BYTE_ABGR);
-
-                               Graphics2D g = resizedImage.createGraphics();
-                               try {
-                                       g.setColor(Color.white);
-                                       g.fillRect(0, HOFFSET, COVER_WIDTH, COVER_HEIGHT);
-
-                                       if (cover != null) {
-                                               BufferedImage coverb = ImageUtilsAwt.fromImage(cover);
-                                               g.drawImage(coverb, 0, HOFFSET, COVER_WIDTH,
-                                                               COVER_HEIGHT, null);
-                                       } else {
-                                               g.setColor(Color.black);
-                                               g.drawLine(0, HOFFSET, COVER_WIDTH, HOFFSET
-                                                               + COVER_HEIGHT);
-                                               g.drawLine(COVER_WIDTH, HOFFSET, 0, HOFFSET
-                                                               + COVER_HEIGHT);
-                                       }
-                               } finally {
-                                       g.dispose();
-                               }
-
-                               if (id != null) {
-                                       ByteArrayOutputStream out = new ByteArrayOutputStream();
-                                       ImageIO.write(resizedImage, "png", out);
-                                       byte[] imageBytes = out.toByteArray();
-                                       in = new ByteArrayInputStream(imageBytes);
-                                       Instance.getInstance().getCache().addToCache(in, id);
-                                       in.close();
-                                       in = null;
-                               }
-                       } catch (MalformedURLException e) {
-                               Instance.getInstance().getTraceHandler().error(e);
-                       } catch (IOException e) {
-                               Instance.getInstance().getTraceHandler().error(e);
-                       }
-               }
-
-               return new ImageIcon(resizedImage);
-       }
-
-       /**
-        * Manually clear the icon set for this item.
-        * 
-        * @param info
-        *            the info about the story or source/type or author
-        */
-       static public void clearIcon(GuiReaderBookInfo info) {
-               String id = getIconId(info);
-               Instance.getInstance().getCache().removeFromCache(id);
-       }
-
-       /**
-        * Get a unique ID from this {@link GuiReaderBookInfo} (note that it can be
-        * a story, a fake item for a source/type or a fake item for an author).
-        * 
-        * @param info
-        *            the info
-        * @return the unique ID
-        */
-       static private String getIconId(GuiReaderBookInfo info) {
-               return info.getId() + ".thumb_" + SPINE_WIDTH + "x" + COVER_WIDTH + "+"
-                               + SPINE_HEIGHT + "+" + COVER_HEIGHT + "@" + HOFFSET;
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java
deleted file mode 100644 (file)
index e0ef6d7..0000000
+++ /dev/null
@@ -1,1015 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Frame;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
-import java.awt.event.WindowEvent;
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JMenu;
-import javax.swing.JMenuBar;
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPopupMenu;
-import javax.swing.SwingUtilities;
-import javax.swing.filechooser.FileFilter;
-import javax.swing.filechooser.FileNameExtensionFilter;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.bundles.Config;
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.bundles.UiConfig;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.fanfix.library.BasicLibrary.Status;
-import be.nikiroo.fanfix.library.LocalLibrary;
-import be.nikiroo.fanfix.output.BasicOutput.OutputType;
-import be.nikiroo.fanfix.reader.BasicReader;
-import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.FrameHelper;
-import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.MetaDataRunnable;
-import be.nikiroo.fanfix.searchable.BasicSearchable;
-import be.nikiroo.fanfix.supported.SupportType;
-import be.nikiroo.utils.Progress;
-import be.nikiroo.utils.Version;
-import be.nikiroo.utils.ui.ConfigEditor;
-
-/**
- * A {@link Frame} that will show a {@link GuiReaderBook} item for each
- * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
- * way to copy them to the {@link GuiReader} cache (
- * {@link BasicReader#getLibrary()}), read them, delete them...
- * 
- * @author niki
- */
-class GuiReaderFrame extends JFrame implements FrameHelper {
-       private static final long serialVersionUID = 1L;
-       private GuiReader reader;
-       private GuiReaderMainPanel mainPanel;
-
-       /**
-        * The different modification actions you can use on {@link Story} items.
-        * 
-        * @author niki
-        */
-       private enum ChangeAction {
-               /** Change the source/type, that is, move it to another source. */
-               SOURCE,
-               /** Change its name. */
-               TITLE,
-               /** Change its author. */
-               AUTHOR
-       }
-
-       /**
-        * Create a new {@link GuiReaderFrame}.
-        * 
-        * @param reader
-        *            the associated {@link GuiReader} to forward some commands and
-        *            access its {@link LocalLibrary}
-        * @param type
-        *            the type of {@link Story} to load, or NULL for all types
-        */
-       public GuiReaderFrame(GuiReader reader, String type) {
-               super(getAppTitle(reader.getLibrary().getLibraryName()));
-
-               this.reader = reader;
-
-               mainPanel = new GuiReaderMainPanel(this, type);
-
-               setSize(800, 600);
-               setLayout(new BorderLayout());
-               add(mainPanel, BorderLayout.CENTER);
-       }
-
-       @Override
-       public JPopupMenu createBookPopup() {
-               Status status = reader.getLibrary().getStatus();
-               JPopupMenu popup = new JPopupMenu();
-               popup.add(createMenuItemOpenBook());
-               popup.addSeparator();
-               popup.add(createMenuItemExport());
-               if (status.isWritable()) {
-                       popup.add(createMenuItemMoveTo());
-                       popup.add(createMenuItemSetCoverForSource());
-                       popup.add(createMenuItemSetCoverForAuthor());
-               }
-               popup.add(createMenuItemDownloadToCache());
-               popup.add(createMenuItemClearCache());
-               if (status.isWritable()) {
-                       popup.add(createMenuItemRedownload());
-                       popup.addSeparator();
-                       popup.add(createMenuItemRename());
-                       popup.add(createMenuItemSetAuthor());
-                       popup.addSeparator();
-                       popup.add(createMenuItemDelete());
-               }
-               popup.addSeparator();
-               popup.add(createMenuItemProperties());
-               return popup;
-       }
-
-       @Override
-       public JPopupMenu createSourceAuthorPopup() {
-               JPopupMenu popup = new JPopupMenu();
-               popup.add(createMenuItemOpenBook());
-               return popup;
-       }
-
-       @Override
-       public void createMenu(Status status) {
-               invalidate();
-
-               JMenuBar bar = new JMenuBar();
-
-               JMenu file = new JMenu(GuiReader.trans(StringIdGui.MENU_FILE));
-               file.setMnemonic(KeyEvent.VK_F);
-
-               JMenuItem imprt = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_FILE_IMPORT_URL),
-                               KeyEvent.VK_U);
-               imprt.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               mainPanel.imprt(true);
-                       }
-               });
-               JMenuItem imprtF = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_FILE_IMPORT_FILE),
-                               KeyEvent.VK_F);
-               imprtF.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               mainPanel.imprt(false);
-                       }
-               });
-               JMenuItem exit = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_FILE_EXIT), KeyEvent.VK_X);
-               exit.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               GuiReaderFrame.this.dispatchEvent(new WindowEvent(
-                                               GuiReaderFrame.this, WindowEvent.WINDOW_CLOSING));
-                       }
-               });
-
-               file.add(createMenuItemOpenBook());
-               file.add(createMenuItemExport());
-               if (status.isWritable()) {
-                       file.add(createMenuItemMoveTo());
-                       file.addSeparator();
-                       file.add(imprt);
-                       file.add(imprtF);
-                       file.addSeparator();
-                       file.add(createMenuItemRename());
-                       file.add(createMenuItemSetAuthor());
-               }
-               file.addSeparator();
-               file.add(createMenuItemProperties());
-               file.addSeparator();
-               file.add(exit);
-
-               bar.add(file);
-
-               JMenu edit = new JMenu(GuiReader.trans(StringIdGui.MENU_EDIT));
-               edit.setMnemonic(KeyEvent.VK_E);
-
-               edit.add(createMenuItemSetCoverForSource());
-               edit.add(createMenuItemSetCoverForAuthor());
-               edit.add(createMenuItemDownloadToCache());
-               edit.add(createMenuItemClearCache());
-               edit.add(createMenuItemRedownload());
-               edit.addSeparator();
-               edit.add(createMenuItemDelete());
-
-               bar.add(edit);
-
-               JMenu search = new JMenu(GuiReader.trans(StringIdGui.MENU_SEARCH));
-               search.setMnemonic(KeyEvent.VK_H);
-               for (final SupportType type : SupportType.values()) {
-                       BasicSearchable searchable = BasicSearchable.getSearchable(type);
-                       if (searchable != null) {
-                               JMenuItem searchItem = new JMenuItem(type.getSourceName());
-                               searchItem.addActionListener(new ActionListener() {
-                                       @Override
-                                       public void actionPerformed(ActionEvent e) {
-                                               reader.search(type, null, 1, 0, false);
-                                       }
-                               });
-                               search.add(searchItem);
-                       }
-               }
-
-               bar.add(search);
-
-               JMenu view = new JMenu(GuiReader.trans(StringIdGui.MENU_VIEW));
-               view.setMnemonic(KeyEvent.VK_V);
-               JMenuItem vauthors = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_VIEW_AUTHOR));
-               vauthors.setMnemonic(KeyEvent.VK_A);
-               vauthors.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               mainPanel.setWords(false);
-                               mainPanel.refreshBooks();
-                       }
-               });
-               view.add(vauthors);
-               JMenuItem vwords = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_VIEW_WCOUNT));
-               vwords.setMnemonic(KeyEvent.VK_W);
-               vwords.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               mainPanel.setWords(true);
-                               mainPanel.refreshBooks();
-                       }
-               });
-               view.add(vwords);
-               bar.add(view);
-
-               Map<String, List<String>> groupedSources = new HashMap<String, List<String>>();
-               if (status.isReady()) {
-                       try {
-                               groupedSources = reader.getLibrary().getSourcesGrouped();
-                       } catch (IOException e) {
-                               error(e.getLocalizedMessage(), "IOException", e);
-                       }
-               }
-               JMenu sources = new JMenu(GuiReader.trans(StringIdGui.MENU_SOURCES));
-               sources.setMnemonic(KeyEvent.VK_S);
-               populateMenuSA(sources, groupedSources, true);
-               bar.add(sources);
-
-               Map<String, List<String>> goupedAuthors = new HashMap<String, List<String>>();
-               if (status.isReady()) {
-                       try {
-                               goupedAuthors = reader.getLibrary().getAuthorsGrouped();
-                       } catch (IOException e) {
-                               error(e.getLocalizedMessage(), "IOException", e);
-                       }
-               }
-               JMenu authors = new JMenu(GuiReader.trans(StringIdGui.MENU_AUTHORS));
-               authors.setMnemonic(KeyEvent.VK_A);
-               populateMenuSA(authors, goupedAuthors, false);
-               bar.add(authors);
-
-               JMenu options = new JMenu(GuiReader.trans(StringIdGui.MENU_OPTIONS));
-               options.setMnemonic(KeyEvent.VK_O);
-               options.add(createMenuItemConfig());
-               options.add(createMenuItemUiConfig());
-               bar.add(options);
-
-               setJMenuBar(bar);
-       }
-
-       // "" = [unknown]
-       private void populateMenuSA(JMenu menu,
-                       Map<String, List<String>> groupedValues, boolean type) {
-
-               // "All" and "Listing" special items first
-               JMenuItem item = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_XXX_ALL_GROUPED));
-               item.addActionListener(getActionOpenList(type, false));
-               menu.add(item);
-               item = new JMenuItem(GuiReader.trans(StringIdGui.MENU_XXX_ALL_LISTING));
-               item.addActionListener(getActionOpenList(type, true));
-               menu.add(item);
-
-               menu.addSeparator();
-
-               for (final String value : groupedValues.keySet()) {
-                       List<String> list = groupedValues.get(value);
-                       if (type && list.size() == 1 && list.get(0).isEmpty()) {
-                               // leaf item source/type
-                               item = new JMenuItem(
-                                               value.isEmpty() ? GuiReader
-                                                               .trans(StringIdGui.MENU_AUTHORS_UNKNOWN)
-                                                               : value);
-                               item.addActionListener(getActionOpen(value, type));
-                               menu.add(item);
-                       } else {
-                               JMenu dir;
-                               if (!type && groupedValues.size() == 1) {
-                                       // only one group of authors
-                                       dir = menu;
-                               } else {
-                                       dir = new JMenu(
-                                                       value.isEmpty() ? GuiReader
-                                                                       .trans(StringIdGui.MENU_AUTHORS_UNKNOWN)
-                                                                       : value);
-                               }
-
-                               for (String sub : list) {
-                                       // " " instead of "" for the visual height
-                                       String itemName = sub;
-                                       if (itemName.isEmpty()) {
-                                               itemName = type ? " " : GuiReader
-                                                               .trans(StringIdGui.MENU_AUTHORS_UNKNOWN);
-                                       }
-
-                                       String actualValue = value;
-                                       if (type) {
-                                               if (!sub.isEmpty()) {
-                                                       actualValue += "/" + sub;
-                                               }
-                                       } else {
-                                               actualValue = sub;
-                                       }
-
-                                       item = new JMenuItem(itemName);
-                                       item.addActionListener(getActionOpen(actualValue, type));
-                                       dir.add(item);
-                               }
-
-                               if (menu != dir) {
-                                       menu.add(dir);
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Return an {@link ActionListener} that will set the given source (type) as
-        * the selected/displayed one.
-        * 
-        * @param type
-        *            the type (source) to select, cannot be NULL
-        * 
-        * @return the {@link ActionListener}
-        */
-       private ActionListener getActionOpen(final String source, final boolean type) {
-               return new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               mainPanel.removeBookPanes();
-                               mainPanel.addBookPane(source, type);
-                               mainPanel.refreshBooks();
-                       }
-               };
-       }
-
-       private ActionListener getActionOpenList(final boolean type,
-                       final boolean listMode) {
-               return new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent ae) {
-                               mainPanel.removeBookPanes();
-                               try {
-                                       mainPanel.addBookPane(type, listMode);
-                               } catch (IOException e) {
-                                       error(e.getLocalizedMessage(), "IOException", e);
-                               }
-                               mainPanel.refreshBooks();
-                       }
-               };
-       }
-
-       /**
-        * Create the Fanfix Configuration menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemConfig() {
-               final String title = GuiReader.trans(StringIdGui.TITLE_CONFIG);
-               JMenuItem item = new JMenuItem(title);
-               item.setMnemonic(KeyEvent.VK_F);
-
-               item.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               ConfigEditor<Config> ed = new ConfigEditor<Config>(
-                                               Config.class, Instance.getInstance().getConfig(), GuiReader.trans(StringIdGui.SUBTITLE_CONFIG));
-                               JFrame frame = new JFrame(title);
-                               frame.add(ed);
-                               frame.setSize(850, 600);
-                               frame.setVisible(true);
-                       }
-               });
-
-               return item;
-       }
-
-       /**
-        * Create the UI Configuration menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemUiConfig() {
-               final String title = GuiReader.trans(StringIdGui.TITLE_CONFIG_UI);
-               JMenuItem item = new JMenuItem(title);
-               item.setMnemonic(KeyEvent.VK_U);
-
-               item.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               ConfigEditor<UiConfig> ed = new ConfigEditor<UiConfig>(
-                                               UiConfig.class, Instance.getInstance().getUiConfig(),
-                                               GuiReader.trans(StringIdGui.SUBTITLE_CONFIG_UI));
-                               JFrame frame = new JFrame(title);
-                               frame.add(ed);
-                               frame.setSize(800, 600);
-                               frame.setVisible(true);
-                       }
-               });
-
-               return item;
-       }
-
-       /**
-        * Create the export menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemExport() {
-               final JFileChooser fc = new JFileChooser();
-               fc.setAcceptAllFileFilterUsed(false);
-
-               // Add the "ALL" filters first, then the others
-               final Map<FileFilter, OutputType> otherFilters = new HashMap<FileFilter, OutputType>();
-               for (OutputType type : OutputType.values()) {
-                       String ext = type.getDefaultExtension(false);
-                       String desc = type.getDesc(false);
-
-                       if (ext == null || ext.isEmpty()) {
-                               fc.addChoosableFileFilter(createAllFilter(desc));
-                       } else {
-                               otherFilters.put(new FileNameExtensionFilter(desc, ext), type);
-                       }
-               }
-
-               for (Entry<FileFilter, OutputType> entry : otherFilters.entrySet()) {
-                       fc.addChoosableFileFilter(entry.getKey());
-               }
-               //
-
-               JMenuItem export = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_FILE_EXPORT), KeyEvent.VK_S);
-               export.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null) {
-                                       fc.showDialog(GuiReaderFrame.this,
-                                                       GuiReader.trans(StringIdGui.TITLE_SAVE));
-                                       if (fc.getSelectedFile() != null) {
-                                               final OutputType type = otherFilters.get(fc
-                                                               .getFileFilter());
-                                               final String path = fc.getSelectedFile()
-                                                               .getAbsolutePath()
-                                                               + type.getDefaultExtension(false);
-                                               final Progress pg = new Progress();
-                                               mainPanel.outOfUi(pg, false, new Runnable() {
-                                                       @Override
-                                                       public void run() {
-                                                               try {
-                                                                       reader.getLibrary().export(
-                                                                                       selectedBook.getInfo().getMeta()
-                                                                                                       .getLuid(), type, path, pg);
-                                                               } catch (IOException e) {
-                                                                       Instance.getInstance().getTraceHandler().error(e);
-                                                               }
-                                                       }
-                                               });
-                                       }
-                               }
-                       }
-               });
-
-               return export;
-       }
-
-       /**
-        * Create a {@link FileFilter} that accepts all files and return the given
-        * description.
-        * 
-        * @param desc
-        *            the description
-        * 
-        * @return the filter
-        */
-       private FileFilter createAllFilter(final String desc) {
-               return new FileFilter() {
-                       @Override
-                       public String getDescription() {
-                               return desc;
-                       }
-
-                       @Override
-                       public boolean accept(File f) {
-                               return true;
-                       }
-               };
-       }
-
-       /**
-        * Create the refresh (delete cache) menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemClearCache() {
-               JMenuItem refresh = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_EDIT_CLEAR_CACHE),
-                               KeyEvent.VK_C);
-               refresh.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null) {
-                                       mainPanel.outOfUi(null, false, new Runnable() {
-                                               @Override
-                                               public void run() {
-                                                       reader.clearLocalReaderCache(selectedBook.getInfo()
-                                                                       .getMeta().getLuid());
-                                                       selectedBook.setCached(false);
-                                                       GuiReaderCoverImager.clearIcon(selectedBook
-                                                                       .getInfo());
-                                                       SwingUtilities.invokeLater(new Runnable() {
-                                                               @Override
-                                                               public void run() {
-                                                                       selectedBook.repaint();
-                                                               }
-                                                       });
-                                               }
-                                       });
-                               }
-                       }
-               });
-
-               return refresh;
-       }
-
-       /**
-        * Create the "move to" menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemMoveTo() {
-               JMenu changeTo = new JMenu(
-                               GuiReader.trans(StringIdGui.MENU_FILE_MOVE_TO));
-               changeTo.setMnemonic(KeyEvent.VK_M);
-
-               Map<String, List<String>> groupedSources = new HashMap<String, List<String>>();
-               try {
-                       groupedSources = reader.getLibrary().getSourcesGrouped();
-               } catch (IOException e) {
-                       error(e.getLocalizedMessage(), "IOException", e);
-               }
-
-               JMenuItem item = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_TYPE));
-               item.addActionListener(createMoveAction(ChangeAction.SOURCE, null));
-               changeTo.add(item);
-               changeTo.addSeparator();
-
-               for (final String type : groupedSources.keySet()) {
-                       List<String> list = groupedSources.get(type);
-                       if (list.size() == 1 && list.get(0).isEmpty()) {
-                               item = new JMenuItem(type);
-                               item.addActionListener(createMoveAction(ChangeAction.SOURCE,
-                                               type));
-                               changeTo.add(item);
-                       } else {
-                               JMenu dir = new JMenu(type);
-                               for (String sub : list) {
-                                       // " " instead of "" for the visual height
-                                       String itemName = sub.isEmpty() ? " " : sub;
-                                       String actualType = type;
-                                       if (!sub.isEmpty()) {
-                                               actualType += "/" + sub;
-                                       }
-
-                                       item = new JMenuItem(itemName);
-                                       item.addActionListener(createMoveAction(
-                                                       ChangeAction.SOURCE, actualType));
-                                       dir.add(item);
-                               }
-                               changeTo.add(dir);
-                       }
-               }
-
-               return changeTo;
-       }
-
-       /**
-        * Create the "set author" menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemSetAuthor() {
-               JMenu changeTo = new JMenu(
-                               GuiReader.trans(StringIdGui.MENU_FILE_SET_AUTHOR));
-               changeTo.setMnemonic(KeyEvent.VK_A);
-
-               // New author
-               JMenuItem newItem = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_AUTHOR));
-               changeTo.add(newItem);
-               changeTo.addSeparator();
-               newItem.addActionListener(createMoveAction(ChangeAction.AUTHOR, null));
-
-               // Existing authors
-               Map<String, List<String>> groupedAuthors;
-
-               try {
-                       groupedAuthors = reader.getLibrary().getAuthorsGrouped();
-               } catch (IOException e) {
-                       error(e.getLocalizedMessage(), "IOException", e);
-                       groupedAuthors = new HashMap<String, List<String>>();
-
-               }
-
-               if (groupedAuthors.size() > 1) {
-                       for (String key : groupedAuthors.keySet()) {
-                               JMenu group = new JMenu(key);
-                               for (String value : groupedAuthors.get(key)) {
-                                       JMenuItem item = new JMenuItem(
-                                                       value.isEmpty() ? GuiReader
-                                                                       .trans(StringIdGui.MENU_AUTHORS_UNKNOWN)
-                                                                       : value);
-                                       item.addActionListener(createMoveAction(
-                                                       ChangeAction.AUTHOR, value));
-                                       group.add(item);
-                               }
-                               changeTo.add(group);
-                       }
-               } else if (groupedAuthors.size() == 1) {
-                       for (String value : groupedAuthors.values().iterator().next()) {
-                               JMenuItem item = new JMenuItem(
-                                               value.isEmpty() ? GuiReader
-                                                               .trans(StringIdGui.MENU_AUTHORS_UNKNOWN)
-                                                               : value);
-                               item.addActionListener(createMoveAction(ChangeAction.AUTHOR,
-                                               value));
-                               changeTo.add(item);
-                       }
-               }
-
-               return changeTo;
-       }
-
-       /**
-        * Create the "rename" menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemRename() {
-               JMenuItem changeTo = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_FILE_RENAME));
-               changeTo.setMnemonic(KeyEvent.VK_R);
-               changeTo.addActionListener(createMoveAction(ChangeAction.TITLE, null));
-               return changeTo;
-       }
-
-       private ActionListener createMoveAction(final ChangeAction what,
-                       final String type) {
-               return new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null) {
-                                       boolean refreshRequired = false;
-
-                                       if (what == ChangeAction.SOURCE) {
-                                               refreshRequired = mainPanel.getCurrentType();
-                                       } else if (what == ChangeAction.TITLE) {
-                                               refreshRequired = false;
-                                       } else if (what == ChangeAction.AUTHOR) {
-                                               refreshRequired = !mainPanel.getCurrentType();
-                                       }
-
-                                       String changeTo = type;
-                                       if (type == null) {
-                                               MetaData meta = selectedBook.getInfo().getMeta();
-                                               String init = "";
-                                               if (what == ChangeAction.SOURCE) {
-                                                       init = meta.getSource();
-                                               } else if (what == ChangeAction.TITLE) {
-                                                       init = meta.getTitle();
-                                               } else if (what == ChangeAction.AUTHOR) {
-                                                       init = meta.getAuthor();
-                                               }
-
-                                               Object rep = JOptionPane.showInputDialog(
-                                                               GuiReaderFrame.this,
-                                                               GuiReader.trans(StringIdGui.SUBTITLE_MOVE_TO),
-                                                               GuiReader.trans(StringIdGui.TITLE_MOVE_TO),
-                                                               JOptionPane.QUESTION_MESSAGE, null, null, init);
-
-                                               if (rep == null) {
-                                                       return;
-                                               }
-
-                                               changeTo = rep.toString();
-                                       }
-
-                                       final String fChangeTo = changeTo;
-                                       mainPanel.outOfUi(null, refreshRequired, new Runnable() {
-                                               @Override
-                                               public void run() {
-                                                       String luid = selectedBook.getInfo().getMeta()
-                                                                       .getLuid();
-                                                       if (what == ChangeAction.SOURCE) {
-                                                               reader.changeSource(luid, fChangeTo);
-                                                       } else if (what == ChangeAction.TITLE) {
-                                                               reader.changeTitle(luid, fChangeTo);
-                                                       } else if (what == ChangeAction.AUTHOR) {
-                                                               reader.changeAuthor(luid, fChangeTo);
-                                                       }
-
-                                                       mainPanel.getSelectedBook().repaint();
-                                                       mainPanel.unsetSelectedBook();
-
-                                                       SwingUtilities.invokeLater(new Runnable() {
-                                                               @Override
-                                                               public void run() {
-                                                                       createMenu(reader.getLibrary().getStatus());
-                                                               }
-                                                       });
-                                               }
-                                       });
-                               }
-                       }
-               };
-       }
-
-       /**
-        * Create the re-download (then delete original) menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemRedownload() {
-               JMenuItem refresh = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_EDIT_REDOWNLOAD),
-                               KeyEvent.VK_R);
-               refresh.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null) {
-                                       final MetaData meta = selectedBook.getInfo().getMeta();
-                                       mainPanel.imprt(meta.getUrl(), new MetaDataRunnable() {
-                                               @Override
-                                               public void run(MetaData newMeta) {
-                                                       if (!newMeta.getSource().equals(meta.getSource())) {
-                                                               reader.changeSource(newMeta.getLuid(),
-                                                                               meta.getSource());
-                                                       }
-                                               }
-                                       }, GuiReader.trans(StringIdGui.PROGRESS_CHANGE_SOURCE));
-                               }
-                       }
-               });
-
-               return refresh;
-       }
-       
-       /**
-        * Create the download to cache menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemDownloadToCache() {
-               JMenuItem refresh = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_EDIT_DOWNLOAD_TO_CACHE),
-                               KeyEvent.VK_T);
-               refresh.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null) {
-                                       mainPanel.prefetchBook(selectedBook);
-                               }
-                       }
-               });
-
-               return refresh;
-       }
-
-
-       /**
-        * Create the delete menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemDelete() {
-               JMenuItem delete = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_EDIT_DELETE), KeyEvent.VK_D);
-               delete.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null
-                                               && selectedBook.getInfo().getMeta() != null) {
-
-                                       final MetaData meta = selectedBook.getInfo().getMeta();
-                                       int rep = JOptionPane.showConfirmDialog(
-                                                       GuiReaderFrame.this,
-                                                       GuiReader.trans(StringIdGui.SUBTITLE_DELETE,
-                                                                       meta.getLuid(), meta.getTitle()),
-                                                       GuiReader.trans(StringIdGui.TITLE_DELETE),
-                                                       JOptionPane.OK_CANCEL_OPTION);
-
-                                       if (rep == JOptionPane.OK_OPTION) {
-                                               mainPanel.outOfUi(null, true, new Runnable() {
-                                                       @Override
-                                                       public void run() {
-                                                               reader.delete(meta.getLuid());
-                                                               mainPanel.unsetSelectedBook();
-                                                       }
-                                               });
-                                       }
-                               }
-                       }
-               });
-
-               return delete;
-       }
-
-       /**
-        * Create the properties menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemProperties() {
-               JMenuItem delete = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_FILE_PROPERTIES),
-                               KeyEvent.VK_P);
-               delete.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null) {
-                                       mainPanel.outOfUi(null, false, new Runnable() {
-                                               @Override
-                                               public void run() {
-                                                       new GuiReaderPropertiesFrame(reader.getLibrary(),
-                                                                       selectedBook.getInfo().getMeta())
-                                                                       .setVisible(true);
-                                               }
-                                       });
-                               }
-                       }
-               });
-
-               return delete;
-       }
-
-       /**
-        * Create the open menu item for a book, a source/type or an author.
-        * 
-        * @return the item
-        */
-       public JMenuItem createMenuItemOpenBook() {
-               JMenuItem open = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_FILE_OPEN), KeyEvent.VK_O);
-               open.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null) {
-                                       if (selectedBook.getInfo().getMeta() == null) {
-                                               mainPanel.removeBookPanes();
-                                               mainPanel.addBookPane(selectedBook.getInfo()
-                                                               .getMainInfo(), mainPanel.getCurrentType());
-                                               mainPanel.refreshBooks();
-                                       } else {
-                                               mainPanel.openBook(selectedBook);
-                                       }
-                               }
-                       }
-               });
-
-               return open;
-       }
-
-       /**
-        * Create the SetCover menu item for a book to change the linked source
-        * cover.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemSetCoverForSource() {
-               JMenuItem open = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_SOURCE),
-                               KeyEvent.VK_C);
-               open.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent ae) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null) {
-                                       BasicLibrary lib = reader.getLibrary();
-                                       String luid = selectedBook.getInfo().getMeta().getLuid();
-                                       String source = selectedBook.getInfo().getMeta()
-                                                       .getSource();
-
-                                       try {
-                                               lib.setSourceCover(source, luid);
-                                       } catch (IOException e) {
-                                               error(e.getLocalizedMessage(), "IOException", e);
-                                       }
-
-                                       GuiReaderBookInfo sourceInfo = GuiReaderBookInfo
-                                                       .fromSource(lib, source);
-                                       GuiReaderCoverImager.clearIcon(sourceInfo);
-                               }
-                       }
-               });
-
-               return open;
-       }
-
-       /**
-        * Create the SetCover menu item for a book to change the linked source
-        * cover.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemSetCoverForAuthor() {
-               JMenuItem open = new JMenuItem(
-                               GuiReader.trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_AUTHOR),
-                               KeyEvent.VK_A);
-               open.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent ae) {
-                               final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
-                               if (selectedBook != null) {
-                                       BasicLibrary lib = reader.getLibrary();
-                                       String luid = selectedBook.getInfo().getMeta().getLuid();
-                                       String author = selectedBook.getInfo().getMeta()
-                                                       .getAuthor();
-
-                                       try {
-                                               lib.setAuthorCover(author, luid);
-                                       } catch (IOException e) {
-                                               error(e.getLocalizedMessage(), "IOException", e);
-                                       }
-
-                                       GuiReaderBookInfo authorInfo = GuiReaderBookInfo
-                                                       .fromAuthor(lib, author);
-                                       GuiReaderCoverImager.clearIcon(authorInfo);
-                               }
-                       }
-               });
-
-               return open;
-       }
-
-       /**
-        * Display an error message and log the linked {@link Exception}.
-        * 
-        * @param message
-        *            the message
-        * @param title
-        *            the title of the error message
-        * @param e
-        *            the exception to log if any
-        */
-       public void error(final String message, final String title, Exception e) {
-               Instance.getInstance().getTraceHandler().error(title + ": " + message);
-               if (e != null) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-
-               SwingUtilities.invokeLater(new Runnable() {
-                       @Override
-                       public void run() {
-                               JOptionPane.showMessageDialog(GuiReaderFrame.this, message,
-                                               title, JOptionPane.ERROR_MESSAGE);
-                       }
-               });
-       }
-
-       @Override
-       public GuiReader getReader() {
-               return reader;
-       }
-
-       /**
-        * Return the title of the application.
-        * 
-        * @param libraryName
-        *            the name of the associated {@link BasicLibrary}, which can be
-        *            EMPTY
-        * 
-        * @return the title
-        */
-       static private String getAppTitle(String libraryName) {
-               if (!libraryName.isEmpty()) {
-                       return GuiReader.trans(StringIdGui.TITLE_LIBRARY_WITH_NAME, Version
-                                       .getCurrentVersion().toString(), libraryName);
-               }
-
-               return GuiReader.trans(StringIdGui.TITLE_LIBRARY, Version
-                               .getCurrentVersion().toString());
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderGroup.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderGroup.java
deleted file mode 100644 (file)
index cc3f1e1..0000000
+++ /dev/null
@@ -1,476 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.Graphics;
-import java.awt.Rectangle;
-import java.awt.event.ActionListener;
-import java.awt.event.ComponentAdapter;
-import java.awt.event.ComponentEvent;
-import java.awt.event.FocusAdapter;
-import java.awt.event.FocusEvent;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener;
-import be.nikiroo.utils.ui.WrapLayout;
-
-/**
- * A group of {@link GuiReaderBook}s for display.
- * 
- * @author niki
- */
-public class GuiReaderGroup extends JPanel {
-       private static final long serialVersionUID = 1L;
-       private BookActionListener action;
-       private Color backgroundColor;
-       private Color backgroundColorDef;
-       private Color backgroundColorDefPane;
-       private GuiReader reader;
-       private List<GuiReaderBookInfo> infos;
-       private List<GuiReaderBook> books;
-       private JPanel pane;
-       private JLabel titleLabel;
-       private boolean words; // words or authors (secondary info on books)
-       private int itemsPerLine;
-
-       /**
-        * Create a new {@link GuiReaderGroup}.
-        * 
-        * @param reader
-        *            the {@link GuiReaderBook} used to probe some information about
-        *            the stories
-        * @param title
-        *            the title of this group (can be NULL for "no title", an empty
-        *            {@link String} will trigger a default title for empty groups)
-        * @param backgroundColor
-        *            the background colour to use (or NULL for default)
-        */
-       public GuiReaderGroup(GuiReader reader, String title, Color backgroundColor) {
-               this.reader = reader;
-
-               this.pane = new JPanel();
-               pane.setLayout(new WrapLayout(WrapLayout.LEADING, 5, 5));
-
-               this.backgroundColorDef = getBackground();
-               this.backgroundColorDefPane = pane.getBackground();
-               setBackground(backgroundColor);
-
-               setLayout(new BorderLayout(0, 10));
-
-               // Make it focusable:
-               setFocusable(true);
-               setEnabled(true);
-               setVisible(true);
-
-               add(pane, BorderLayout.CENTER);
-
-               titleLabel = new JLabel();
-               titleLabel.setHorizontalAlignment(JLabel.CENTER);
-               add(titleLabel, BorderLayout.NORTH);
-               setTitle(title);
-
-               // Compute the number of items per line at each resize
-               addComponentListener(new ComponentAdapter() {
-                       @Override
-                       public void componentResized(ComponentEvent e) {
-                               super.componentResized(e);
-                               computeItemsPerLine();
-                       }
-               });
-               computeItemsPerLine();
-
-               addKeyListener(new KeyAdapter() {
-                       @Override
-                       public void keyPressed(KeyEvent e) {
-                               onKeyPressed(e);
-                       }
-
-                       @Override
-                       public void keyTyped(KeyEvent e) {
-                               onKeyTyped(e);
-                       }
-               });
-
-               addFocusListener(new FocusAdapter() {
-                       @Override
-                       public void focusGained(FocusEvent e) {
-                               if (getSelectedBookIndex() < 0) {
-                                       setSelectedBook(0, true);
-                               }
-                       }
-
-                       @Override
-                       public void focusLost(FocusEvent e) {
-                               setBackground(null);
-                               setSelectedBook(-1, false);
-                       }
-               });
-       }
-
-       /**
-        * Note: this class supports NULL as a background colour, which will revert
-        * it to its default state.
-        * <p>
-        * Note: this class' implementation will also set the main pane background
-        * colour at the same time.
-        * <p>
-        * Sets the background colour of this component. The background colour is
-        * used only if the component is opaque, and only by subclasses of
-        * <code>JComponent</code> or <code>ComponentUI</code> implementations.
-        * Direct subclasses of <code>JComponent</code> must override
-        * <code>paintComponent</code> to honour this property.
-        * <p>
-        * It is up to the look and feel to honour this property, some may choose to
-        * ignore it.
-        * 
-        * @param backgroundColor
-        *            the desired background <code>Colour</code>
-        * @see java.awt.Component#getBackground
-        * @see #setOpaque
-        * 
-        * @beaninfo preferred: true bound: true attribute: visualUpdate true
-        *           description: The background colour of the component.
-        */
-       @Override
-       public void setBackground(Color backgroundColor) {
-               this.backgroundColor = backgroundColor;
-               
-               Color cme = backgroundColor == null ? backgroundColorDef
-                               : backgroundColor;
-               Color cpane = backgroundColor == null ? backgroundColorDefPane
-                               : backgroundColor;
-
-               if (pane != null) { // can happen at theme setup time
-                       pane.setBackground(cpane);
-               }
-               super.setBackground(cme);
-       }
-
-       /**
-        * The title of this group (can be NULL for "no title", an empty
-        * {@link String} will trigger a default title for empty groups)
-        * 
-        * @param title
-        *            the title or NULL
-        */
-       public void setTitle(String title) {
-               if (title != null) {
-                       if (title.isEmpty()) {
-                               title = GuiReader.trans(StringIdGui.MENU_AUTHORS_UNKNOWN);
-                       }
-
-                       titleLabel.setText(String.format("<html>"
-                                       + "<body style='text-align: center; color: gray;'><br><b>"
-                                       + "%s" + "</b></body>" + "</html>", title));
-                       titleLabel.setVisible(true);
-               } else {
-                       titleLabel.setVisible(false);
-               }
-       }
-
-       /**
-        * Compute how many items can fit in a line so UP and DOWN can be used to go
-        * up/down one line at a time.
-        */
-       private void computeItemsPerLine() {
-               itemsPerLine = 1;
-
-               if (books != null && books.size() > 0) {
-                       // this.pane holds all the books with a hgap of 5 px
-                       int wbook = books.get(0).getWidth() + 5;
-                       itemsPerLine = pane.getWidth() / wbook;
-               }
-       }
-
-       /**
-        * Set the {@link ActionListener} that will be fired on each
-        * {@link GuiReaderBook} action.
-        * 
-        * @param action
-        *            the action
-        */
-       public void setActionListener(BookActionListener action) {
-               this.action = action;
-               refreshBooks();
-       }
-
-       /**
-        * Clear all the books in this {@link GuiReaderGroup}.
-        */
-       public void clear() {
-               refreshBooks(new ArrayList<GuiReaderBookInfo>());
-       }
-
-       /**
-        * Refresh the list of {@link GuiReaderBook}s displayed in the control.
-        */
-       public void refreshBooks() {
-               refreshBooks(infos, words);
-       }
-
-       /**
-        * Refresh the list of {@link GuiReaderBook}s displayed in the control.
-        * 
-        * @param infos
-        *            the new list of infos
-        */
-       public void refreshBooks(List<GuiReaderBookInfo> infos) {
-               refreshBooks(infos, words);
-       }
-
-       /**
-        * Refresh the list of {@link GuiReaderBook}s displayed in the control.
-        * 
-        * @param infos
-        *            the new list of infos
-        * @param seeWordcount
-        *            TRUE to see word counts, FALSE to see authors
-        */
-       public void refreshBooks(List<GuiReaderBookInfo> infos, boolean seeWordcount) {
-               this.infos = infos;
-               refreshBooks(seeWordcount);
-       }
-
-       /**
-        * Refresh the list of {@link GuiReaderBook}s displayed in the control.
-        * <p>
-        * Will not change the current stories.
-        * 
-        * @param seeWordcount
-        *            TRUE to see word counts, FALSE to see authors
-        */
-       public void refreshBooks(boolean seeWordcount) {
-               this.words = seeWordcount;
-
-               books = new ArrayList<GuiReaderBook>();
-               invalidate();
-               pane.invalidate();
-               pane.removeAll();
-
-               if (infos != null) {
-                       for (GuiReaderBookInfo info : infos) {
-                               boolean isCached = false;
-                               if (info.getMeta() != null && info.getMeta().getLuid() != null) {
-                                       isCached = reader.isCached(info.getMeta().getLuid());
-                               }
-
-                               GuiReaderBook book = new GuiReaderBook(reader, info, isCached,
-                                               words);
-                               if (backgroundColor != null) {
-                                       book.setBackground(backgroundColor);
-                               }
-
-                               books.add(book);
-
-                               book.addActionListener(new BookActionListener() {
-                                       @Override
-                                       public void select(GuiReaderBook book) {
-                                               GuiReaderGroup.this.requestFocusInWindow();
-                                               for (GuiReaderBook abook : books) {
-                                                       abook.setSelected(abook == book);
-                                               }
-                                       }
-
-                                       @Override
-                                       public void popupRequested(GuiReaderBook book,
-                                                       Component target, int x, int y) {
-                                       }
-
-                                       @Override
-                                       public void action(GuiReaderBook book) {
-                                       }
-                               });
-
-                               if (action != null) {
-                                       book.addActionListener(action);
-                               }
-
-                               pane.add(book);
-                       }
-               }
-
-               pane.validate();
-               pane.repaint();
-               validate();
-               repaint();
-
-               computeItemsPerLine();
-       }
-
-       /**
-        * Enables or disables this component, depending on the value of the
-        * parameter <code>b</code>. An enabled component can respond to user input
-        * and generate events. Components are enabled initially by default.
-        * <p>
-        * Disabling this component will also affect its children.
-        * 
-        * @param b
-        *            If <code>true</code>, this component is enabled; otherwise
-        *            this component is disabled
-        */
-       @Override
-       public void setEnabled(boolean b) {
-               if (books != null) {
-                       for (GuiReaderBook book : books) {
-                               book.setEnabled(b);
-                               book.repaint();
-                       }
-               }
-
-               pane.setEnabled(b);
-               super.setEnabled(b);
-               repaint();
-       }
-
-       /**
-        * The number of books in this group.
-        * 
-        * @return the count
-        */
-       public int getBooksCount() {
-               return books.size();
-       }
-
-       /**
-        * Return the index of the currently selected book if any, -1 if none.
-        * 
-        * @return the index or -1
-        */
-       public int getSelectedBookIndex() {
-               int index = -1;
-               for (int i = 0; i < books.size(); i++) {
-                       if (books.get(i).isSelected()) {
-                               index = i;
-                               break;
-                       }
-               }
-               return index;
-       }
-
-       /**
-        * Select the given book, or unselect all items.
-        * 
-        * @param index
-        *            the index of the book to select, can be outside the bounds
-        *            (either all the items will be unselected or the first or last
-        *            book will then be selected, see <tt>forceRange></tt>)
-        * @param forceRange
-        *            TRUE to constraint the index to the first/last element, FALSE
-        *            to unselect when outside the range
-        */
-       public void setSelectedBook(int index, boolean forceRange) {
-               int previousIndex = getSelectedBookIndex();
-
-               if (index >= books.size()) {
-                       if (forceRange) {
-                               index = books.size() - 1;
-                       } else {
-                               index = -1;
-                       }
-               }
-
-               if (index < 0 && forceRange) {
-                       index = 0;
-               }
-
-               if (previousIndex >= 0) {
-                       books.get(previousIndex).setSelected(false);
-               }
-
-               if (index >= 0 && !books.isEmpty()) {
-                       books.get(index).setSelected(true);
-               }
-       }
-
-       /**
-        * The action to execute when a key is typed.
-        * 
-        * @param e
-        *            the key event
-        */
-       private void onKeyTyped(KeyEvent e) {
-               boolean consumed = false;
-               boolean action = e.getKeyChar() == '\n';
-               boolean popup = e.getKeyChar() == ' ';
-               if (action || popup) {
-                       consumed = true;
-
-                       int index = getSelectedBookIndex();
-                       if (index >= 0) {
-                               GuiReaderBook book = books.get(index);
-                               if (action) {
-                                       book.action();
-                               } else if (popup) {
-                                       book.popup(book, book.getWidth() / 2, book.getHeight() / 2);
-                               }
-                       }
-               }
-
-               if (consumed) {
-                       e.consume();
-               }
-       }
-
-       /**
-        * The action to execute when a key is pressed.
-        * 
-        * @param e
-        *            the key event
-        */
-       private void onKeyPressed(KeyEvent e) {
-               boolean consumed = false;
-               if (e.isActionKey()) {
-                       int offset = 0;
-                       switch (e.getKeyCode()) {
-                       case KeyEvent.VK_LEFT:
-                               offset = -1;
-                               break;
-                       case KeyEvent.VK_RIGHT:
-                               offset = 1;
-                               break;
-                       case KeyEvent.VK_UP:
-                               offset = -itemsPerLine;
-                               break;
-                       case KeyEvent.VK_DOWN:
-                               offset = itemsPerLine;
-                               break;
-                       }
-
-                       if (offset != 0) {
-                               consumed = true;
-
-                               int previousIndex = getSelectedBookIndex();
-                               if (previousIndex >= 0) {
-                                       setSelectedBook(previousIndex + offset, true);
-                               }
-                       }
-               }
-
-               if (consumed) {
-                       e.consume();
-               }
-       }
-
-       @Override
-       public void paint(Graphics g) {
-               super.paint(g);
-
-               Rectangle clip = g.getClipBounds();
-               if (clip.getWidth() <= 0 || clip.getHeight() <= 0) {
-                       return;
-               }
-
-               if (!isEnabled()) {
-                       g.setColor(new Color(128, 128, 128, 128));
-                       g.fillRect(clip.x, clip.y, clip.width, clip.height);
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java
deleted file mode 100644 (file)
index a5eb691..0000000
+++ /dev/null
@@ -1,790 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.EventQueue;
-import java.awt.Frame;
-import java.awt.Toolkit;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.FocusAdapter;
-import java.awt.event.FocusEvent;
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.InvocationTargetException;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.swing.BoxLayout;
-import javax.swing.JFileChooser;
-import javax.swing.JLabel;
-import javax.swing.JMenuBar;
-import javax.swing.JOptionPane;
-import javax.swing.JPanel;
-import javax.swing.JPopupMenu;
-import javax.swing.JScrollPane;
-import javax.swing.SwingConstants;
-import javax.swing.SwingUtilities;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.bundles.UiConfig;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.fanfix.library.BasicLibrary.Status;
-import be.nikiroo.fanfix.library.LocalLibrary;
-import be.nikiroo.fanfix.reader.BasicReader;
-import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener;
-import be.nikiroo.fanfix.reader.ui.GuiReaderBookInfo.Type;
-import be.nikiroo.utils.Progress;
-import be.nikiroo.utils.ui.ProgressBar;
-
-/**
- * A {@link Frame} that will show a {@link GuiReaderBook} item for each
- * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
- * way to copy them to the {@link GuiReader} cache (
- * {@link BasicReader#getLibrary()}), read them, delete them...
- * 
- * @author niki
- */
-class GuiReaderMainPanel extends JPanel {
-       private static final long serialVersionUID = 1L;
-       private FrameHelper helper;
-       private Map<String, GuiReaderGroup> books;
-       private GuiReaderGroup bookPane; // for more "All"
-       private JPanel pane;
-       private Color color;
-       private ProgressBar pgBar;
-       private JMenuBar bar;
-       private GuiReaderBook selectedBook;
-       private boolean words; // words or authors (secondary info on books)
-       private boolean currentType; // type/source or author mode (All and Listing)
-
-       /**
-        * An object that offers some helper methods to access the frame that host
-        * it and the Fanfix-related functions.
-        * 
-        * @author niki
-        */
-       public interface FrameHelper {
-               /**
-                * Return the reader associated to this {@link FrameHelper}.
-                * 
-                * @return the reader
-                */
-               public GuiReader getReader();
-
-               /**
-                * Create the main menu bar.
-                * <p>
-                * Will invalidate the layout.
-                * 
-                * @param status
-                *            the library status, <b>must not</b> be NULL
-                */
-               public void createMenu(Status status);
-
-               /**
-                * Create a popup menu for a {@link GuiReaderBook} that represents a
-                * story.
-                * 
-                * @return the popup menu to display
-                */
-               public JPopupMenu createBookPopup();
-
-               /**
-                * Create a popup menu for a {@link GuiReaderBook} that represents a
-                * source/type or an author.
-                * 
-                * @return the popup menu to display
-                */
-               public JPopupMenu createSourceAuthorPopup();
-       }
-
-       /**
-        * A {@link Runnable} with a {@link MetaData} parameter.
-        * 
-        * @author niki
-        */
-       public interface MetaDataRunnable {
-               /**
-                * Run the action.
-                * 
-                * @param meta
-                *            the meta of the story
-                */
-               public void run(MetaData meta);
-       }
-
-       /**
-        * Create a new {@link GuiReaderMainPanel}.
-        * 
-        * @param parent
-        *            the associated {@link FrameHelper} to forward some commands
-        *            and access its {@link LocalLibrary}
-        * @param type
-        *            the type of {@link Story} to load, or NULL for all types
-        */
-       public GuiReaderMainPanel(FrameHelper parent, String type) {
-               super(new BorderLayout(), true);
-
-               this.helper = parent;
-
-               pane = new JPanel();
-               pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
-               JScrollPane scroll = new JScrollPane(pane);
-
-               Integer icolor = Instance.getInstance().getUiConfig().getColor(UiConfig.BACKGROUND_COLOR);
-               if (icolor != null) {
-                       color = new Color(icolor);
-                       setBackground(color);
-                       pane.setBackground(color);
-                       scroll.setBackground(color);
-               }
-
-               scroll.getVerticalScrollBar().setUnitIncrement(16);
-               add(scroll, BorderLayout.CENTER);
-
-               String message = parent.getReader().getLibrary().getLibraryName();
-               if (!message.isEmpty()) {
-                       JLabel name = new JLabel(message, SwingConstants.CENTER);
-                       add(name, BorderLayout.NORTH);
-               }
-
-               pgBar = new ProgressBar();
-               add(pgBar, BorderLayout.SOUTH);
-
-               pgBar.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               pgBar.invalidate();
-                               pgBar.setProgress(null);
-                               setEnabled(true);
-                               validate();
-                       }
-               });
-
-               pgBar.addUpdateListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               pgBar.invalidate();
-                               validate();
-                               repaint();
-                       }
-               });
-
-               books = new TreeMap<String, GuiReaderGroup>();
-
-               addFocusListener(new FocusAdapter() {
-                       @Override
-                       public void focusGained(FocusEvent e) {
-                               focus();
-                       }
-               });
-
-               pane.setVisible(false);
-               final Progress pg = new Progress();
-               final String typeF = type;
-               outOfUi(pg, true, new Runnable() {
-                       @Override
-                       public void run() {
-                               final BasicLibrary lib = helper.getReader().getLibrary();
-                               final Status status = lib.getStatus();
-
-                               if (status == Status.READ_WRITE) {
-                                       lib.refresh(pg);
-                               }
-
-                               inUi(new Runnable() {
-                                       @Override
-                                       public void run() {
-                                               if (status.isReady()) {
-                                                       helper.createMenu(status);
-                                                       pane.setVisible(true);
-                                                       if (typeF == null) {
-                                                               try {
-                                                                       addBookPane(true, false);
-                                                               } catch (IOException e) {
-                                                                       error(e.getLocalizedMessage(),
-                                                                                       "IOException", e);
-                                                               }
-                                                       } else {
-                                                               addBookPane(typeF, true);
-                                                       }
-                                               } else {
-                                                       helper.createMenu(status);
-                                                       validate();
-
-                                                       String desc = Instance.getInstance().getTransGui().getStringX(StringIdGui.ERROR_LIB_STATUS,
-                                                                       status.toString());
-                                                       if (desc == null) {
-                                                               desc = GuiReader
-                                                                               .trans(StringIdGui.ERROR_LIB_STATUS);
-                                                       }
-
-                                                       String err = lib.getLibraryName() + "\n" + desc;
-                                                       error(err, GuiReader
-                                                                       .trans(StringIdGui.TITLE_ERROR_LIBRARY),
-                                                                       null);
-                                               }
-                                       }
-                               });
-                       }
-               });
-       }
-
-       public boolean getCurrentType() {
-               return currentType;
-       }
-
-       /**
-        * Add a new {@link GuiReaderGroup} on the frame to display all the
-        * sources/types or all the authors, or a listing of all the books sorted
-        * either by source or author.
-        * <p>
-        * A display of all the sources/types or all the authors will show one icon
-        * per source/type or author.
-        * <p>
-        * A listing of all the books sorted by source/type or author will display
-        * all the books.
-        * 
-        * @param type
-        *            TRUE for type/source, FALSE for author
-        * @param listMode
-        *            TRUE to get a listing of all the sources or authors, FALSE to
-        *            get one icon per source or author
-        * 
-        * @throws IOException
-        *             in case of I/O error
-        */
-       public void addBookPane(boolean type, boolean listMode) throws IOException {
-               this.currentType = type;
-               BasicLibrary lib = helper.getReader().getLibrary();
-               if (type) {
-                       if (!listMode) {
-                               addListPane(GuiReader.trans(StringIdGui.MENU_SOURCES),
-                                               lib.getSources(), type);
-                       } else {
-                               for (String tt : lib.getSources()) {
-                                       if (tt != null) {
-                                               addBookPane(tt, type);
-                                       }
-                               }
-                       }
-               } else {
-                       if (!listMode) {
-                               addListPane(GuiReader.trans(StringIdGui.MENU_AUTHORS),
-                                               lib.getAuthors(), type);
-                       } else {
-                               for (String tt : lib.getAuthors()) {
-                                       if (tt != null) {
-                                               addBookPane(tt, type);
-                                       }
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Add a new {@link GuiReaderGroup} on the frame to display the books of the
-        * selected type or author.
-        * <p>
-        * Will invalidate the layout.
-        * 
-        * @param value
-        *            the author or the type, or NULL to get all the
-        *            authors-or-types
-        * @param type
-        *            TRUE for type/source, FALSE for author
-        */
-       public void addBookPane(String value, boolean type) {
-               this.currentType = type;
-
-               GuiReaderGroup bookPane = new GuiReaderGroup(helper.getReader(), value,
-                               color);
-
-               books.put(value, bookPane);
-
-               pane.invalidate();
-               pane.add(bookPane);
-
-               bookPane.setActionListener(new BookActionListener() {
-                       @Override
-                       public void select(GuiReaderBook book) {
-                               selectedBook = book;
-                       }
-
-                       @Override
-                       public void popupRequested(GuiReaderBook book, Component target,
-                                       int x, int y) {
-                               JPopupMenu popup = helper.createBookPopup();
-                               popup.show(target, x, y);
-                       }
-
-                       @Override
-                       public void action(final GuiReaderBook book) {
-                               openBook(book);
-                       }
-               });
-
-               focus();
-       }
-
-       /**
-        * Clear the pane from any book that may be present, usually prior to adding
-        * new ones.
-        * <p>
-        * Will invalidate the layout.
-        */
-       public void removeBookPanes() {
-               books.clear();
-               pane.invalidate();
-               pane.removeAll();
-       }
-
-       /**
-        * Refresh the list of {@link GuiReaderBook}s from disk.
-        * <p>
-        * Will validate the layout, as it is a "refresh" operation.
-        */
-       public void refreshBooks() {
-               BasicLibrary lib = helper.getReader().getLibrary();
-               for (String value : books.keySet()) {
-                       List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
-
-                       List<MetaData> metas;
-                       try {
-                               if (currentType) {
-                                       metas = lib.getList().filter(value, null, null);
-                               } else {
-                                       metas = lib.getList().filter(null, value, null);
-                               }
-                       } catch (IOException e) {
-                               error(e.getLocalizedMessage(), "IOException", e);
-                               metas = new ArrayList<MetaData>();
-                       }
-
-                       for (MetaData meta : metas) {
-                               infos.add(GuiReaderBookInfo.fromMeta(meta));
-                       }
-
-                       books.get(value).refreshBooks(infos, words);
-               }
-
-               if (bookPane != null) {
-                       bookPane.refreshBooks(words);
-               }
-
-               this.validate();
-       }
-
-       /**
-        * Open a {@link GuiReaderBook} item.
-        * 
-        * @param book
-        *            the {@link GuiReaderBook} to open
-        */
-       public void openBook(final GuiReaderBook book) {
-               final Progress pg = new Progress();
-               outOfUi(pg, false, new Runnable() {
-                       @Override
-                       public void run() {
-                               try {
-                                       helper.getReader().read(book.getInfo().getMeta().getLuid(),
-                                                       false, pg);
-                                       SwingUtilities.invokeLater(new Runnable() {
-                                               @Override
-                                               public void run() {
-                                                       book.setCached(true);
-                                               }
-                                       });
-                               } catch (IOException e) {
-                                       Instance.getInstance().getTraceHandler().error(e);
-                                       error(GuiReader.trans(StringIdGui.ERROR_CANNOT_OPEN),
-                                                       GuiReader.trans(StringIdGui.TITLE_ERROR), e);
-                               }
-                       }
-               });
-       }
-       
-       /**
-        * Prefetch a {@link GuiReaderBook} item (which can be a group, in which
-        * case we prefetch all its members).
-        * 
-        * @param book
-        *            the {@link GuiReaderBook} to open
-        */
-       public void prefetchBook(final GuiReaderBook book) {
-               final List<String> luids = new LinkedList<String>();
-               try {
-                       switch (book.getInfo().getType()) {
-                       case STORY:
-                               luids.add(book.getInfo().getMeta().getLuid());
-                               break;
-                       case SOURCE:
-                               for (MetaData meta : helper.getReader().getLibrary()
-                                               .getList().filter(book.getInfo().getMainInfo(), null, null)) {
-                                       luids.add(meta.getLuid());
-                               }
-                               break;
-                       case AUTHOR:
-                               for (MetaData meta : helper.getReader().getLibrary()
-                                               .getList().filter(null, book.getInfo().getMainInfo(), null)) {
-                                       luids.add(meta.getLuid());
-                               }
-                               break;
-                       }
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-
-               final Progress pg = new Progress();
-               pg.setMax(luids.size());
-
-               outOfUi(pg, false, new Runnable() {
-                       @Override
-                       public void run() {
-                               try {
-                                       for (String luid : luids) {
-                                               Progress pgStep = new Progress();
-                                               pg.addProgress(pgStep, 1);
-
-                                               helper.getReader().prefetch(luid, pgStep);
-                                       }
-
-                                       // TODO: also set the green button on sources/authors?
-                                       // requires to do the same when all stories inside are green
-                                       if (book.getInfo().getType() == Type.STORY) {
-                                               SwingUtilities.invokeLater(new Runnable() {
-                                                       @Override
-                                                       public void run() {
-                                                               book.setCached(true);
-                                                       }
-                                               });
-                                       }
-                               } catch (IOException e) {
-                                       Instance.getInstance().getTraceHandler().error(e);
-                                       error(GuiReader.trans(StringIdGui.ERROR_CANNOT_OPEN),
-                                                       GuiReader.trans(StringIdGui.TITLE_ERROR), e);
-                               }
-                       }
-               });
-       }
-
-       /**
-        * Process the given action out of the Swing UI thread and link the given
-        * {@link ProgressBar} to the action.
-        * <p>
-        * The code will make sure that the {@link ProgressBar} (if not NULL) is set
-        * to done when the action is done.
-        * 
-        * @param progress
-        *            the {@link ProgressBar} or NULL
-        * @param refreshBooks
-        *            TRUE to refresh the books after
-        * @param run
-        *            the action to run
-        */
-       public void outOfUi(Progress progress, final boolean refreshBooks,
-                       final Runnable run) {
-               final Progress pg = new Progress();
-               final Progress reload = new Progress(
-                               GuiReader.trans(StringIdGui.PROGRESS_OUT_OF_UI_RELOAD_BOOKS));
-
-               if (progress == null) {
-                       progress = new Progress();
-               }
-
-               if (refreshBooks) {
-                       pg.addProgress(progress, 100);
-               } else {
-                       pg.addProgress(progress, 90);
-                       pg.addProgress(reload, 10);
-               }
-
-               invalidate();
-               pgBar.setProgress(pg);
-               validate();
-               setEnabled(false);
-
-               new Thread(new Runnable() {
-                       @Override
-                       public void run() {
-                               try {
-                                       run.run();
-                                       if (refreshBooks) {
-                                               refreshBooks();
-                                       }
-                               } finally {
-                                       reload.done();
-                                       if (!pg.isDone()) {
-                                               // will trigger pgBar ActionListener:
-                                               pg.done();
-                                       }
-                               }
-                       }
-               }, "outOfUi thread").start();
-       }
-
-       /**
-        * Process the given action in the main Swing UI thread.
-        * <p>
-        * The code will make sure the current thread is the main UI thread and, if
-        * not, will switch to it before executing the runnable.
-        * <p>
-        * Synchronous operation.
-        * 
-        * @param run
-        *            the action to run
-        */
-       public void inUi(final Runnable run) {
-               if (EventQueue.isDispatchThread()) {
-                       run.run();
-               } else {
-                       try {
-                               EventQueue.invokeAndWait(run);
-                       } catch (InterruptedException e) {
-                               Instance.getInstance().getTraceHandler().error(e);
-                       } catch (InvocationTargetException e) {
-                               Instance.getInstance().getTraceHandler().error(e);
-                       }
-               }
-       }
-
-       /**
-        * Import a {@link Story} into the main {@link LocalLibrary}.
-        * <p>
-        * Should be called inside the UI thread.
-        * 
-        * @param askUrl
-        *            TRUE for an {@link URL}, false for a {@link File}
-        */
-       public void imprt(boolean askUrl) {
-               JFileChooser fc = new JFileChooser();
-
-               Object url;
-               if (askUrl) {
-                       String clipboard = "";
-                       try {
-                               clipboard = ("" + Toolkit.getDefaultToolkit()
-                                               .getSystemClipboard().getData(DataFlavor.stringFlavor))
-                                               .trim();
-                       } catch (Exception e) {
-                               // No data will be handled
-                       }
-
-                       if (clipboard == null || !(clipboard.startsWith("http://") || //
-                                       clipboard.startsWith("https://"))) {
-                               clipboard = "";
-                       }
-
-                       url = JOptionPane.showInputDialog(GuiReaderMainPanel.this,
-                                       GuiReader.trans(StringIdGui.SUBTITLE_IMPORT_URL),
-                                       GuiReader.trans(StringIdGui.TITLE_IMPORT_URL),
-                                       JOptionPane.QUESTION_MESSAGE, null, null, clipboard);
-               } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) {
-                       url = fc.getSelectedFile().getAbsolutePath();
-               } else {
-                       url = null;
-               }
-
-               if (url != null && !url.toString().isEmpty()) {
-                       imprt(url.toString(), null, null);
-               }
-       }
-
-       /**
-        * Actually import the {@link Story} into the main {@link LocalLibrary}.
-        * <p>
-        * Should be called inside the UI thread.
-        * 
-        * @param url
-        *            the {@link Story} to import by {@link URL}
-        * @param onSuccess
-        *            Action to execute on success
-        * @param onSuccessPgName
-        *            the name to use for the onSuccess progress bar
-        */
-       public void imprt(final String url, final MetaDataRunnable onSuccess,
-                       String onSuccessPgName) {
-               final Progress pg = new Progress();
-               final Progress pgImprt = new Progress();
-               final Progress pgOnSuccess = new Progress(onSuccessPgName);
-               pg.addProgress(pgImprt, 95);
-               pg.addProgress(pgOnSuccess, 5);
-
-               outOfUi(pg, true, new Runnable() {
-                       @Override
-                       public void run() {
-                               Exception ex = null;
-                               MetaData meta = null;
-                               try {
-                                       meta = helper.getReader().getLibrary()
-                                                       .imprt(BasicReader.getUrl(url), pgImprt);
-                               } catch (IOException e) {
-                                       ex = e;
-                               }
-
-                               final Exception e = ex;
-
-                               final boolean ok = (e == null);
-
-                               pgOnSuccess.setProgress(0);
-                               if (!ok) {
-                                       if (e instanceof UnknownHostException) {
-                                               error(GuiReader.trans(
-                                                               StringIdGui.ERROR_URL_NOT_SUPPORTED, url),
-                                                               GuiReader.trans(StringIdGui.TITLE_ERROR), null);
-                                       } else {
-                                               error(GuiReader.trans(
-                                                               StringIdGui.ERROR_URL_IMPORT_FAILED, url,
-                                                               e.getMessage()), GuiReader
-                                                               .trans(StringIdGui.TITLE_ERROR), e);
-                                       }
-                               } else {
-                                       if (onSuccess != null) {
-                                               onSuccess.run(meta);
-                                       }
-                               }
-                               pgOnSuccess.done();
-                       }
-               });
-       }
-
-       /**
-        * Enables or disables this component, depending on the value of the
-        * parameter <code>b</code>. An enabled component can respond to user input
-        * and generate events. Components are enabled initially by default.
-        * <p>
-        * Enabling or disabling <b>this</b> component will also affect its
-        * children.
-        * 
-        * @param b
-        *            If <code>true</code>, this component is enabled; otherwise
-        *            this component is disabled
-        */
-       @Override
-       public void setEnabled(boolean b) {
-               if (bar != null) {
-                       bar.setEnabled(b);
-               }
-
-               for (GuiReaderGroup group : books.values()) {
-                       group.setEnabled(b);
-               }
-               super.setEnabled(b);
-               repaint();
-       }
-
-       public void setWords(boolean words) {
-               this.words = words;
-       }
-
-       public GuiReaderBook getSelectedBook() {
-               return selectedBook;
-       }
-
-       public void unsetSelectedBook() {
-               selectedBook = null;
-       }
-
-       private void addListPane(String name, List<String> values,
-                       final boolean type) {
-               GuiReader reader = helper.getReader();
-               BasicLibrary lib = reader.getLibrary();
-
-               bookPane = new GuiReaderGroup(reader, name, color);
-
-               List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
-               for (String value : values) {
-                       if (type) {
-                               infos.add(GuiReaderBookInfo.fromSource(lib, value));
-                       } else {
-                               infos.add(GuiReaderBookInfo.fromAuthor(lib, value));
-                       }
-               }
-
-               bookPane.refreshBooks(infos, words);
-
-               this.invalidate();
-               pane.invalidate();
-               pane.add(bookPane);
-               pane.validate();
-               this.validate();
-
-               bookPane.setActionListener(new BookActionListener() {
-                       @Override
-                       public void select(GuiReaderBook book) {
-                               selectedBook = book;
-                       }
-
-                       @Override
-                       public void popupRequested(GuiReaderBook book, Component target,
-                                       int x, int y) {
-                               JPopupMenu popup = helper.createSourceAuthorPopup();
-                               popup.show(target, x, y);
-                       }
-
-                       @Override
-                       public void action(final GuiReaderBook book) {
-                               removeBookPanes();
-                               addBookPane(book.getInfo().getMainInfo(), type);
-                               refreshBooks();
-                       }
-               });
-
-               focus();
-       }
-
-       /**
-        * Focus the first {@link GuiReaderGroup} we find.
-        */
-       private void focus() {
-               GuiReaderGroup group = null;
-               Map<String, GuiReaderGroup> books = this.books;
-               if (books.size() > 0) {
-                       group = books.values().iterator().next();
-               }
-
-               if (group == null) {
-                       group = bookPane;
-               }
-
-               if (group != null) {
-                       group.requestFocusInWindow();
-               }
-       }
-
-       /**
-        * Display an error message and log the linked {@link Exception}.
-        * 
-        * @param message
-        *            the message
-        * @param title
-        *            the title of the error message
-        * @param e
-        *            the exception to log if any
-        */
-       private void error(final String message, final String title, Exception e) {
-               Instance.getInstance().getTraceHandler().error(title + ": " + message);
-               if (e != null) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-
-               SwingUtilities.invokeLater(new Runnable() {
-                       @Override
-                       public void run() {
-                               JOptionPane.showMessageDialog(GuiReaderMainPanel.this, message,
-                                               title, JOptionPane.ERROR_MESSAGE);
-                       }
-               });
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderNavBar.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderNavBar.java
deleted file mode 100644 (file)
index 43c1a99..0000000
+++ /dev/null
@@ -1,343 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.Color;
-import java.awt.LayoutManager;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.BoxLayout;
-import javax.swing.JButton;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-
-import be.nikiroo.fanfix.Instance;
-
-/**
- * A Swing-based navigation bar, that displays first/previous/next/last page
- * buttons.
- * 
- * @author niki
- */
-public class GuiReaderNavBar extends JPanel {
-       private static final long serialVersionUID = 1L;
-
-       private JLabel label;
-       private int index = 0;
-       private int min = 0;
-       private int max = 0;
-       private JButton[] navButtons;
-       String extraLabel = null;
-
-       private List<ActionListener> listeners = new ArrayList<ActionListener>();
-
-       /**
-        * Create a new navigation bar.
-        * <p>
-        * The minimum must be lower or equal to the maximum.
-        * <p>
-        * Note than a max of "-1" means "infinite".
-        * 
-        * @param min
-        *            the minimum page number (cannot be negative)
-        * @param max
-        *            the maximum page number (cannot be lower than min, except if
-        *            -1 (infinite))
-        * 
-        * @throws IndexOutOfBoundsException
-        *             if min &gt; max and max is not "-1"
-        */
-       public GuiReaderNavBar(int min, int max) {
-               if (min > max && max != -1) {
-                       throw new IndexOutOfBoundsException(String.format(
-                                       "min (%d) > max (%d)", min, max));
-               }
-
-               LayoutManager layout = new BoxLayout(this, BoxLayout.X_AXIS);
-               setLayout(layout);
-
-               // TODO:
-               // JButton up = new BasicArrowButton(BasicArrowButton.NORTH);
-               // JButton down = new BasicArrowButton(BasicArrowButton.SOUTH);
-
-               navButtons = new JButton[4];
-
-               navButtons[0] = createNavButton("<<", new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               setIndex(GuiReaderNavBar.this.min);
-                               fireEvent();
-                       }
-               });
-               navButtons[1] = createNavButton(" < ", new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               setIndex(index - 1);
-                               fireEvent();
-                       }
-               });
-               navButtons[2] = createNavButton(" > ", new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               setIndex(index + 1);
-                               fireEvent();
-                       }
-               });
-               navButtons[3] = createNavButton(">>", new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               setIndex(GuiReaderNavBar.this.max);
-                               fireEvent();
-                       }
-               });
-
-               for (JButton navButton : navButtons) {
-                       add(navButton);
-               }
-
-               label = new JLabel("");
-               add(label);
-
-               this.min = min;
-               this.max = max;
-               this.index = min;
-
-               updateEnabled();
-               updateLabel();
-               fireEvent();
-       }
-
-       /**
-        * The current index, must be between {@link GuiReaderNavBar#min} and
-        * {@link GuiReaderNavBar#max}, both inclusive.
-        * 
-        * @return the index
-        */
-       public int getIndex() {
-               return index;
-       }
-
-       /**
-        * The current index, must be between {@link GuiReaderNavBar#min} and
-        * {@link GuiReaderNavBar#max}, both inclusive.
-        * 
-        * @param index
-        *            the new index
-        */
-       public void setIndex(int index) {
-               if (index != this.index) {
-                       if (index < min || (index > max && max != -1)) {
-                               throw new IndexOutOfBoundsException(String.format(
-                                               "Index %d but min/max is [%d/%d]", index, min, max));
-                       }
-
-                       this.index = index;
-                       updateLabel();
-               }
-
-               updateEnabled();
-       }
-
-       /**
-        * The minimun page number. Cannot be negative.
-        * 
-        * @return the min
-        */
-       public int getMin() {
-               return min;
-       }
-
-       /**
-        * The minimum page number. Cannot be negative.
-        * <p>
-        * May update the index if needed (if the index is &lt; the new min).
-        * <p>
-        * Will also (always) update the label and enable/disable the required
-        * buttons.
-        * 
-        * @param min
-        *            the new min
-        */
-       public void setMin(int min) {
-               this.min = min;
-               if (index < min) {
-                       index = min;
-               }
-               updateEnabled();
-               updateLabel();
-
-       }
-
-       /**
-        * The maximum page number. Cannot be lower than min, except if -1
-        * (infinite).
-        * 
-        * @return the max
-        */
-       public int getMax() {
-               return max;
-       }
-
-       /**
-        * The maximum page number. Cannot be lower than min, except if -1
-        * (infinite).
-        * <p>
-        * May update the index if needed (if the index is &gt; the new max).
-        * <p>
-        * Will also (always) update the label and enable/disable the required
-        * buttons.
-        * 
-        * @param max
-        *            the new max
-        */
-       public void setMax(int max) {
-               this.max = max;
-               if (index > max && max != -1) {
-                       index = max;
-               }
-               updateEnabled();
-               updateLabel();
-       }
-
-       /**
-        * The current extra label to display with the default
-        * {@link GuiReaderNavBar#computeLabel(int, int, int)} implementation.
-        * 
-        * @return the current label
-        */
-       public String getExtraLabel() {
-               return extraLabel;
-       }
-
-       /**
-        * The current extra label to display with the default
-        * {@link GuiReaderNavBar#computeLabel(int, int, int)} implementation.
-        * 
-        * @param currentLabel
-        *            the new current label
-        */
-       public void setExtraLabel(String currentLabel) {
-               this.extraLabel = currentLabel;
-               updateLabel();
-       }
-
-       /**
-        * Add a listener that will be called on each page change.
-        * 
-        * @param listener
-        *            the new listener
-        */
-       public void addActionListener(ActionListener listener) {
-               listeners.add(listener);
-       }
-
-       /**
-        * Remove the given listener if possible.
-        * 
-        * @param listener
-        *            the listener to remove
-        * @return TRUE if it was removed, FALSE if it was not found
-        */
-       public boolean removeActionListener(ActionListener listener) {
-               return listeners.remove(listener);
-       }
-
-       /**
-        * Remove all the listeners.
-        */
-       public void clearActionsListeners() {
-               listeners.clear();
-       }
-
-       /**
-        * Notify a change of page.
-        */
-       public void fireEvent() {
-               for (ActionListener listener : listeners) {
-                       try {
-                               listener.actionPerformed(new ActionEvent(this,
-                                               ActionEvent.ACTION_FIRST, "page changed"));
-                       } catch (Exception e) {
-                               Instance.getInstance().getTraceHandler().error(e);
-                       }
-               }
-       }
-
-       /**
-        * Create a single navigation button.
-        * 
-        * @param text
-        *            the text to display
-        * @param action
-        *            the action to take on click
-        * @return the button
-        */
-       private JButton createNavButton(String text, ActionListener action) {
-               JButton navButton = new JButton(text);
-               navButton.addActionListener(action);
-               navButton.setForeground(Color.BLUE);
-               return navButton;
-       }
-
-       /**
-        * Update the label displayed in the UI.
-        */
-       private void updateLabel() {
-               label.setText(computeLabel(index, min, max));
-       }
-
-       /**
-        * Update the navigation buttons "enabled" state according to the current
-        * index value.
-        */
-       private void updateEnabled() {
-               navButtons[0].setEnabled(index > min);
-               navButtons[1].setEnabled(index > min);
-               navButtons[2].setEnabled(index < max || max == -1);
-               navButtons[3].setEnabled(index < max || max == -1);
-       }
-
-       /**
-        * Return the label to display for the given index.
-        * <p>
-        * Swing HTML (HTML3) is supported if surrounded by &lt;HTML&gt; and
-        * &lt;/HTML&gt;.
-        * <p>
-        * By default, return "Page 1/5: current_label" (with the current index and
-        * {@link GuiReaderNavBar#getCurrentLabel()}).
-        * 
-        * @param index
-        *            the new index number
-        * @param mix
-        *            the minimum index (inclusive)
-        * @param max
-        *            the maximum index (inclusive)
-        * @return the label
-        */
-       protected String computeLabel(int index,
-                       @SuppressWarnings("unused") int min, int max) {
-
-               String base = "&nbsp;&nbsp;<B>Page <SPAN COLOR='#444466'>%d</SPAN>&nbsp;";
-               if (max >= 0) {
-                       base += "/&nbsp;%d";
-               }
-               base += "</B>";
-
-               String ifLabel = ": %s";
-
-               String display = base;
-               String label = getExtraLabel();
-               if (label != null && !label.trim().isEmpty()) {
-                       display += ifLabel;
-               }
-
-               display = "<HTML>" + display + "</HTML>";
-
-               if (max >= 0) {
-                       return String.format(display, index, max, label);
-               }
-
-               return String.format(display, index, label);
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java
deleted file mode 100644 (file)
index 9615d75..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-
-import javax.swing.JFrame;
-
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-
-/**
- * A frame displaying properties and other information of a {@link Story}.
- * 
- * @author niki
- */
-public class GuiReaderPropertiesFrame extends JFrame {
-       private static final long serialVersionUID = 1L;
-
-       /**
-        * Create a new {@link GuiReaderPropertiesFrame}.
-        * 
-        * @param lib
-        *            the library to use for the cover image
-        * @param meta
-        *            the meta to describe
-        */
-       public GuiReaderPropertiesFrame(BasicLibrary lib, MetaData meta) {
-               setTitle(GuiReader.trans(StringIdGui.TITLE_STORY, meta.getLuid(),
-                               meta.getTitle()));
-
-               GuiReaderPropertiesPane desc = new GuiReaderPropertiesPane(lib, meta);
-               setSize(800,
-                               (int) desc.getPreferredSize().getHeight() + 2
-                                               * desc.getBorderThickness());
-
-               setLayout(new BorderLayout());
-               add(desc, BorderLayout.NORTH);
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesPane.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesPane.java
deleted file mode 100644 (file)
index 2c9c7e7..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Font;
-import java.util.Map;
-
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.ImageIcon;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JTextArea;
-
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.fanfix.reader.BasicReader;
-
-/**
- * A panel displaying properties and other information of a {@link Story}.
- * 
- * @author niki
- */
-public class GuiReaderPropertiesPane extends JPanel {
-       private static final long serialVersionUID = 1L;
-       private final int space = 10;
-
-       /**
-        * Create a new {@link GuiReaderPropertiesPane}.
-        * 
-        * @param lib
-        *            the library to use for the cover image
-        * @param meta
-        *            the meta to describe
-        */
-       public GuiReaderPropertiesPane(BasicLibrary lib, MetaData meta) {
-               // Image
-               ImageIcon img = GuiReaderCoverImager.generateCoverIcon(lib, meta);
-
-               setLayout(new BorderLayout());
-
-               // Main panel
-               JPanel mainPanel = new JPanel(new BorderLayout());
-               JPanel mainPanelKeys = new JPanel();
-               mainPanelKeys.setLayout(new BoxLayout(mainPanelKeys, BoxLayout.Y_AXIS));
-               JPanel mainPanelValues = new JPanel();
-               mainPanelValues.setLayout(new BoxLayout(mainPanelValues,
-                               BoxLayout.Y_AXIS));
-
-               mainPanel.add(mainPanelKeys, BorderLayout.WEST);
-               mainPanel.add(mainPanelValues, BorderLayout.CENTER);
-
-               Map<String, String> desc = BasicReader.getMetaDesc(meta);
-
-               Color trans = new Color(0, 0, 0, 1);
-               Color base = mainPanelValues.getBackground();
-               for (String key : desc.keySet()) {
-                       JTextArea jKey = new JTextArea(key);
-                       jKey.setFont(new Font(jKey.getFont().getFontName(), Font.BOLD, jKey
-                                       .getFont().getSize()));
-                       jKey.setEditable(false);
-                       jKey.setLineWrap(false);
-                       jKey.setBackground(trans);
-                       mainPanelKeys.add(jKey);
-
-                       final JTextArea jValue = new JTextArea(desc.get(key));
-                       jValue.setEditable(false);
-                       jValue.setLineWrap(false);
-                       jValue.setBackground(base);
-                       mainPanelValues.add(jValue);
-               }
-
-               // Image
-               JLabel imgLabel = new JLabel(img);
-               imgLabel.setVerticalAlignment(JLabel.TOP);
-
-               // Borders
-               mainPanelKeys.setBorder(BorderFactory.createEmptyBorder(space, space,
-                               space, space));
-               mainPanelValues.setBorder(BorderFactory.createEmptyBorder(space, space,
-                               space, space));
-               imgLabel.setBorder(BorderFactory.createEmptyBorder(0, space, space, 0));
-
-               // Add all
-               add(imgLabel, BorderLayout.WEST);
-               add(mainPanel, BorderLayout.CENTER);
-       }
-
-       /**
-        * The invisible border size (multiply by 2 if you need the total width or
-        * the total height).
-        * 
-        * @return the invisible border thickness
-        */
-       public int getBorderThickness() {
-               return space;
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchAction.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchAction.java
deleted file mode 100644 (file)
index 6d91a0c..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.io.IOException;
-import java.net.URL;
-
-import javax.swing.JButton;
-import javax.swing.JFrame;
-import javax.swing.JPanel;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.utils.Progress;
-import be.nikiroo.utils.ui.ProgressBar;
-
-public class GuiReaderSearchAction extends JFrame {
-       private static final long serialVersionUID = 1L;
-
-       private GuiReaderBookInfo info;
-       private ProgressBar pgBar;
-
-       public GuiReaderSearchAction(BasicLibrary lib, GuiReaderBookInfo info) {
-               super(info.getMainInfo());
-               this.setSize(800, 600);
-               this.info = info;
-
-               setLayout(new BorderLayout());
-
-               JPanel main = new JPanel(new BorderLayout());
-               JPanel props = new GuiReaderPropertiesPane(lib, info.getMeta());
-
-               main.add(props, BorderLayout.NORTH);
-               main.add(new GuiReaderViewerPanel(info.getMeta(), info.getMeta()
-                               .isImageDocument()), BorderLayout.CENTER);
-               main.add(createImportButton(lib), BorderLayout.SOUTH);
-
-               add(main, BorderLayout.CENTER);
-
-               pgBar = new ProgressBar();
-               pgBar.setVisible(false);
-               add(pgBar, BorderLayout.SOUTH);
-
-               pgBar.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               pgBar.invalidate();
-                               pgBar.setProgress(null);
-                               setEnabled(true);
-                               validate();
-                       }
-               });
-
-               pgBar.addUpdateListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               pgBar.invalidate();
-                               validate();
-                               repaint();
-                       }
-               });
-       }
-
-       private Component createImportButton(final BasicLibrary lib) {
-               JButton imprt = new JButton("Import into library");
-               imprt.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent ae) {
-                               final Progress pg = new Progress();
-                               pgBar.setProgress(pg);
-
-                               new Thread(new Runnable() {
-                                       @Override
-                                       public void run() {
-                                               try {
-                                                       lib.imprt(new URL(info.getMeta().getUrl()), null);
-                                               } catch (IOException e) {
-                                                       Instance.getInstance().getTraceHandler().error(e);
-                                               }
-
-                                               pg.done();
-                                       }
-                               }).start();
-                       }
-               });
-
-               return imprt;
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByNamePanel.java
deleted file mode 100644 (file)
index ebdb21a..0000000
+++ /dev/null
@@ -1,246 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.JButton;
-import javax.swing.JPanel;
-import javax.swing.JTextField;
-
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.reader.ui.GuiReaderSearchByPanel.Waitable;
-import be.nikiroo.fanfix.searchable.BasicSearchable;
-
-/**
- * This panel represents a search panel that works for keywords and tags based
- * searches.
- * 
- * @author niki
- */
-public class GuiReaderSearchByNamePanel extends JPanel {
-       private static final long serialVersionUID = 1L;
-
-       private BasicSearchable searchable;
-
-       private JTextField keywordsField;
-       private JButton submitKeywords;
-
-       private int page;
-       private int maxPage;
-       private List<MetaData> stories = new ArrayList<MetaData>();
-       private int storyItem;
-
-       public GuiReaderSearchByNamePanel(final Waitable waitable) {
-               super(new BorderLayout());
-
-               keywordsField = new JTextField();
-               add(keywordsField, BorderLayout.CENTER);
-
-               submitKeywords = new JButton("Search");
-               add(submitKeywords, BorderLayout.EAST);
-
-               // should be done out of UI
-               final Runnable go = new Runnable() {
-                       @Override
-                       public void run() {
-                               waitable.setWaiting(true);
-                               try {
-                                       search(keywordsField.getText(), 1, 0);
-                                       waitable.fireEvent();
-                               } finally {
-                                       waitable.setWaiting(false);
-                               }
-                       }
-               };
-
-               keywordsField.addKeyListener(new KeyAdapter() {
-                       @Override
-                       public void keyReleased(KeyEvent e) {
-                               if (e.getKeyCode() == KeyEvent.VK_ENTER) {
-                                       new Thread(go).start();
-                               } else {
-                                       super.keyReleased(e);
-                               }
-                       }
-               });
-
-               submitKeywords.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               new Thread(go).start();
-                       }
-               });
-
-               setSearchable(null);
-       }
-
-       /**
-        * The {@link BasicSearchable} object use for the searches themselves.
-        * <p>
-        * Can be NULL, but no searches will work.
-        * 
-        * @param searchable
-        *            the new searchable
-        */
-       public void setSearchable(BasicSearchable searchable) {
-               this.searchable = searchable;
-               page = 0;
-               maxPage = -1;
-               storyItem = 0;
-               stories = new ArrayList<MetaData>();
-               updateKeywords("");
-       }
-
-       /**
-        * The currently displayed page of result for the current search (see the
-        * <tt>page</tt> parameter of
-        * {@link GuiReaderSearchByNamePanel#search(String, int, int)}).
-        * 
-        * @return the currently displayed page of results
-        */
-       public int getPage() {
-               return page;
-       }
-
-       /**
-        * The number of pages of result for the current search (see the
-        * <tt>page</tt> parameter of
-        * {@link GuiReaderSearchByPanel#search(String, int, int)}).
-        * <p>
-        * For an unknown number or when not applicable, -1 is returned.
-        * 
-        * @return the number of pages of results or -1
-        */
-       public int getMaxPage() {
-               return maxPage;
-       }
-
-       /**
-        * Return the keywords used for the current search.
-        * 
-        * @return the keywords
-        */
-       public String getCurrentKeywords() {
-               return keywordsField.getText();
-       }
-
-       /**
-        * The currently loaded stories (the result of the latest search).
-        * 
-        * @return the stories
-        */
-       public List<MetaData> getStories() {
-               return stories;
-       }
-
-       /**
-        * Return the currently selected story (the <tt>item</tt>) if it was
-        * specified in the latest, or 0 if not.
-        * <p>
-        * Note: this is thus a 1-based index, <b>not</b> a 0-based index.
-        * 
-        * @return the item
-        */
-       public int getStoryItem() {
-               return storyItem;
-       }
-
-       /**
-        * Update the keywords displayed on screen.
-        * 
-        * @param keywords
-        *            the keywords
-        */
-       private void updateKeywords(final String keywords) {
-               if (!keywords.equals(keywordsField.getText())) {
-                       GuiReaderSearchFrame.inUi(new Runnable() {
-                               @Override
-                               public void run() {
-                                       keywordsField.setText(keywords);
-                               }
-                       });
-               }
-       }
-
-       /**
-        * Search for the given terms on the currently selected searchable.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * 
-        * @param keywords
-        *            the keywords to search for
-        * @param page
-        *            the page of results to load
-        * @param item
-        *            the item to select (or 0 for none by default)
-        * 
-        * @throw IndexOutOfBoundsException if the page is out of bounds
-        */
-       public void search(String keywords, int page, int item) {
-               List<MetaData> stories = new ArrayList<MetaData>();
-               int storyItem = 0;
-
-               updateKeywords(keywords);
-
-               int maxPage = -1;
-               if (searchable != null) {
-                       try {
-                               maxPage = searchable.searchPages(keywords);
-                       } catch (IOException e) {
-                               GuiReaderSearchFrame.error(e);
-                       }
-               }
-
-               if (page > 0) {
-                       if (maxPage >= 0 && (page <= 0 || page > maxPage)) {
-                               throw new IndexOutOfBoundsException("Page " + page + " out of "
-                                               + maxPage);
-                       }
-
-                       if (searchable != null) {
-                               try {
-                                       stories = searchable.search(keywords, page);
-                               } catch (IOException e) {
-                                       GuiReaderSearchFrame.error(e);
-                               }
-                       }
-
-                       if (item > 0 && item <= stories.size()) {
-                               storyItem = item;
-                       } else if (item > 0) {
-                               GuiReaderSearchFrame.error(String.format(
-                                               "Story item does not exist: Search [%s], item %d",
-                                               keywords, item));
-                       }
-               }
-
-               this.page = page;
-               this.maxPage = maxPage;
-               this.stories = stories;
-               this.storyItem = storyItem;
-       }
-
-       /**
-        * Enables or disables this component, depending on the value of the
-        * parameter <code>b</code>. An enabled component can respond to user input
-        * and generate events. Components are enabled initially by default.
-        * <p>
-        * Disabling this component will also affect its children.
-        * 
-        * @param b
-        *            If <code>true</code>, this component is enabled; otherwise
-        *            this component is disabled
-        */
-       @Override
-       public void setEnabled(boolean b) {
-               super.setEnabled(b);
-               keywordsField.setEnabled(b);
-               submitKeywords.setEnabled(b);
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByPanel.java
deleted file mode 100644 (file)
index 8f95d4c..0000000
+++ /dev/null
@@ -1,281 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.util.List;
-
-import javax.swing.JPanel;
-import javax.swing.JTabbedPane;
-import javax.swing.event.ChangeEvent;
-import javax.swing.event.ChangeListener;
-
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.searchable.BasicSearchable;
-import be.nikiroo.fanfix.searchable.SearchableTag;
-import be.nikiroo.fanfix.supported.SupportType;
-
-/**
- * This panel represents a search panel that works for keywords and tags based
- * searches.
- * 
- * @author niki
- */
-public class GuiReaderSearchByPanel extends JPanel {
-       private static final long serialVersionUID = 1L;
-
-       private Waitable waitable;
-
-       private boolean searchByTags;
-       private JTabbedPane searchTabs;
-       private GuiReaderSearchByNamePanel byName;
-       private GuiReaderSearchByTagPanel byTag;
-
-       /**
-        * This interface represents an item that wan be put in "wait" mode. It is
-        * supposed to be used for long running operations during which we want to
-        * disable UI interactions.
-        * <p>
-        * It also allows reporting an event to the item.
-        * 
-        * @author niki
-        */
-       public interface Waitable {
-               /**
-                * Set the item in wait mode, blocking it from accepting UI input.
-                * 
-                * @param waiting
-                *            TRUE for wait more, FALSE to restore normal mode
-                */
-               public void setWaiting(boolean waiting);
-
-               /**
-                * Notify the {@link Waitable} that an event occured (i.e., new stories
-                * were found).
-                */
-               public void fireEvent();
-       }
-
-       /**
-        * Create a new {@link GuiReaderSearchByPanel}.
-        * 
-        * @param waitable
-        *            the waitable we can wait on for long UI operations
-        */
-       public GuiReaderSearchByPanel(Waitable waitable) {
-               setLayout(new BorderLayout());
-
-               this.waitable = waitable;
-               searchByTags = false;
-
-               byName = new GuiReaderSearchByNamePanel(waitable);
-               byTag = new GuiReaderSearchByTagPanel(waitable);
-
-               searchTabs = new JTabbedPane();
-               searchTabs.addTab("By name", byName);
-               searchTabs.addTab("By tags", byTag);
-               searchTabs.addChangeListener(new ChangeListener() {
-                       @Override
-                       public void stateChanged(ChangeEvent e) {
-                               searchByTags = (searchTabs.getSelectedComponent() == byTag);
-                       }
-               });
-
-               add(searchTabs, BorderLayout.CENTER);
-               updateSearchBy(searchByTags);
-       }
-
-       /**
-        * Set the new {@link SupportType}.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * <p>
-        * Note that if a non-searchable {@link SupportType} is used, an
-        * {@link IllegalArgumentException} will be thrown.
-        * 
-        * @param supportType
-        *            the support mode, must be searchable or NULL
-        * 
-        * @throws IllegalArgumentException
-        *             if the {@link SupportType} is not NULL but not searchable
-        *             (see {@link BasicSearchable#getSearchable(SupportType)})
-        */
-       public void setSupportType(SupportType supportType) {
-               BasicSearchable searchable = BasicSearchable.getSearchable(supportType);
-               if (searchable == null && supportType != null) {
-                       throw new IllegalArgumentException("Unupported support type: "
-                                       + supportType);
-               }
-
-               byName.setSearchable(searchable);
-               byTag.setSearchable(searchable);
-       }
-
-       /**
-        * The currently displayed page of result for the current search (see the
-        * <tt>page</tt> parameter of
-        * {@link GuiReaderSearchByPanel#search(String, int, int)} or
-        * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
-        * ).
-        * 
-        * @return the currently displayed page of results
-        */
-       public int getPage() {
-               if (!searchByTags) {
-                       return byName.getPage();
-               }
-
-               return byTag.getPage();
-       }
-
-       /**
-        * The number of pages of result for the current search (see the
-        * <tt>page</tt> parameter of
-        * {@link GuiReaderSearchByPanel#search(String, int, int)} or
-        * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
-        * ).
-        * <p>
-        * For an unknown number or when not applicable, -1 is returned.
-        * 
-        * @return the number of pages of results or -1
-        */
-       public int getMaxPage() {
-               if (!searchByTags) {
-                       return byName.getMaxPage();
-               }
-
-               return byTag.getMaxPage();
-       }
-
-       /**
-        * Set the page of results to display for the current search. This will
-        * cause {@link Waitable#fireEvent()} to be called if needed.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * 
-        * @param page
-        *            the page of results to set
-        * 
-        * @throw IndexOutOfBoundsException if the page is out of bounds
-        */
-       public void setPage(int page) {
-               if (searchByTags) {
-                       searchTag(byTag.getCurrentTag(), page, 0);
-               } else {
-                       search(byName.getCurrentKeywords(), page, 0);
-               }
-       }
-
-       /**
-        * The currently loaded stories (the result of the latest search).
-        * 
-        * @return the stories
-        */
-       public List<MetaData> getStories() {
-               if (!searchByTags) {
-                       return byName.getStories();
-               }
-
-               return byTag.getStories();
-       }
-
-       /**
-        * Return the currently selected story (the <tt>item</tt>) if it was
-        * specified in the latest, or 0 if not.
-        * <p>
-        * Note: this is thus a 1-based index, <b>not</b> a 0-based index.
-        * 
-        * @return the item
-        */
-       public int getStoryItem() {
-               if (!searchByTags) {
-                       return byName.getStoryItem();
-               }
-
-               return byTag.getStoryItem();
-       }
-
-       /**
-        * Update the kind of searches to make: search by keywords or search by tags
-        * (it will impact what the user can see and interact with on the UI).
-        * 
-        * @param byTag
-        *            TRUE for tag-based searches, FALSE for keywords-based searches
-        */
-       private void updateSearchBy(final boolean byTag) {
-               GuiReaderSearchFrame.inUi(new Runnable() {
-                       @Override
-                       public void run() {
-                               if (!byTag) {
-                                       searchTabs.setSelectedIndex(0);
-                               } else {
-                                       searchTabs.setSelectedIndex(1);
-                               }
-                       }
-               });
-       }
-
-       /**
-        * Search for the given terms on the currently selected searchable. This
-        * will cause {@link Waitable#fireEvent()} to be called if needed.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * 
-        * @param keywords
-        *            the keywords to search for
-        * @param page
-        *            the page of results to load
-        * @param item
-        *            the item to select (or 0 for none by default)
-        * 
-        * @throw IndexOutOfBoundsException if the page is out of bounds
-        */
-       public void search(final String keywords, final int page, final int item) {
-               updateSearchBy(false);
-               byName.search(keywords, page, item);
-               waitable.fireEvent();
-       }
-
-       /**
-        * Search for the given tag on the currently selected searchable. This will
-        * cause {@link Waitable#fireEvent()} to be called if needed.
-        * <p>
-        * If the tag contains children tags, those will be displayed so you can
-        * select them; if the tag is a leaf tag, the linked stories will be
-        * displayed.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * 
-        * @param tag
-        *            the tag to search for, or NULL for base tags
-        * @param page
-        *            the page of results to load
-        * @param item
-        *            the item to select (or 0 for none by default)
-        * 
-        * @throw IndexOutOfBoundsException if the page is out of bounds
-        */
-       public void searchTag(final SearchableTag tag, final int page,
-                       final int item) {
-               updateSearchBy(true);
-               byTag.searchTag(tag, page, item);
-               waitable.fireEvent();
-       }
-
-       /**
-        * Enables or disables this component, depending on the value of the
-        * parameter <code>b</code>. An enabled component can respond to user input
-        * and generate events. Components are enabled initially by default.
-        * <p>
-        * Disabling this component will also affect its children.
-        * 
-        * @param b
-        *            If <code>true</code>, this component is enabled; otherwise
-        *            this component is disabled
-        */
-       @Override
-       public void setEnabled(boolean b) {
-               super.setEnabled(b);
-               searchTabs.setEnabled(b);
-               byName.setEnabled(b);
-               byTag.setEnabled(b);
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByTagPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchByTagPanel.java
deleted file mode 100644 (file)
index 260fc48..0000000
+++ /dev/null
@@ -1,458 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Component;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.BoxLayout;
-import javax.swing.JComboBox;
-import javax.swing.JList;
-import javax.swing.JPanel;
-import javax.swing.ListCellRenderer;
-
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.reader.ui.GuiReaderSearchByPanel.Waitable;
-import be.nikiroo.fanfix.searchable.BasicSearchable;
-import be.nikiroo.fanfix.searchable.SearchableTag;
-import be.nikiroo.fanfix.supported.SupportType;
-
-/**
- * This panel represents a search panel that works for keywords and tags based
- * searches.
- * 
- * @author niki
- */
-// JCombobox<E> not 1.6 compatible
-@SuppressWarnings({ "unchecked", "rawtypes" })
-public class GuiReaderSearchByTagPanel extends JPanel {
-       private static final long serialVersionUID = 1L;
-
-       private BasicSearchable searchable;
-       private Waitable waitable;
-
-       private SearchableTag currentTag;
-       private JPanel tagBars;
-       private List<JComboBox> combos;
-
-       private int page;
-       private int maxPage;
-       private List<MetaData> stories = new ArrayList<MetaData>();
-       private int storyItem;
-
-       public GuiReaderSearchByTagPanel(Waitable waitable) {
-               setLayout(new BorderLayout());
-
-               this.waitable = waitable;
-               combos = new ArrayList<JComboBox>();
-               page = 0;
-               maxPage = -1;
-
-               tagBars = new JPanel();
-               tagBars.setLayout(new BoxLayout(tagBars, BoxLayout.Y_AXIS));
-               add(tagBars, BorderLayout.NORTH);
-       }
-
-       /**
-        * The {@link BasicSearchable} object use for the searches themselves.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * <p>
-        * Can be NULL, but no searches will work.
-        * 
-        * @param searchable
-        *            the new searchable
-        */
-       public void setSearchable(BasicSearchable searchable) {
-               this.searchable = searchable;
-               page = 0;
-               maxPage = -1;
-               storyItem = 0;
-               stories = new ArrayList<MetaData>();
-               updateTags(null);
-       }
-
-       /**
-        * The currently displayed page of result for the current search (see the
-        * <tt>page</tt> parameter of
-        * {@link GuiReaderSearchByTagPanel#searchTag(SupportType, int, int, SearchableTag)}
-        * ).
-        * 
-        * @return the currently displayed page of results
-        */
-       public int getPage() {
-               return page;
-       }
-
-       /**
-        * The number of pages of result for the current search (see the
-        * <tt>page</tt> parameter of
-        * {@link GuiReaderSearchByPanel#searchTag(SupportType, int, int, SearchableTag)}
-        * ).
-        * <p>
-        * For an unknown number or when not applicable, -1 is returned.
-        * 
-        * @return the number of pages of results or -1
-        */
-       public int getMaxPage() {
-               return maxPage;
-       }
-
-       /**
-        * Return the tag used for the current search.
-        * 
-        * @return the tag (which can be NULL, for "base tags")
-        */
-       public SearchableTag getCurrentTag() {
-               return currentTag;
-       }
-
-       /**
-        * The currently loaded stories (the result of the latest search).
-        * 
-        * @return the stories
-        */
-       public List<MetaData> getStories() {
-               return stories;
-       }
-
-       /**
-        * Return the currently selected story (the <tt>item</tt>) if it was
-        * specified in the latest, or 0 if not.
-        * <p>
-        * Note: this is thus a 1-based index, <b>not</b> a 0-based index.
-        * 
-        * @return the item
-        */
-       public int getStoryItem() {
-               return storyItem;
-       }
-
-       /**
-        * Update the tags displayed on screen and reset the tags bar.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * 
-        * @param tag
-        *            the tag to use, or NULL for base tags
-        */
-       private void updateTags(final SearchableTag tag) {
-               final List<SearchableTag> parents = new ArrayList<SearchableTag>();
-               SearchableTag parent = (tag == null) ? null : tag;
-               while (parent != null) {
-                       parents.add(parent);
-                       parent = parent.getParent();
-               }
-
-               List<SearchableTag> rootTags = new ArrayList<SearchableTag>();
-               SearchableTag selectedRootTag = null;
-               selectedRootTag = parents.isEmpty() ? null : parents
-                               .get(parents.size() - 1);
-
-               if (searchable != null) {
-                       try {
-                               rootTags = searchable.getTags();
-                       } catch (IOException e) {
-                               GuiReaderSearchFrame.error(e);
-                       }
-               }
-
-               final List<SearchableTag> rootTagsF = rootTags;
-               final SearchableTag selectedRootTagF = selectedRootTag;
-
-               GuiReaderSearchFrame.inUi(new Runnable() {
-                       @Override
-                       public void run() {
-                               tagBars.invalidate();
-                               tagBars.removeAll();
-
-                               addTagBar(rootTagsF, selectedRootTagF);
-
-                               for (int i = parents.size() - 1; i >= 0; i--) {
-                                       SearchableTag selectedChild = null;
-                                       if (i > 0) {
-                                               selectedChild = parents.get(i - 1);
-                                       }
-
-                                       SearchableTag parent = parents.get(i);
-                                       addTagBar(parent.getChildren(), selectedChild);
-                               }
-
-                               tagBars.validate();
-                       }
-               });
-       }
-
-       /**
-        * Add a tags bar (do not remove possible previous ones).
-        * <p>
-        * Will always add an "empty" (NULL) tag as first option.
-        * 
-        * @param tags
-        *            the tags to display
-        * @param selected
-        *            the selected tag if any, or NULL for none
-        */
-       private void addTagBar(List<SearchableTag> tags,
-                       final SearchableTag selected) {
-               tags.add(0, null);
-
-               final int comboIndex = combos.size();
-
-               final JComboBox combo = new JComboBox(
-                               tags.toArray(new SearchableTag[] {}));
-               combo.setSelectedItem(selected);
-
-               final ListCellRenderer basic = combo.getRenderer();
-
-               combo.setRenderer(new ListCellRenderer() {
-                       @Override
-                       public Component getListCellRendererComponent(JList list,
-                                       Object value, int index, boolean isSelected,
-                                       boolean cellHasFocus) {
-
-                               Object displayValue = value;
-                               if (value instanceof SearchableTag) {
-                                       displayValue = ((SearchableTag) value).getName();
-                               } else {
-                                       displayValue = "Select a tag...";
-                                       cellHasFocus = false;
-                                       isSelected = false;
-                               }
-
-                               Component rep = basic.getListCellRendererComponent(list,
-                                               displayValue, index, isSelected, cellHasFocus);
-
-                               if (value == null) {
-                                       rep.setForeground(Color.GRAY);
-                               }
-
-                               return rep;
-                       }
-               });
-
-               combo.addActionListener(createComboTagAction(comboIndex));
-
-               combos.add(combo);
-               tagBars.add(combo);
-       }
-
-       /**
-        * The action to do on {@link JComboBox} selection.
-        * <p>
-        * The content of the action is:
-        * <ul>
-        * <li>Remove all tags bar below this one</li>
-        * <li>Load the subtags if any in anew tags bar</li>
-        * <li>Load the related stories if the tag was a leaf tag and notify the
-        * {@link Waitable} (via {@link Waitable#fireEvent()})</li>
-        * </ul>
-        * 
-        * @param comboIndex
-        *            the index of the related {@link JComboBox}
-        * 
-        * @return the action
-        */
-       private ActionListener createComboTagAction(final int comboIndex) {
-               return new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent ae) {
-                               List<JComboBox> combos = GuiReaderSearchByTagPanel.this.combos;
-                               if (combos == null || comboIndex < 0
-                                               || comboIndex >= combos.size()) {
-                                       return;
-                               }
-
-                               // Tag can be NULL
-                               final SearchableTag tag = (SearchableTag) combos
-                                               .get(comboIndex).getSelectedItem();
-
-                               while (comboIndex + 1 < combos.size()) {
-                                       JComboBox combo = combos.remove(comboIndex + 1);
-                                       tagBars.remove(combo);
-                               }
-
-                               new Thread(new Runnable() {
-                                       @Override
-                                       public void run() {
-                                               waitable.setWaiting(true);
-                                               try {
-                                                       final List<SearchableTag> children = getChildrenForTag(tag);
-                                                       if (children != null) {
-                                                               GuiReaderSearchFrame.inUi(new Runnable() {
-                                                                       @Override
-                                                                       public void run() {
-                                                                               addTagBar(children, tag);
-                                                                       }
-                                                               });
-                                                       }
-
-                                                       if (tag != null && tag.isLeaf()) {
-                                                               storyItem = 0;
-                                                               try {
-                                                                       searchable.fillTag(tag);
-                                                                       page = 1;
-                                                                       stories = searchable.search(tag, 1);
-                                                                       maxPage = searchable.searchPages(tag);
-                                                                       currentTag = tag;
-                                                               } catch (IOException e) {
-                                                                       GuiReaderSearchFrame.error(e);
-                                                                       page = 0;
-                                                                       maxPage = -1;
-                                                                       stories = new ArrayList<MetaData>();
-                                                               }
-
-                                                               waitable.fireEvent();
-                                                       }
-                                               } finally {
-                                                       waitable.setWaiting(false);
-                                               }
-                                       }
-                               }).start();
-                       }
-               };
-       }
-
-       /**
-        * Get the children of the given tag (or the base tags if the given tag is
-        * NULL).
-        * <p>
-        * This action will "fill" ({@link BasicSearchable#fillTag(SearchableTag)})
-        * the given tag if needed first.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * 
-        * @param tag
-        *            the tag to search into or NULL for the base tags
-        * @return the children
-        */
-       private List<SearchableTag> getChildrenForTag(final SearchableTag tag) {
-               List<SearchableTag> children = new ArrayList<SearchableTag>();
-               if (tag == null) {
-                       try {
-                               List<SearchableTag> baseTags = searchable.getTags();
-                               children = baseTags;
-                       } catch (IOException e) {
-                               GuiReaderSearchFrame.error(e);
-                       }
-               } else {
-                       try {
-                               searchable.fillTag(tag);
-                       } catch (IOException e) {
-                               GuiReaderSearchFrame.error(e);
-                       }
-
-                       if (!tag.isLeaf()) {
-                               children = tag.getChildren();
-                       } else {
-                               children = null;
-                       }
-               }
-
-               return children;
-       }
-
-       /**
-        * Search for the given tag on the currently selected searchable.
-        * <p>
-        * If the tag contains children tags, those will be displayed so you can
-        * select them; if the tag is a leaf tag, the linked stories will be
-        * displayed.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * 
-        * @param tag
-        *            the tag to search for, or NULL for base tags
-        * @param page
-        *            the page of results to load
-        * @param item
-        *            the item to select (or 0 for none by default)
-        * 
-        * @throw IndexOutOfBoundsException if the page is out of bounds
-        */
-       public void searchTag(SearchableTag tag, int page, int item) {
-               List<MetaData> stories = new ArrayList<MetaData>();
-               int storyItem = 0;
-
-               currentTag = tag;
-               updateTags(tag);
-
-               int maxPage = -1;
-               if (tag != null) {
-                       try {
-                               searchable.fillTag(tag);
-
-                               if (!tag.isLeaf()) {
-                                       List<SearchableTag> subtags = tag.getChildren();
-                                       if (item > 0 && item <= subtags.size()) {
-                                               SearchableTag subtag = subtags.get(item - 1);
-                                               try {
-                                                       tag = subtag;
-                                                       searchable.fillTag(tag);
-                                               } catch (IOException e) {
-                                                       GuiReaderSearchFrame.error(e);
-                                               }
-                                       } else if (item > 0) {
-                                               GuiReaderSearchFrame.error(String.format(
-                                                               "Tag item does not exist: Tag [%s], item %d",
-                                                               tag.getFqName(), item));
-                                       }
-                               }
-
-                               maxPage = searchable.searchPages(tag);
-                               if (page > 0 && tag.isLeaf()) {
-                                       if (maxPage >= 0 && (page <= 0 || page > maxPage)) {
-                                               throw new IndexOutOfBoundsException("Page " + page
-                                                               + " out of " + maxPage);
-                                       }
-
-                                       try {
-                                               stories = searchable.search(tag, page);
-                                               if (item > 0 && item <= stories.size()) {
-                                                       storyItem = item;
-                                               } else if (item > 0) {
-                                                       GuiReaderSearchFrame
-                                                                       .error(String
-                                                                                       .format("Story item does not exist: Tag [%s], item %d",
-                                                                                                       tag.getFqName(), item));
-                                               }
-                                       } catch (IOException e) {
-                                               GuiReaderSearchFrame.error(e);
-                                       }
-                               }
-                       } catch (IOException e) {
-                               GuiReaderSearchFrame.error(e);
-                               maxPage = 0;
-                       }
-               }
-
-               this.stories = stories;
-               this.storyItem = storyItem;
-               this.page = page;
-               this.maxPage = maxPage;
-       }
-
-       /**
-        * Enables or disables this component, depending on the value of the
-        * parameter <code>b</code>. An enabled component can respond to user input
-        * and generate events. Components are enabled initially by default.
-        * <p>
-        * Disabling this component will also affect its children.
-        * 
-        * @param b
-        *            If <code>true</code>, this component is enabled; otherwise
-        *            this component is disabled
-        */
-       @Override
-       public void setEnabled(boolean b) {
-               super.setEnabled(b);
-               tagBars.setEnabled(b);
-               for (JComboBox combo : combos) {
-                       combo.setEnabled(b);
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderSearchFrame.java
deleted file mode 100644 (file)
index def9fc6..0000000
+++ /dev/null
@@ -1,380 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.EventQueue;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.swing.JComboBox;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener;
-import be.nikiroo.fanfix.searchable.BasicSearchable;
-import be.nikiroo.fanfix.searchable.SearchableTag;
-import be.nikiroo.fanfix.supported.SupportType;
-
-/**
- * This frame will allow you to search through the supported websites for new
- * stories/comics.
- * 
- * @author niki
- */
-// JCombobox<E> not 1.6 compatible
-@SuppressWarnings({ "unchecked", "rawtypes" })
-public class GuiReaderSearchFrame extends JFrame {
-       private static final long serialVersionUID = 1L;
-
-       private List<SupportType> supportTypes;
-
-       private JComboBox comboSupportTypes;
-       private ActionListener comboSupportTypesListener;
-       private GuiReaderSearchByPanel searchPanel;
-       private GuiReaderNavBar navbar;
-
-       private boolean seeWordcount;
-       private GuiReaderGroup books;
-
-       public GuiReaderSearchFrame(final GuiReader reader) {
-               super("Browse stories");
-               setLayout(new BorderLayout());
-               setSize(800, 600);
-
-               supportTypes = new ArrayList<SupportType>();
-               supportTypes.add(null);
-               for (SupportType type : SupportType.values()) {
-                       if (BasicSearchable.getSearchable(type) != null) {
-                               supportTypes.add(type);
-                       }
-               }
-
-               comboSupportTypes = new JComboBox(
-                               supportTypes.toArray(new SupportType[] {}));
-
-               comboSupportTypesListener = new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final SupportType support = (SupportType) comboSupportTypes
-                                               .getSelectedItem();
-                               setWaiting(true);
-                               new Thread(new Runnable() {
-                                       @Override
-                                       public void run() {
-                                               try {
-                                                       updateSupportType(support);
-                                               } finally {
-                                                       setWaiting(false);
-                                               }
-                                       }
-                               }).start();
-                       }
-               };
-               comboSupportTypes.addActionListener(comboSupportTypesListener);
-
-               JPanel searchSites = new JPanel(new BorderLayout());
-               searchSites.add(comboSupportTypes, BorderLayout.CENTER);
-               searchSites.add(new JLabel(" " + "Website : "), BorderLayout.WEST);
-
-               searchPanel = new GuiReaderSearchByPanel(
-                               new GuiReaderSearchByPanel.Waitable() {
-                                       @Override
-                                       public void setWaiting(boolean waiting) {
-                                               GuiReaderSearchFrame.this.setWaiting(waiting);
-                                       }
-
-                                       @Override
-                                       public void fireEvent() {
-                                               updatePages(searchPanel.getPage(),
-                                                               searchPanel.getMaxPage());
-                                               List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
-                                               for (MetaData meta : searchPanel.getStories()) {
-                                                       infos.add(GuiReaderBookInfo.fromMeta(meta));
-                                               }
-
-                                               int page = searchPanel.getPage();
-                                               if (page <= 0) {
-                                                       navbar.setMin(1);
-                                                       navbar.setMax(1);
-                                               } else {
-                                                       int max = searchPanel.getMaxPage();
-                                                       navbar.setMin(1);
-                                                       navbar.setMax(max);
-                                                       navbar.setIndex(page);
-                                               }
-                                               updateBooks(infos);
-
-                                               // ! 1-based index !
-                                               int item = searchPanel.getStoryItem();
-                                               if (item > 0 && item <= books.getBooksCount()) {
-                                                       books.setSelectedBook(item - 1, false);
-                                               }
-                                       }
-                               });
-
-               JPanel top = new JPanel(new BorderLayout());
-               top.add(searchSites, BorderLayout.NORTH);
-               top.add(searchPanel, BorderLayout.CENTER);
-
-               add(top, BorderLayout.NORTH);
-
-               books = new GuiReaderGroup(reader, null, null);
-               books.setActionListener(new BookActionListener() {
-                       @Override
-                       public void select(GuiReaderBook book) {
-                       }
-
-                       @Override
-                       public void popupRequested(GuiReaderBook book, Component target,
-                                       int x, int y) {
-                       }
-
-                       @Override
-                       public void action(GuiReaderBook book) {
-                               new GuiReaderSearchAction(reader.getLibrary(), book.getInfo())
-                                               .setVisible(true);
-                       }
-               });
-               JScrollPane scroll = new JScrollPane(books);
-               scroll.getVerticalScrollBar().setUnitIncrement(16);
-               add(scroll, BorderLayout.CENTER);
-
-               navbar = new GuiReaderNavBar(-1, -1) {
-                       private static final long serialVersionUID = 1L;
-
-                       @Override
-                       protected String computeLabel(int index, int min, int max) {
-                               if (index <= 0) {
-                                       return "";
-                               }
-                               return super.computeLabel(index, min, max);
-                       }
-               };
-
-               navbar.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               searchPanel.setPage(navbar.getIndex());
-                       }
-               });
-
-               add(navbar, BorderLayout.SOUTH);
-       }
-
-       /**
-        * Update the {@link SupportType} currently displayed to the user.
-        * <p>
-        * Will also cause a search for the new base tags of the given support if
-        * not NULL.
-        * <p>
-        * This operation can be long and should be run outside the UI thread.
-        * 
-        * @param supportType
-        *            the new {@link SupportType}
-        */
-       private void updateSupportType(final SupportType supportType) {
-               inUi(new Runnable() {
-                       @Override
-                       public void run() {
-                               books.clear();
-
-                               comboSupportTypes
-                                               .removeActionListener(comboSupportTypesListener);
-                               comboSupportTypes.setSelectedItem(supportType);
-                               comboSupportTypes.addActionListener(comboSupportTypesListener);
-                       }
-               });
-
-               searchPanel.setSupportType(supportType);
-       }
-
-       /**
-        * Update the pages and the lined buttons currently displayed on screen.
-        * <p>
-        * Those are the same pages and maximum pages used by
-        * {@link GuiReaderSearchByPanel#search(String, int, int)} and
-        * {@link GuiReaderSearchByPanel#searchTag(SearchableTag, int, int)}.
-        * 
-        * @param page
-        *            the current page of results
-        * @param maxPage
-        *            the maximum number of pages of results
-        */
-       private void updatePages(final int page, final int maxPage) {
-               inUi(new Runnable() {
-                       @Override
-                       public void run() {
-                               if (maxPage >= 1) {
-                                       navbar.setMin(1);
-                                       navbar.setMax(maxPage);
-                                       navbar.setIndex(page);
-                               } else {
-                                       navbar.setMin(-1);
-                                       navbar.setMax(-1);
-                               }
-                       }
-               });
-       }
-
-       /**
-        * Update the currently displayed books.
-        * 
-        * @param infos
-        *            the new books
-        */
-       private void updateBooks(final List<GuiReaderBookInfo> infos) {
-               inUi(new Runnable() {
-                       @Override
-                       public void run() {
-                               books.refreshBooks(infos, seeWordcount);
-                       }
-               });
-       }
-
-       /**
-        * Search for the given terms on the currently selected searchable. This
-        * will update the displayed books if needed.
-        * <p>
-        * This operation is asynchronous.
-        * 
-        * @param keywords
-        *            the keywords to search for
-        * @param page
-        *            the page of results to load
-        * @param item
-        *            the item to select (or 0 for none by default)
-        */
-       public void search(final SupportType searchOn, final String keywords,
-                       final int page, final int item) {
-               setWaiting(true);
-               new Thread(new Runnable() {
-                       @Override
-                       public void run() {
-                               try {
-                                       updateSupportType(searchOn);
-                                       searchPanel.search(keywords, page, item);
-                               } finally {
-                                       setWaiting(false);
-                               }
-                       }
-               }).start();
-       }
-
-       /**
-        * Search for the given tag on the currently selected searchable. This will
-        * update the displayed books if needed.
-        * <p>
-        * If the tag contains children tags, those will be displayed so you can
-        * select them; if the tag is a leaf tag, the linked stories will be
-        * displayed.
-        * <p>
-        * This operation is asynchronous.
-        * 
-        * @param tag
-        *            the tag to search for, or NULL for base tags
-        * @param page
-        *            the page of results to load
-        * @param item
-        *            the item to select (or 0 for none by default)
-        */
-       public void searchTag(final SupportType searchOn, final int page,
-                       final int item, final SearchableTag tag) {
-               setWaiting(true);
-               new Thread(new Runnable() {
-                       @Override
-                       public void run() {
-                               try {
-                                       updateSupportType(searchOn);
-                                       searchPanel.searchTag(tag, page, item);
-                               } finally {
-                                       setWaiting(false);
-                               }
-                       }
-               }).start();
-       }
-
-       /**
-        * Process the given action in the main Swing UI thread.
-        * <p>
-        * The code will make sure the current thread is the main UI thread and, if
-        * not, will switch to it before executing the runnable.
-        * <p>
-        * Synchronous operation.
-        * 
-        * @param run
-        *            the action to run
-        */
-       static void inUi(final Runnable run) {
-               if (EventQueue.isDispatchThread()) {
-                       run.run();
-               } else {
-                       try {
-                               EventQueue.invokeAndWait(run);
-                       } catch (InterruptedException e) {
-                               error(e);
-                       } catch (InvocationTargetException e) {
-                               error(e);
-                       }
-               }
-       }
-
-       /**
-        * An error occurred, inform the user and/or log the error.
-        * 
-        * @param e
-        *            the error
-        */
-       static void error(Exception e) {
-               Instance.getInstance().getTraceHandler().error(e);
-       }
-
-       /**
-        * An error occurred, inform the user and/or log the error.
-        * 
-        * @param e
-        *            the error message
-        */
-       static void error(String e) {
-               Instance.getInstance().getTraceHandler().error(e);
-       }
-
-       /**
-        * Enables or disables this component, depending on the value of the
-        * parameter <code>b</code>. An enabled component can respond to user input
-        * and generate events. Components are enabled initially by default.
-        * <p>
-        * Disabling this component will also affect its children.
-        * 
-        * @param b
-        *            If <code>true</code>, this component is enabled; otherwise
-        *            this component is disabled
-        */
-       @Override
-       public void setEnabled(boolean b) {
-               super.setEnabled(b);
-               books.setEnabled(b);
-               searchPanel.setEnabled(b);
-       }
-
-       /**
-        * Set the item in wait mode, blocking it from accepting UI input.
-        * 
-        * @param waiting
-        *            TRUE for wait more, FALSE to restore normal mode
-        */
-       private void setWaiting(final boolean waiting) {
-               inUi(new Runnable() {
-                       @Override
-                       public void run() {
-                               GuiReaderSearchFrame.this.setEnabled(!waiting);
-                       }
-               });
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewer.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewer.java
deleted file mode 100644 (file)
index bfb1892..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.Font;
-import java.awt.LayoutManager;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-
-import javax.swing.BorderFactory;
-import javax.swing.BoxLayout;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.SwingConstants;
-
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.data.Chapter;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-
-/**
- * An internal, Swing-based {@link Story} viewer.
- * <p>
- * Works on both text and image document (see {@link MetaData#isImageDocument()}
- * ).
- * 
- * @author niki
- */
-public class GuiReaderViewer extends JFrame {
-       private static final long serialVersionUID = 1L;
-
-       private Story story;
-       private MetaData meta;
-       private JLabel title;
-       private GuiReaderPropertiesPane descPane;
-       private GuiReaderViewerPanel mainPanel;
-       private GuiReaderNavBar navbar;
-
-       /**
-        * Create a new {@link Story} viewer.
-        * 
-        * @param lib
-        *            the {@link BasicLibrary} to load the cover from
-        * @param story
-        *            the {@link Story} to display
-        */
-       public GuiReaderViewer(BasicLibrary lib, Story story) {
-               setTitle(GuiReader.trans(StringIdGui.TITLE_STORY, story.getMeta()
-                               .getLuid(), story.getMeta().getTitle()));
-
-               setSize(800, 600);
-
-               this.story = story;
-               this.meta = story.getMeta();
-
-               initGuiBase(lib);
-               initGuiNavButtons();
-
-               setChapter(-1);
-       }
-
-       /**
-        * Initialise the base panel with everything but the navigation buttons.
-        * 
-        * @param lib
-        *            the {@link BasicLibrary} to use to retrieve the cover image in
-        *            the description panel
-        */
-       private void initGuiBase(BasicLibrary lib) {
-               setLayout(new BorderLayout());
-
-               title = new JLabel();
-               title.setFont(new Font(Font.SERIF, Font.BOLD,
-                               title.getFont().getSize() * 3));
-               title.setText(meta.getTitle());
-               title.setHorizontalAlignment(SwingConstants.CENTER);
-               title.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
-               add(title, BorderLayout.NORTH);
-
-               JPanel contentPane = new JPanel(new BorderLayout());
-               add(contentPane, BorderLayout.CENTER);
-
-               descPane = new GuiReaderPropertiesPane(lib, meta);
-               contentPane.add(descPane, BorderLayout.NORTH);
-
-               mainPanel = new GuiReaderViewerPanel(story);
-               contentPane.add(mainPanel, BorderLayout.CENTER);
-       }
-
-       /**
-        * Create the 4 navigation buttons in {@link GuiReaderViewer#navButtons} and
-        * initialise them.
-        */
-       private void initGuiNavButtons() {
-               navbar = new GuiReaderNavBar(-1, story.getChapters().size() - 1) {
-                       private static final long serialVersionUID = 1L;
-
-                       @Override
-                       protected String computeLabel(int index, int min, int max) {
-                               int chapter = index;
-                               Chapter chap;
-                               if (chapter < 0) {
-                                       chap = meta.getResume();
-                                       descPane.setVisible(true);
-                               } else {
-                                       chap = story.getChapters().get(chapter);
-                                       descPane.setVisible(false);
-                               }
-
-                               String chapterDisplay = GuiReader.trans(
-                                               StringIdGui.CHAPTER_HTML_UNNAMED, chap.getNumber(),
-                                               story.getChapters().size());
-                               if (chap.getName() != null && !chap.getName().trim().isEmpty()) {
-                                       chapterDisplay = GuiReader.trans(
-                                                       StringIdGui.CHAPTER_HTML_NAMED, chap.getNumber(),
-                                                       story.getChapters().size(), chap.getName());
-                               }
-
-                               return "<HTML>" + chapterDisplay + "</HTML>";
-                       }
-               };
-
-               navbar.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               setChapter(navbar.getIndex());
-                       }
-               });
-
-               JPanel navButtonsPane = new JPanel();
-               LayoutManager layout = new BoxLayout(navButtonsPane, BoxLayout.X_AXIS);
-               navButtonsPane.setLayout(layout);
-
-               add(navbar, BorderLayout.SOUTH);
-       }
-
-       /**
-        * Set the current chapter, 0-based.
-        * <p>
-        * Chapter -1 is reserved for the description page.
-        * 
-        * @param chapter
-        *            the chapter number to set
-        */
-       private void setChapter(int chapter) {
-               Chapter chap;
-               if (chapter < 0) {
-                       chap = meta.getResume();
-                       descPane.setVisible(true);
-               } else {
-                       chap = story.getChapters().get(chapter);
-                       descPane.setVisible(false);
-               }
-
-               mainPanel.setChapter(chap);
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerPanel.java
deleted file mode 100644 (file)
index 0577a0a..0000000
+++ /dev/null
@@ -1,298 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.awt.BorderLayout;
-import java.awt.EventQueue;
-import java.awt.Graphics2D;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.image.BufferedImage;
-
-import javax.swing.Icon;
-import javax.swing.ImageIcon;
-import javax.swing.JButton;
-import javax.swing.JEditorPane;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JProgressBar;
-import javax.swing.JScrollPane;
-import javax.swing.SwingConstants;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.data.Chapter;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.utils.Image;
-import be.nikiroo.utils.ui.ImageUtilsAwt;
-
-/**
- * A {@link JPanel} that will show a {@link Story} chapter on screen.
- * 
- * @author niki
- */
-public class GuiReaderViewerPanel extends JPanel {
-       private static final long serialVersionUID = 1L;
-
-       private boolean imageDocument;
-       private Chapter chap;
-       private JScrollPane scroll;
-       private GuiReaderViewerTextOutput htmlOutput;
-
-       // text only:
-       private JEditorPane text;
-
-       // image only:
-       private JLabel image;
-       private JProgressBar imageProgress;
-       private int currentImage;
-       private JButton left;
-       private JButton right;
-
-       /**
-        * Create a new viewer.
-        * 
-        * @param story
-        *            the {@link Story} to work on
-        */
-       public GuiReaderViewerPanel(Story story) {
-               this(story.getMeta(), story.getMeta().isImageDocument());
-       }
-
-       /**
-        * Create a new viewer.
-        * 
-        * @param meta
-        *            the {@link MetaData} of the story to show
-        * @param isImageDocument
-        *            TRUE if it is an image document, FALSE if not
-        */
-       public GuiReaderViewerPanel(MetaData meta, boolean isImageDocument) {
-               super(new BorderLayout());
-
-               this.imageDocument = isImageDocument;
-
-               this.text = new JEditorPane("text/html", "");
-               text.setEditable(false);
-               text.setAlignmentY(TOP_ALIGNMENT);
-               htmlOutput = new GuiReaderViewerTextOutput();
-
-               image = new JLabel();
-               image.setHorizontalAlignment(SwingConstants.CENTER);
-
-               scroll = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
-                               JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
-               scroll.getVerticalScrollBar().setUnitIncrement(16);
-
-               // TODO:
-               // JButton up = new BasicArrowButton(BasicArrowButton.NORTH);
-               // JButton down = new BasicArrowButton(BasicArrowButton.SOUTH);
-
-               if (!imageDocument) {
-                       add(scroll, BorderLayout.CENTER);
-               } else {
-                       imageProgress = new JProgressBar();
-                       imageProgress.setStringPainted(true);
-                       add(imageProgress, BorderLayout.SOUTH);
-
-                       JPanel main = new JPanel(new BorderLayout());
-                       main.add(scroll, BorderLayout.CENTER);
-
-                       left = new JButton("<HTML>&nbsp; &nbsp; &lt; &nbsp; &nbsp;</HTML>");
-                       left.addActionListener(new ActionListener() {
-                               @Override
-                               public void actionPerformed(ActionEvent e) {
-                                       setImage(--currentImage);
-                               }
-                       });
-                       main.add(left, BorderLayout.WEST);
-
-                       right = new JButton("<HTML>&nbsp; &nbsp; &gt; &nbsp; &nbsp;</HTML>");
-                       right.addActionListener(new ActionListener() {
-                               @Override
-                               public void actionPerformed(ActionEvent e) {
-                                       setImage(++currentImage);
-                               }
-                       });
-                       main.add(right, BorderLayout.EAST);
-
-                       add(main, BorderLayout.CENTER);
-                       main.invalidate();
-               }
-
-               setChapter(meta.getResume());
-       }
-
-       /**
-        * Load the given chapter.
-        * <p>
-        * Will always be text for a non-image document.
-        * <p>
-        * Will be an image and left/right controls for an image-document, except
-        * for chapter 0 which will be text (chapter 0 = resume).
-        * 
-        * @param chap
-        *            the chapter to load
-        */
-       public void setChapter(Chapter chap) {
-               this.chap = chap;
-
-               if (!imageDocument) {
-                       setText(chap);
-               } else {
-                       left.setVisible(chap.getNumber() > 0);
-                       right.setVisible(chap.getNumber() > 0);
-                       imageProgress.setVisible(chap.getNumber() > 0);
-
-                       imageProgress.setMinimum(0);
-                       imageProgress.setMaximum(chap.getParagraphs().size() - 1);
-
-                       if (chap.getNumber() == 0) {
-                               setText(chap);
-                       } else {
-                               setImage(0);
-                       }
-               }
-       }
-
-       /**
-        * Will set and display the current chapter text.
-        * 
-        * @param chap
-        *            the chapter to display
-        */
-       private void setText(final Chapter chap) {
-               new Thread(new Runnable() {
-                       @Override
-                       public void run() {
-                               final String content = htmlOutput.convert(chap);
-                               // Wait until size computations are correct
-                               while (!scroll.isValid()) {
-                                       try {
-                                               Thread.sleep(1);
-                                       } catch (InterruptedException e) {
-                                       }
-                               }
-
-                               setText(content);
-                       }
-               }).start();
-       }
-
-       /**
-        * Actually set the text in the UI.
-        * <p>
-        * Do <b>NOT</b> use this method from the UI thread.
-        * 
-        * @param content
-        *            the text
-        */
-       private void setText(final String content) {
-               EventQueue.invokeLater(new Runnable() {
-                       @Override
-                       public void run() {
-                               text.setText(content);
-                               text.setCaretPosition(0);
-                               scroll.setViewportView(text);
-                       }
-               });
-       }
-
-       /**
-        * Will set and display the current image, take care about the progression
-        * and update the left and right cursors' <tt>enabled</tt> property.
-        * 
-        * @param i
-        *            the image index to load
-        */
-       private void setImage(int i) {
-               left.setEnabled(i > 0);
-               right.setEnabled(i + 1 < chap.getParagraphs().size());
-
-               if (i < 0 || i >= chap.getParagraphs().size()) {
-                       return;
-               }
-
-               imageProgress.setValue(i);
-               imageProgress.setString(GuiReader.trans(StringIdGui.IMAGE_PROGRESSION,
-                               i + 1, chap.getParagraphs().size()));
-
-               currentImage = i;
-
-               final Image img = chap.getParagraphs().get(i).getContentImage();
-
-               // prepare the viewport to get the right sizes later on
-               image.setIcon(null);
-               scroll.setViewportView(image);
-
-               new Thread(new Runnable() {
-                       @Override
-                       public void run() {
-                               // Wait until size computations are correct
-                               while (!scroll.isValid()) {
-                                       try {
-                                               Thread.sleep(1);
-                                       } catch (InterruptedException e) {
-                                       }
-                               }
-
-                               if (img == null) {
-                                       setText("Error: cannot render image.");
-                               } else {
-                                       setImage(img);
-                               }
-                       }
-               }).start();
-       }
-
-       /**
-        * Actually set the image in the UI.
-        * <p>
-        * Do <b>NOT</b> use this method from the UI thread.
-        * 
-        * @param img
-        *            the image to set
-        */
-       private void setImage(Image img) {
-               try {
-                       int scrollWidth = scroll.getWidth()
-                                       - scroll.getVerticalScrollBar().getWidth();
-
-                       BufferedImage buffImg = ImageUtilsAwt.fromImage(img);
-
-                       int iw = buffImg.getWidth();
-                       int ih = buffImg.getHeight();
-                       double ratio = ((double) ih) / iw;
-
-                       int w = scrollWidth;
-                       int h = (int) (ratio * scrollWidth);
-
-                       BufferedImage resizedImage = new BufferedImage(w, h,
-                                       BufferedImage.TYPE_4BYTE_ABGR);
-
-                       Graphics2D g = resizedImage.createGraphics();
-                       try {
-                               g.drawImage(buffImg, 0, 0, w, h, null);
-                       } finally {
-                               g.dispose();
-                       }
-
-                       final Icon icon = new ImageIcon(resizedImage);
-                       EventQueue.invokeLater(new Runnable() {
-                               @Override
-                               public void run() {
-                                       image.setIcon(icon);
-                                       scroll.setViewportView(image);
-                               }
-                       });
-               } catch (Exception e) {
-                       Instance.getInstance().getTraceHandler().error(
-                                       new Exception("Failed to load image into label", e));
-                       EventQueue.invokeLater(new Runnable() {
-                               @Override
-                               public void run() {
-                                       text.setText("Error: cannot load image.");
-                               }
-                       });
-               }
-       }
-}
diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerTextOutput.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderViewerTextOutput.java
deleted file mode 100644 (file)
index fc914dd..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-package be.nikiroo.fanfix.reader.ui;
-
-import java.io.IOException;
-import java.util.Arrays;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.data.Chapter;
-import be.nikiroo.fanfix.data.Paragraph;
-import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.output.BasicOutput;
-
-/**
- * This class can export a chapter into HTML3 code ready for Java Swing support.
- * 
- * @author niki
- */
-public class GuiReaderViewerTextOutput {
-       private StringBuilder builder;
-       private BasicOutput output;
-       private Story fakeStory;
-
-       /**
-        * Create a new {@link GuiReaderViewerTextOutput} that will convert a
-        * {@link Chapter} into HTML3 suited for Java Swing.
-        */
-       public GuiReaderViewerTextOutput() {
-               builder = new StringBuilder();
-               fakeStory = new Story();
-
-               output = new BasicOutput() {
-                       private boolean paraInQuote;
-
-                       @Override
-                       protected void writeChapterHeader(Chapter chap) throws IOException {
-                               builder.append("<HTML>");
-
-                               builder.append("<H1>");
-                               builder.append("Chapter ");
-                               builder.append(chap.getNumber());
-                               builder.append(": ");
-                               builder.append(chap.getName());
-                               builder.append("</H1>");
-
-                               builder.append("<DIV align='justify'>");
-                       }
-
-                       @Override
-                       protected void writeChapterFooter(Chapter chap) throws IOException {
-                               if (paraInQuote) {
-                                       builder.append("</DIV>");
-                               }
-                               paraInQuote = false;
-
-                               builder.append("</DIV>");
-                               builder.append("</HTML>");
-                       }
-
-                       @Override
-                       protected void writeParagraph(Paragraph para) throws IOException {
-                               if ((para.getType() == ParagraphType.QUOTE) == !paraInQuote) {
-                                       paraInQuote = !paraInQuote;
-                                       if (paraInQuote) {
-                                               builder.append("<BR>");
-                                               builder.append("<DIV>");
-                                       } else {
-                                               builder.append("</DIV>");
-                                               builder.append("<BR>");
-                                       }
-                               }
-
-                               switch (para.getType()) {
-                               case NORMAL:
-                                       builder.append("&nbsp;&nbsp;&nbsp;&nbsp;");
-                                       builder.append(decorateText(para.getContent()));
-                                       builder.append("<BR>");
-                                       break;
-                               case BLANK:
-                                       builder.append("<BR><BR>");
-                                       break;
-                               case BREAK:
-                                       builder.append("<BR><P COLOR='#7777DD' ALIGN='CENTER'><B>");
-                                       builder.append("* * *");
-                                       builder.append("</B></P><BR><BR>");
-                                       break;
-                               case QUOTE:
-                                       builder.append("<DIV>");
-                                       builder.append("&nbsp;&nbsp;&nbsp;&nbsp;");
-                                       builder.append("&mdash;&nbsp;");
-                                       builder.append(decorateText(para.getContent()));
-                                       builder.append("</DIV>");
-
-                                       break;
-                               case IMAGE:
-                               }
-                       }
-
-                       @Override
-                       protected String enbold(String word) {
-                               return "<B COLOR='#7777DD'>" + word + "</B>";
-                       }
-
-                       @Override
-                       protected String italize(String word) {
-                               return "<I COLOR='GRAY'>" + word + "</I>";
-                       }
-               };
-       }
-
-       /**
-        * Convert the chapter into HTML3 code.
-        * 
-        * @param chap
-        *            the {@link Chapter} to convert.
-        * 
-        * @return HTML3 code tested with Java Swing
-        */
-       public String convert(Chapter chap) {
-               builder.setLength(0);
-               try {
-                       fakeStory.setChapters(Arrays.asList(chap));
-                       output.process(fakeStory, null, null);
-               } catch (IOException e) {
-                       Instance.getInstance().getTraceHandler().error(e);
-               }
-               return builder.toString();
-       }
-}