Version 1.0.0 fanfix-1.0.0
authorNiki Roo <niki@nikiroo.be>
Fri, 17 Feb 2017 19:49:12 +0000 (20:49 +0100)
committerNiki Roo <niki@nikiroo.be>
Fri, 17 Feb 2017 19:49:12 +0000 (20:49 +0100)
- fixes everywhere
- a  screenshot in the README
- update to nikiroo-utils 1.0.0
- a mostly usable UI, not too ugly any more

20 files changed:
README.md
VERSION
libs/nikiroo-utils-1.0.0-sources.jar [moved from libs/nikiroo-utils-0.9.8-sources.jar with 67% similarity]
screenshots/fanfix.png [new file with mode: 0644]
src/be/nikiroo/fanfix/Instance.java
src/be/nikiroo/fanfix/Library.java
src/be/nikiroo/fanfix/Main.java
src/be/nikiroo/fanfix/bundles/Config.java
src/be/nikiroo/fanfix/bundles/Target.java
src/be/nikiroo/fanfix/bundles/UiConfig.java [new file with mode: 0644]
src/be/nikiroo/fanfix/bundles/UiConfigBundle.java [new file with mode: 0644]
src/be/nikiroo/fanfix/bundles/config.properties
src/be/nikiroo/fanfix/bundles/resources.properties
src/be/nikiroo/fanfix/bundles/ui.properties [new file with mode: 0644]
src/be/nikiroo/fanfix/reader/LocalReader.java
src/be/nikiroo/fanfix/reader/LocalReaderBook.java
src/be/nikiroo/fanfix/reader/LocalReaderFrame.java
src/be/nikiroo/fanfix/reader/WrapLayout.java [deleted file]
src/be/nikiroo/fanfix/supported/BasicSupport.java
src/be/nikiroo/fanfix/supported/Fanfiction.java

index 6e2d8b00b0e134e9a8c5fc40c2b8acdeff23b29b..03f2ce60e521a91426135613cb4659e5531772b5 100644 (file)
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
 
 Fanfix is a small Java program that can download stories from some supported websites and render them offline.
 
 
 Fanfix is a small Java program that can download stories from some supported websites and render them offline.
 
+![Main GUI](screenshots/fanfix.png?raw=true "Main GUI")
+
 It will convert from a (supported) URL to an .epub file for stories or a .cbz file for comics (a few other output types are also available, like Plain Text or LaTeX).
 
 To help organize your stories, it can also work as a local library.
 It will convert from a (supported) URL to an .epub file for stories or a .cbz file for comics (a few other output types are also available, like Plain Text or LaTeX).
 
 To help organize your stories, it can also work as a local library.
@@ -37,16 +39,17 @@ We support a few file types for local story conversion (both as input and as out
 
 Any platform with at lest Java 1.6 on it should be ok.
 
 
 Any platform with at lest Java 1.6 on it should be ok.
 
-If you have any problems to compile it with a supported Java version (1.5 won't work, but you may try to cross-compile or change the Bundle.java class from the utilities; 1.6 and 1.8 have been tested and work), please contact me.
+It has only been tested on Linux and Windows for now, but feel free to inform me if you try it on another system.
+
+If you have any problems to compile it with a supported Java version (1.5 won't work, but you may try to cross-compile; 1.6 and 1.8 have been tested and work), please contact me.
 
 ## Usage
 
 
 ## Usage
 
-You can start the program in CLI mode:
+You can start the program in GUI mode (as in the screenshot on top):
 - ```java -jar fanfix.jar```
 
 - ```java -jar fanfix.jar```
 
-__TODO__: offer a GUI mode (work in progress)
 
 
-The following arguments are allowed:
+The following arguments are also allowed:
 - ```--import [URL]```: import the story at URL into the local library
 - ```--export [id] [output_type] [target]```: export the story denoted by ID to the target file
 - ```--convert [URL] [output_type] [target] (+info)```: convert the story at URL into target, and force-add the .info and cover if +info is passed
 - ```--import [URL]```: import the story at URL into the local library
 - ```--export [id] [output_type] [target]```: export the story denoted by ID to the target file
 - ```--convert [URL] [output_type] [target] (+info)```: convert the story at URL into target, and force-add the .info and cover if +info is passed
@@ -85,7 +88,7 @@ Currently missing, but either in progress or planned:
 - [ ] A GUI (work in progress)
   - [x] Make one
   - [x] Make it run when no args passed
 - [ ] A GUI (work in progress)
   - [x] Make one
   - [x] Make it run when no args passed
-  - [ ] Fix the UI, it is ugly
+  - [x] Fix the UI, it is ugly
   - [ ] Work on the UI thread is BAD
   - [ ] Allow export
   - [ ] Show a list of types
   - [ ] Work on the UI thread is BAD
   - [ ] Allow export
   - [ ] Show a list of types
@@ -99,6 +102,7 @@ Currently missing, but either in progress or planned:
   - [x] Make use of it
   - [x] Use it for all user output (some WIP remains)
   - [ ] French translation
   - [x] Make use of it
   - [x] Use it for all user output (some WIP remains)
   - [ ] French translation
-- [ ] Allow lauching a custom application instead of Desktop.star ?
+- [ ] Allow lauching a custom application instead of Desktop.start ?
   - [ ] Make a wrapper for firefox to create a new, empty profile ?
   - [ ] Make a wrapper for firefox to create a new, empty profile ?
+- [ ] Install a mechanism to handle stories import progress update
 
 
diff --git a/VERSION b/VERSION
index b0bb878545dc6dc410a02b0df8b7ea9fd5705960..3eefcb9dd5b38e2c1dc061052455dd97bcd51e6c 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.9.5
+1.0.0
similarity index 67%
rename from libs/nikiroo-utils-0.9.8-sources.jar
rename to libs/nikiroo-utils-1.0.0-sources.jar
index 3f930a41e0498a9cef074ffc2414e2120d788b81..ab8360ec257451dcbf8b6647bf76043c506d5de7 100644 (file)
Binary files a/libs/nikiroo-utils-0.9.8-sources.jar and b/libs/nikiroo-utils-1.0.0-sources.jar differ
diff --git a/screenshots/fanfix.png b/screenshots/fanfix.png
new file mode 100644 (file)
index 0000000..d4d6c26
Binary files /dev/null and b/screenshots/fanfix.png differ
index 9c20682fd8d6798da64eea8406ddfc86fa1c9dee..cd57efe038532e78d6983e2e2cbd410184e16884 100644 (file)
@@ -6,6 +6,8 @@ import java.io.IOException;
 import be.nikiroo.fanfix.bundles.Config;
 import be.nikiroo.fanfix.bundles.ConfigBundle;
 import be.nikiroo.fanfix.bundles.StringIdBundle;
 import be.nikiroo.fanfix.bundles.Config;
 import be.nikiroo.fanfix.bundles.ConfigBundle;
 import be.nikiroo.fanfix.bundles.StringIdBundle;
+import be.nikiroo.fanfix.bundles.UiConfig;
+import be.nikiroo.fanfix.bundles.UiConfigBundle;
 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
 import be.nikiroo.utils.resources.Bundles;
 
 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
 import be.nikiroo.utils.resources.Bundles;
 
@@ -16,6 +18,7 @@ import be.nikiroo.utils.resources.Bundles;
  */
 public class Instance {
        private static ConfigBundle config;
  */
 public class Instance {
        private static ConfigBundle config;
+       private static UiConfigBundle uiconfig;
        private static StringIdBundle trans;
        private static Cache cache;
        private static Library lib;
        private static StringIdBundle trans;
        private static Cache cache;
        private static Library lib;
@@ -45,6 +48,12 @@ public class Instance {
                        } catch (IOException e) {
                                syserr(e);
                        }
                        } catch (IOException e) {
                                syserr(e);
                        }
+                       try {
+                               uiconfig = new UiConfigBundle();
+                               uiconfig.updateFile(configDir);
+                       } catch (IOException e) {
+                               syserr(e);
+                       }
                        try {
                                trans = new StringIdBundle(getLang());
                                trans.updateFile(configDir);
                        try {
                                trans = new StringIdBundle(getLang());
                                trans.updateFile(configDir);
@@ -55,6 +64,7 @@ public class Instance {
                        Bundles.setDirectory(configDir);
                }
 
                        Bundles.setDirectory(configDir);
                }
 
+               uiconfig = new UiConfigBundle();
                trans = new StringIdBundle(getLang());
                try {
                        lib = new Library(getFile(Config.LIBRARY_DIR),
                trans = new StringIdBundle(getLang());
                try {
                        lib = new Library(getFile(Config.LIBRARY_DIR),
@@ -67,7 +77,7 @@ public class Instance {
                debug = Instance.getConfig().getBoolean(Config.DEBUG_ERR, false);
                coverDir = getFile(Config.DEFAULT_COVERS_DIR);
                File tmp = getFile(Config.CACHE_DIR);
                debug = Instance.getConfig().getBoolean(Config.DEBUG_ERR, false);
                coverDir = getFile(Config.DEFAULT_COVERS_DIR);
                File tmp = getFile(Config.CACHE_DIR);
-               readerTmp = getFile(Config.CACHE_DIR_LOCAL_READER);
+               readerTmp = getFile(UiConfig.CACHE_DIR_LOCAL_READER);
 
                if (checkEnv("NOUTF")) {
                        trans.setUnicode(false);
 
                if (checkEnv("NOUTF")) {
                        trans.setUnicode(false);
@@ -121,6 +131,15 @@ public class Instance {
                return config;
        }
 
                return config;
        }
 
+       /**
+        * Get the (unique) UI configuration service for the program.
+        * 
+        * @return the configuration service
+        */
+       public static UiConfigBundle getUiConfig() {
+               return uiconfig;
+       }
+
        /**
         * Get the (unique) {@link Cache} for the program.
         * 
        /**
         * Get the (unique) {@link Cache} for the program.
         * 
@@ -186,8 +205,25 @@ public class Instance {
         * @return the path
         */
        private static File getFile(Config id) {
         * @return the path
         */
        private static File getFile(Config id) {
+               return getFile(config.getString(id));
+       }
+
+       /**
+        * Return a path, but support the special $HOME variable.
+        * 
+        * @return the path
+        */
+       private static File getFile(UiConfig id) {
+               return getFile(uiconfig.getString(id));
+       }
+
+       /**
+        * Return a path, but support the special $HOME variable.
+        * 
+        * @return the path
+        */
+       private static File getFile(String path) {
                File file = null;
                File file = null;
-               String path = config.getString(id);
                if (path != null && !path.isEmpty()) {
                        path = path.replace('/', File.separatorChar);
                        if (path.contains("$HOME")) {
                if (path != null && !path.isEmpty()) {
                        path = path.replace('/', File.separatorChar);
                        if (path.contains("$HOME")) {
index a8d9302bfe15cc05d87fe11345b11e6e3f3c0284..0d9e067f30dcb6c24fe2e3a1e48fa098464f04e9 100644 (file)
@@ -246,7 +246,7 @@ public class Library {
         * @throws IOException
         *             in case of I/O error
         */
         * @throws IOException
         *             in case of I/O error
         */
-       private Story save(Story story, String luid) throws IOException {
+       public Story save(Story story, String luid) throws IOException {
                // Do not change the original metadata, but change the original story
                MetaData key = story.getMeta().clone();
                story.setMeta(key);
                // Do not change the original metadata, but change the original story
                MetaData key = story.getMeta().clone();
                story.setMeta(key);
index 1c1bc545d59a88e7aeda74dbe79dfa871437581a..f51071d5c82faffd10e7bedd9d5b81c1b593f96f 100644 (file)
@@ -14,6 +14,7 @@ import be.nikiroo.fanfix.reader.BasicReader;
 import be.nikiroo.fanfix.reader.BasicReader.ReaderType;
 import be.nikiroo.fanfix.supported.BasicSupport;
 import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
 import be.nikiroo.fanfix.reader.BasicReader.ReaderType;
 import be.nikiroo.fanfix.supported.BasicSupport;
 import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
+import be.nikiroo.utils.UIUtils;
 
 /**
  * Main program entry point.
 
 /**
  * Main program entry point.
@@ -188,6 +189,7 @@ public class Main {
                        case SET_READER:
                                break;
                        case START:
                        case SET_READER:
                                break;
                        case START:
+                               UIUtils.setLookAndFeel();
                                BasicReader.setDefaultReaderType(ReaderType.LOCAL);
                                BasicReader.getReader().start(null);
                                break;
                                BasicReader.setDefaultReaderType(ReaderType.LOCAL);
                                BasicReader.getReader().start(null);
                                break;
index 86f480d421db9b1345ca9fe4e3a7b21f2c2571d6..48de5b503663a12d1e6a13a347d4c65fbcbc8dd0 100644 (file)
@@ -14,8 +14,6 @@ public enum Config {
        READER_TYPE, //
        @Meta(what = "directory", where = "", format = "absolute path, $HOME variable supported, / is always accepted as dir separator", info = "The directory where to store temporary files, defaults to a directory 'fanfic-tmp' in the system default temporary directory")
        CACHE_DIR, //
        READER_TYPE, //
        @Meta(what = "directory", where = "", format = "absolute path, $HOME variable supported, / is always accepted as dir separator", info = "The directory where to store temporary files, defaults to a directory 'fanfic-tmp' in the system default temporary directory")
        CACHE_DIR, //
-       @Meta(what = "directory", where = "", format = "absolute path, $HOME variable supported, / is always accepted as dir separator", info = "The directory where to store temporary files, defaults to a directory 'fanfic-reader' in the system default temporary directory")
-       CACHE_DIR_LOCAL_READER, //
        @Meta(what = "delay in hours", where = "", format = "integer | 0: no cache | -1: infinite time cache which is default", info = "The delay after which a cached resource that is thought to change ~often is considered too old and triggers a refresh")
        CACHE_MAX_TIME_CHANGING, //
        @Meta(what = "delay in hours", where = "", format = "integer | 0: no cache | -1: infinite time cache which is default", info = "The delay after which a cached resource that is thought to change rarely is considered too old and triggers a refresh")
        @Meta(what = "delay in hours", where = "", format = "integer | 0: no cache | -1: infinite time cache which is default", info = "The delay after which a cached resource that is thought to change ~often is considered too old and triggers a refresh")
        CACHE_MAX_TIME_CHANGING, //
        @Meta(what = "delay in hours", where = "", format = "integer | 0: no cache | -1: infinite time cache which is default", info = "The delay after which a cached resource that is thought to change rarely is considered too old and triggers a refresh")
@@ -46,9 +44,4 @@ public enum Config {
        CHAPTER_EN, //
        @Meta(what = "Chapter identification string", where = "", format = "", info = "used to identify a starting chapter in text mode")
        CHAPTER_FR, //
        CHAPTER_EN, //
        @Meta(what = "Chapter identification string", where = "", format = "", info = "used to identify a starting chapter in text mode")
        CHAPTER_FR, //
-       @Meta(what = "Output type", where = "Local Reader", format = "One of the known output type", info = "The type of output for the Local Reader for non-images documents")
-       LOCAL_READER_NON_IMAGES_DOCUMENT_TYPE, //
-       @Meta(what = "Output type", where = "Local Reader", format = "One of the known output type", info = "The type of output for the Local Reader for images documents")
-       LOCAL_READER_IMAGES_DOCUMENT_TYPE, //
-
 }
 }
index aace98a9f7ca2aa7d1db8a078a50af1a6e2dd906..3318e9e942d4caa4673c820dd229f8ee481b97b7 100644 (file)
@@ -11,9 +11,11 @@ import be.nikiroo.utils.resources.Bundle;
 public enum Target {
        /**
         * Configuration options that the user can change in the
 public enum Target {
        /**
         * Configuration options that the user can change in the
-        * <tt>.properties</tt> file.
+        * <tt>.properties</tt> file
         */
        config,
         */
        config,
-       /** Translation resources. */
+       /** Translation resources */
        resources,
        resources,
+       /** UI resources (from colours to behaviour) */
+       ui,
 }
 }
diff --git a/src/be/nikiroo/fanfix/bundles/UiConfig.java b/src/be/nikiroo/fanfix/bundles/UiConfig.java
new file mode 100644 (file)
index 0000000..ee97491
--- /dev/null
@@ -0,0 +1,19 @@
+package be.nikiroo.fanfix.bundles;
+
+import be.nikiroo.utils.resources.Meta;
+
+/**
+ * The configuration options.
+ * 
+ * @author niki
+ */
+public enum UiConfig {
+       @Meta(what = "directory", where = "", format = "absolute path, $HOME variable supported, / is always accepted as dir separator", info = "The directory where to store temporary files, defaults to a directory 'fanfic-reader' in the system default temporary directory")
+       CACHE_DIR_LOCAL_READER, //
+       @Meta(what = "Output type", where = "Local Reader", format = "One of the known output type", info = "The type of output for the Local Reader for non-images documents")
+       LOCAL_READER_NON_IMAGES_DOCUMENT_TYPE, //
+       @Meta(what = "Output type", where = "Local Reader", format = "One of the known output type", info = "The type of output for the Local Reader for images documents")
+       LOCAL_READER_IMAGES_DOCUMENT_TYPE, //
+       @Meta(what = "A background colour", where = "Local Reader Frame", format = "#rrggbb", info = "The background colour if you don't want the default system one")
+       BACKGROUND_COLOR, //
+}
diff --git a/src/be/nikiroo/fanfix/bundles/UiConfigBundle.java b/src/be/nikiroo/fanfix/bundles/UiConfigBundle.java
new file mode 100644 (file)
index 0000000..2c31d09
--- /dev/null
@@ -0,0 +1,39 @@
+package be.nikiroo.fanfix.bundles;
+
+import java.io.File;
+import java.io.IOException;
+
+import be.nikiroo.utils.resources.Bundle;
+
+/**
+ * This class manages the configuration of UI of the application (colours and
+ * behaviour)
+ * 
+ * @author niki
+ */
+public class UiConfigBundle extends Bundle<UiConfig> {
+       public UiConfigBundle() {
+               super(UiConfig.class, Target.ui);
+       }
+
+       /**
+        * Update resource file.
+        * 
+        * @param args
+        *            not used
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
+       public static void main(String[] args) throws IOException {
+               String path = new File(".").getAbsolutePath()
+                               + "/src/be/nikiroo/fanfix/bundles/";
+               new UiConfigBundle().updateFile(path);
+               System.out.println("Path updated: " + path);
+       }
+
+       @Override
+       protected String getBundleDisplayName() {
+               return "UI configuration options";
+       }
+}
index 535fb32617e0a32e15b912a39ee77007979479f9..c8a9e71a2c1c8df74681eaca24239c8e33b53566 100644 (file)
@@ -11,9 +11,6 @@ READER_TYPE =
 # (WHAT: directory, FORMAT: absolute path, $HOME variable supported, / is always accepted as dir separator)
 # The directory where to store temporary files, defaults to a directory 'fanfic-tmp' in the system default temporary directory
 CACHE_DIR = 
 # (WHAT: directory, FORMAT: absolute path, $HOME variable supported, / is always accepted as dir separator)
 # The directory where to store temporary files, defaults to a directory 'fanfic-tmp' in the system default temporary directory
 CACHE_DIR = 
-# (WHAT: directory, FORMAT: absolute path, $HOME variable supported, / is always accepted as dir separator)
-# The directory where to store temporary files, defaults to a directory 'fanfic-reader' in the system default temporary directory
-CACHE_DIR_LOCAL_READER = 
 # (WHAT: delay in hours, FORMAT: integer | 0: no cache | -1: infinite time cache which is default)
 # The delay after which a cached resource that is thought to change ~often is considered too old and triggers a refresh
 CACHE_MAX_TIME_CHANGING = 24
 # (WHAT: delay in hours, FORMAT: integer | 0: no cache | -1: infinite time cache which is default)
 # The delay after which a cached resource that is thought to change ~often is considered too old and triggers a refresh
 CACHE_MAX_TIME_CHANGING = 24
@@ -59,9 +56,3 @@ CHAPTER_EN = Chapter
 # (WHAT: Chapter identification string)
 # used to identify a starting chapter in text mode
 CHAPTER_FR = Chapitre
 # (WHAT: Chapter identification string)
 # used to identify a starting chapter in text mode
 CHAPTER_FR = Chapitre
-# (WHAT: Output type, WHERE: Local Reader, FORMAT: One of the known output type)
-# The type of output for the Local Reader for non-images documents
-LOCAL_READER_NON_IMAGES_DOCUMENT_TYPE = HTML
-# (WHAT: Output type, WHERE: Local Reader, FORMAT: One of the known output type)
-# The type of output for the Local Reader for images documents
-LOCAL_READER_IMAGES_DOCUMENT_TYPE = CBZ
index 37ecd554999b0b15ac9768c94e0305898ca6c700..afe07f447e6eb08b501fb9535b3678a1f4932ed2 100644 (file)
 # (WHAT: help message, WHERE: cli, FORMAT: %s = supported input, %s = supported output)
 # help message for the syntax
 HELP_SYNTAX = Valid options:\n\
 # (WHAT: help message, WHERE: cli, FORMAT: %s = supported input, %s = supported output)
 # help message for the syntax
 HELP_SYNTAX = Valid options:\n\
-\t--import [URL]: import into library\n\
-\t--export [id] [output_type] [target]: export story to target\n\
-\t--convert [URL] [output_type] [target] (+info): convert URL into target\n\
-\t--read [id] ([chapter number]): read the given story from the library\n\
-\t--read-url [URL] ([chapter number]): convert on the fly and read the story, without saving it\n\
-\t--list: list the stories present in the library\n\
-\t--set-reader [reader type]: set the reader type to CLI or LOCAL for this command\n\
-\t--help: this help message\n\
+t--import [URL]: import into library\n\
+t--export [id] [output_type] [target]: export story to target\n\
+t--convert [URL] [output_type] [target] (+info): convert URL into target\n\
+t--read [id] ([chapter number]): read the given story from the library\n\
+t--read-url [URL] ([chapter number]): convert on the fly and read the story, without saving it\n\
+t--list: list the stories present in the library\n\
+t--set-reader [reader type]: set the reader type to CLI or LOCAL for this command\n\
+t--help: this help message\n\
 \n\
 Supported input types:\n\
 %s\n\
 \n\
 Supported input types:\n\
 %s\n\
@@ -82,11 +82,11 @@ INPUT_DESC_EPUB = EPUB files created by this program (we do not support "all" EP
 # (WHAT: input format description, WHERE: SupportType)
 # Description of this input type
 INPUT_DESC_TEXT = Support class for local stories encoded in textual format, with a few rules :\n\
 # (WHAT: input format description, WHERE: SupportType)
 # Description of this input type
 INPUT_DESC_TEXT = Support class for local stories encoded in textual format, with a few rules :\n\
-\tthe title must be on the first line, \n\
-\tthe author (preceded by nothing, "by " or "©") must be on the second line, possibly with the publication date in parenthesis (i.e., "By Unknown (3rd October 1998)"), \n\
-\tchapters must be declared with "Chapter x" or "Chapter x: NAME OF THE CHAPTER", where "x" is the chapter number,\n\
-\ta description of the story must be given as chapter number 0,\n\
-\ta cover image may be present with the same filename but a PNG, JPEG or JPG extension.
+tthe title must be on the first line, \n\
+tthe author (preceded by nothing, "by " or "©") must be on the second line, possibly with the publication date in parenthesis (i.e., "By Unknown (3rd October 1998)"), \n\
+tchapters must be declared with "Chapter x" or "Chapter x: NAME OF THE CHAPTER", where "x" is the chapter number,\n\
+ta description of the story must be given as chapter number 0,\n\
+ta cover image may be present with the same filename but a PNG, JPEG or JPG extension.
 # (WHAT: input format description, WHERE: SupportType)
 # Description of this input type
 INPUT_DESC_INFO_TEXT = Contains the same information as the TEXT format, but with a companion ".info" file to store some metadata
 # (WHAT: input format description, WHERE: SupportType)
 # Description of this input type
 INPUT_DESC_INFO_TEXT = Contains the same information as the TEXT format, but with a companion ".info" file to store some metadata
@@ -111,11 +111,11 @@ OUTPUT_DESC_EPUB = Standard EPUB file working on most e-book readers and viewers
 # (WHAT: output format description, WHERE: OutputType)
 # Description of this output type
 OUTPUT_DESC_TEXT = Local stories encoded in textual format, with a few rules :\n\
 # (WHAT: output format description, WHERE: OutputType)
 # Description of this output type
 OUTPUT_DESC_TEXT = Local stories encoded in textual format, with a few rules :\n\
-\tthe title must be on the first line, \n\
-\tthe author (preceded by nothing, "by " or "©") must be on the second line, possibly with the publication date in parenthesis (i.e., "By Unknown (3rd October 1998)"), \n\
-\tchapters must be declared with "Chapter x" or "Chapter x: NAME OF THE CHAPTER", where "x" is the chapter number,\n\
-\ta description of the story must be given as chapter number 0,\n\
-\ta cover image may be present with the same filename but a PNG, JPEG or JPG extension.
+tthe title must be on the first line, \n\
+tthe author (preceded by nothing, "by " or "©") must be on the second line, possibly with the publication date in parenthesis (i.e., "By Unknown (3rd October 1998)"), \n\
+tchapters must be declared with "Chapter x" or "Chapter x: NAME OF THE CHAPTER", where "x" is the chapter number,\n\
+ta description of the story must be given as chapter number 0,\n\
+ta cover image may be present with the same filename but a PNG, JPEG or JPG extension.
 # (WHAT: output format description, WHERE: OutputType)
 # Description of this output type
 OUTPUT_DESC_INFO_TEXT = Contains the same information as the TEXT format, but with a companion ".info" file to store some metadata
 # (WHAT: output format description, WHERE: OutputType)
 # Description of this output type
 OUTPUT_DESC_INFO_TEXT = Contains the same information as the TEXT format, but with a companion ".info" file to store some metadata
diff --git a/src/be/nikiroo/fanfix/bundles/ui.properties b/src/be/nikiroo/fanfix/bundles/ui.properties
new file mode 100644 (file)
index 0000000..f0caf3a
--- /dev/null
@@ -0,0 +1,16 @@
+# UI configuration options
+#
+
+
+# (WHAT: directory, FORMAT: absolute path, $HOME variable supported, / is always accepted as dir separator)
+# The directory where to store temporary files, defaults to a directory 'fanfic-reader' in the system default temporary directory
+CACHE_DIR_LOCAL_READER = 
+# (WHAT: Output type, WHERE: Local Reader, FORMAT: One of the known output type)
+# The type of output for the Local Reader for non-images documents
+LOCAL_READER_NON_IMAGES_DOCUMENT_TYPE = HTML
+# (WHAT: Output type, WHERE: Local Reader, FORMAT: One of the known output type)
+# The type of output for the Local Reader for images documents
+LOCAL_READER_IMAGES_DOCUMENT_TYPE = CBZ
+# (WHAT: A background colour, WHERE: Local Reader Frame, FORMAT: #rrggbb)
+# The background colour if you don't want the default system one
+BACKGROUND_COLOR = #FFFFFF
index 86b779c343c0b964d9c80d96493ccd47ec02f7d5..c17e83eaa097531eb423473d544c60eabff92a64 100644 (file)
@@ -6,8 +6,7 @@ import java.io.IOException;
 
 import be.nikiroo.fanfix.Instance;
 import be.nikiroo.fanfix.Library;
 
 import be.nikiroo.fanfix.Instance;
 import be.nikiroo.fanfix.Library;
-import be.nikiroo.fanfix.bundles.Config;
-import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.bundles.UiConfig;
 import be.nikiroo.fanfix.data.Story;
 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
 
 import be.nikiroo.fanfix.data.Story;
 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
 
@@ -23,14 +22,14 @@ class LocalReader extends BasicReader {
                }
 
                // TODO: can throw an exception, manage that (convert to IOEx ?)
                }
 
                // TODO: can throw an exception, manage that (convert to IOEx ?)
-               OutputType text = OutputType.valueOfNullOkUC(Instance.getConfig()
-                               .getString(Config.LOCAL_READER_NON_IMAGES_DOCUMENT_TYPE));
+               OutputType text = OutputType.valueOfNullOkUC(Instance.getUiConfig()
+                               .getString(UiConfig.LOCAL_READER_NON_IMAGES_DOCUMENT_TYPE));
                if (text == null) {
                        text = OutputType.HTML;
                }
 
                if (text == null) {
                        text = OutputType.HTML;
                }
 
-               OutputType images = OutputType.valueOfNullOkUC(Instance.getConfig()
-                               .getString(Config.LOCAL_READER_IMAGES_DOCUMENT_TYPE));
+               OutputType images = OutputType.valueOfNullOkUC(Instance.getUiConfig()
+                               .getString(UiConfig.LOCAL_READER_IMAGES_DOCUMENT_TYPE));
                if (images == null) {
                        images = OutputType.CBZ;
                }
                if (images == null) {
                        images = OutputType.CBZ;
                }
@@ -47,13 +46,12 @@ class LocalReader extends BasicReader {
        public void read(int chapter) {
        }
 
        public void read(int chapter) {
        }
 
-       // return new luid
-       public String imprt(String luid) throws IOException {
+       // keep same luid
+       public void imprt(String luid) throws IOException {
                try {
                        Story story = Instance.getLibrary().getStory(luid);
                        if (story != null) {
                try {
                        Story story = Instance.getLibrary().getStory(luid);
                        if (story != null) {
-                               story = lib.save(story);
-                               return story.getMeta().getLuid();
+                               story = lib.save(story, luid);
                        } else {
                                throw new IOException("Cannot find story in Library: " + luid);
                        }
                        } else {
                                throw new IOException("Cannot find story in Library: " + luid);
                        }
@@ -65,12 +63,10 @@ class LocalReader extends BasicReader {
        }
 
        public File getTarget(String luid) throws IOException {
        }
 
        public File getTarget(String luid) throws IOException {
-               MetaData meta = lib.getInfo(luid);
                File file = lib.getFile(luid);
                if (file == null) {
                File file = lib.getFile(luid);
                if (file == null) {
-                       luid = imprt(luid);
+                       imprt(luid);
                        file = lib.getFile(luid);
                        file = lib.getFile(luid);
-                       meta = lib.getInfo(luid);
                }
 
                return file;
                }
 
                return file;
index ef6ba48588df4cfb9cc778343e1b6f26fd665f0a..8e353d91472cb3b738490ca3c108a92a7de8bc55 100644 (file)
@@ -4,6 +4,7 @@ import java.awt.BorderLayout;
 import java.awt.Color;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
 import java.awt.Color;
 import java.awt.Graphics;
 import java.awt.Graphics2D;
+import java.awt.Polygon;
 import java.awt.Rectangle;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.Rectangle;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
@@ -16,7 +17,6 @@ import java.util.List;
 import javax.swing.ImageIcon;
 import javax.swing.JLabel;
 import javax.swing.JPanel;
 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.MetaData;
 
@@ -49,10 +49,20 @@ class LocalReaderBook extends JPanel {
                public void action(LocalReaderBook book);
        }
 
                public void action(LocalReaderBook book);
        }
 
+       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 int TEXT_WIDTH = COVER_WIDTH + 40;
+       private static final int TEXT_HEIGHT = 50;
+       private static final String AUTHOR_COLOR = "#888888";
+
        private static final long serialVersionUID = 1L;
        private JLabel icon;
        private static final long serialVersionUID = 1L;
        private JLabel icon;
-       private JTextArea title;
-       private JTextArea author;
+       private JLabel tt;
        private boolean selected;
        private boolean hovered;
        private Date lastClick;
        private boolean selected;
        private boolean hovered;
        private Date lastClick;
@@ -61,27 +71,38 @@ class LocalReaderBook extends JPanel {
 
        public LocalReaderBook(MetaData meta) {
                if (meta.getCover() != null) {
 
        public LocalReaderBook(MetaData meta) {
                if (meta.getCover() != null) {
-                       BufferedImage resizedImage = new BufferedImage(100, 150,
+                       BufferedImage resizedImage = new BufferedImage(SPINE_WIDTH
+                                       + COVER_WIDTH, SPINE_HEIGHT + COVER_HEIGHT + HOFFSET,
                                        BufferedImage.TYPE_4BYTE_ABGR);
                        Graphics2D g = resizedImage.createGraphics();
                                        BufferedImage.TYPE_4BYTE_ABGR);
                        Graphics2D g = resizedImage.createGraphics();
-                       g.drawImage(meta.getCover(), 0, 0, 100, 150, null);
+                       g.setColor(Color.white);
+                       g.fillRect(0, HOFFSET, COVER_WIDTH, COVER_HEIGHT);
+                       g.drawImage(meta.getCover(), 0, HOFFSET, COVER_WIDTH, COVER_HEIGHT,
+                                       null);
                        g.dispose();
 
                        icon = new JLabel(new ImageIcon(resizedImage));
                } else {
                        g.dispose();
 
                        icon = new JLabel(new ImageIcon(resizedImage));
                } else {
+                       // TODO: a big black "X" ?
                        icon = new JLabel(" [ no cover ] ");
                }
 
                        icon = new JLabel(" [ no cover ] ");
                }
 
-               title = new JTextArea(meta.getTitle());
-               title.setWrapStyleWord(true);
-               title.setLineWrap(true);
-               title.setEditable(false);
-               title.setBackground(new Color(0, true));
-               author = new JTextArea("by " + meta.getAuthor());
-
-               this.setLayout(new BorderLayout());
+               String optAuthor = meta.getAuthor();
+               if (optAuthor != null && !optAuthor.isEmpty()) {
+                       optAuthor = "(" + optAuthor + ")";
+               }
+               tt = new JLabel(
+                               String.format(
+                                               "<html>"
+                                                               + "<body style='width: %d px; height: %d px; text-align: center'>"
+                                                               + "%s" + "<br>" + "<span style='color: %s;'>"
+                                                               + "%s" + "</span>" + "</body>" + "</html>",
+                                               TEXT_WIDTH, TEXT_HEIGHT, meta.getTitle(), AUTHOR_COLOR,
+                                               optAuthor));
+
+               this.setLayout(new BorderLayout(10, 10));
                this.add(icon, BorderLayout.CENTER);
                this.add(icon, BorderLayout.CENTER);
-               this.add(title, BorderLayout.SOUTH);
+               this.add(tt, BorderLayout.SOUTH);
 
                setupListeners();
                setSelected(false);
 
                setupListeners();
                setSelected(false);
@@ -160,6 +181,23 @@ class LocalReaderBook extends JPanel {
        public void paint(Graphics g) {
                super.paint(g);
 
        public void paint(Graphics g) {
                super.paint(g);
 
+               int h = COVER_HEIGHT;
+               int w = COVER_WIDTH;
+               int xOffset = (TEXT_WIDTH - COVER_WIDTH) - 4;
+
+               int[] xs = new int[] { xOffset, xOffset + SPINE_WIDTH,
+                               xOffset + w + SPINE_WIDTH, xOffset + w };
+               int[] ys = new int[] { HOFFSET + h, HOFFSET + h + SPINE_HEIGHT,
+                               HOFFSET + h + SPINE_HEIGHT, HOFFSET + 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[] { HOFFSET, HOFFSET + SPINE_HEIGHT,
+                               HOFFSET + h + SPINE_HEIGHT, HOFFSET + h };
+               g.setColor(SPINE_COLOR_RIGHT);
+               g.fillPolygon(new Polygon(xs, ys, xs.length));
+
                Color color = new Color(255, 255, 255, 0);
                if (selected && !hovered) {
                        color = new Color(80, 80, 100, 40);
                Color color = new Color(255, 255, 255, 0);
                if (selected && !hovered) {
                        color = new Color(80, 80, 100, 40);
index dd9a8f2e4651ccf83c5fa10de6c09dcf660d5fe1..a7c743cf82858e6af475f25cda6e4b1f244880c2 100644 (file)
@@ -1,6 +1,7 @@
 package be.nikiroo.fanfix.reader;
 
 import java.awt.BorderLayout;
 package be.nikiroo.fanfix.reader;
 
 import java.awt.BorderLayout;
+import java.awt.Color;
 import java.awt.Desktop;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.Desktop;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
@@ -21,8 +22,10 @@ import javax.swing.JScrollPane;
 
 import be.nikiroo.fanfix.Instance;
 import be.nikiroo.fanfix.Main;
 
 import be.nikiroo.fanfix.Instance;
 import be.nikiroo.fanfix.Main;
+import be.nikiroo.fanfix.bundles.UiConfig;
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.reader.LocalReaderBook.BookActionListner;
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.reader.LocalReaderBook.BookActionListner;
+import be.nikiroo.utils.WrapLayout;
 
 class LocalReaderFrame extends JFrame {
        private static final long serialVersionUID = 1L;
 
 class LocalReaderFrame extends JFrame {
        private static final long serialVersionUID = 1L;
@@ -31,9 +34,10 @@ class LocalReaderFrame extends JFrame {
        private List<LocalReaderBook> books;
        private JPanel bookPane;
        private String type;
        private List<LocalReaderBook> books;
        private JPanel bookPane;
        private String type;
+       private Color color;
 
        public LocalReaderFrame(LocalReader reader, String type) {
 
        public LocalReaderFrame(LocalReader reader, String type) {
-               super("HTML reader");
+               super("Fanfix Library");
 
                this.reader = reader;
 
 
                this.reader = reader;
 
@@ -42,9 +46,29 @@ class LocalReaderFrame extends JFrame {
                setLayout(new BorderLayout());
 
                books = new ArrayList<LocalReaderBook>();
                setLayout(new BorderLayout());
 
                books = new ArrayList<LocalReaderBook>();
-               bookPane = new JPanel(new WrapLayout(WrapLayout.LEADING));
+               bookPane = new JPanel(new WrapLayout(WrapLayout.LEADING, 5, 5));
+
+               color = null;
+               String bg = Instance.getUiConfig().getString(UiConfig.BACKGROUND_COLOR);
+               if (bg.startsWith("#") && bg.length() == 7) {
+                       try {
+                               color = new Color(Integer.parseInt(bg.substring(1, 3), 16),
+                                               Integer.parseInt(bg.substring(3, 5), 16),
+                                               Integer.parseInt(bg.substring(5, 7), 16));
+                       } catch (NumberFormatException e) {
+                               color = null; // no changes
+                               e.printStackTrace();
+                       }
+               }
+
+               if (color != null) {
+                       setBackground(color);
+                       bookPane.setBackground(color);
+               }
 
 
-               add(new JScrollPane(bookPane), BorderLayout.CENTER);
+               JScrollPane scroll = new JScrollPane(bookPane);
+               scroll.getVerticalScrollBar().setUnitIncrement(16);
+               add(scroll, BorderLayout.CENTER);
 
                refreshBooks(type);
                setJMenuBar(createMenu());
 
                refreshBooks(type);
                setJMenuBar(createMenu());
@@ -59,6 +83,10 @@ class LocalReaderFrame extends JFrame {
                bookPane.removeAll();
                for (MetaData meta : stories) {
                        LocalReaderBook book = new LocalReaderBook(meta);
                bookPane.removeAll();
                for (MetaData meta : stories) {
                        LocalReaderBook book = new LocalReaderBook(meta);
+                       if (color != null) {
+                               book.setBackground(color);
+                       }
+
                        books.add(book);
                        final String luid = meta.getLuid();
                        book.addActionListener(new BookActionListner() {
                        books.add(book);
                        final String luid = meta.getLuid();
                        book.addActionListener(new BookActionListner() {
@@ -95,13 +123,18 @@ class LocalReaderFrame extends JFrame {
                imprt.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                                String url = JOptionPane.showInputDialog(LocalReaderFrame.this,
                imprt.addActionListener(new ActionListener() {
                        public void actionPerformed(ActionEvent e) {
                                String url = JOptionPane.showInputDialog(LocalReaderFrame.this,
-                                               "url?");
-                               if (Main.imprt(url) != 0) {
-                                       JOptionPane.showMessageDialog(LocalReaderFrame.this,
-                                                       "Cannot import", "Imort error",
-                                                       JOptionPane.ERROR_MESSAGE);
-                               } else {
-                                       refreshBooks(type);
+                                               "url of the story to import?\n" + "\n"
+                                                               + "Note: it will currently make the UI \n"
+                                                               + "unresponsive until it is downloaded...",
+                                               "Importing from URL", JOptionPane.QUESTION_MESSAGE);
+                               if (url != null && !url.isEmpty()) {
+                                       if (Main.imprt(url) != 0) {
+                                               JOptionPane.showMessageDialog(LocalReaderFrame.this,
+                                                               "Cannot import: " + url, "Imort error",
+                                                               JOptionPane.ERROR_MESSAGE);
+                                       } else {
+                                               refreshBooks(type);
+                                       }
                                }
                        }
                });
                                }
                        }
                });
diff --git a/src/be/nikiroo/fanfix/reader/WrapLayout.java b/src/be/nikiroo/fanfix/reader/WrapLayout.java
deleted file mode 100644 (file)
index 3c23a54..0000000
+++ /dev/null
@@ -1,201 +0,0 @@
-package be.nikiroo.fanfix.reader;
-
-import java.awt.Component;
-import java.awt.Container;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.Insets;
-
-import javax.swing.JScrollPane;
-import javax.swing.SwingUtilities;
-
-/**
- * FlowLayout subclass that fully supports wrapping of components.
- * 
- * @author https://tips4java.wordpress.com/2008/11/06/wrap-layout/
- */
-class WrapLayout extends FlowLayout {
-       private static final long serialVersionUID = 1L;
-
-       /**
-        * Constructs a new <code>WrapLayout</code> with a left alignment and a
-        * default 5-unit horizontal and vertical gap.
-        */
-       public WrapLayout() {
-               super();
-       }
-
-       /**
-        * Constructs a new <code>FlowLayout</code> with the specified alignment and
-        * a default 5-unit horizontal and vertical gap. The value of the alignment
-        * argument must be one of <code>WrapLayout</code>, <code>WrapLayout</code>,
-        * or <code>WrapLayout</code>.
-        * 
-        * @param align
-        *            the alignment value
-        */
-       public WrapLayout(int align) {
-               super(align);
-       }
-
-       /**
-        * Creates a new flow layout manager with the indicated alignment and the
-        * indicated horizontal and vertical gaps.
-        * <p>
-        * The value of the alignment argument must be one of
-        * <code>WrapLayout</code>, <code>WrapLayout</code>, or
-        * <code>WrapLayout</code>.
-        * 
-        * @param align
-        *            the alignment value
-        * @param hgap
-        *            the horizontal gap between components
-        * @param vgap
-        *            the vertical gap between components
-        */
-       public WrapLayout(int align, int hgap, int vgap) {
-               super(align, hgap, vgap);
-       }
-
-       /**
-        * Returns the preferred dimensions for this layout given the <i>visible</i>
-        * components in the specified target container.
-        * 
-        * @param target
-        *            the component which needs to be laid out
-        * @return the preferred dimensions to lay out the subcomponents of the
-        *         specified container
-        */
-       @Override
-       public Dimension preferredLayoutSize(Container target) {
-               return layoutSize(target, true);
-       }
-
-       /**
-        * Returns the minimum dimensions needed to layout the <i>visible</i>
-        * components contained in the specified target container.
-        * 
-        * @param target
-        *            the component which needs to be laid out
-        * @return the minimum dimensions to lay out the subcomponents of the
-        *         specified container
-        */
-       @Override
-       public Dimension minimumLayoutSize(Container target) {
-               Dimension minimum = layoutSize(target, false);
-               minimum.width -= (getHgap() + 1);
-               return minimum;
-       }
-
-       /**
-        * Returns the minimum or preferred dimension needed to layout the target
-        * container.
-        *
-        * @param target
-        *            target to get layout size for
-        * @param preferred
-        *            should preferred size be calculated
-        * @return the dimension to layout the target container
-        */
-       private Dimension layoutSize(Container target, boolean preferred) {
-               synchronized (target.getTreeLock()) {
-                       // Each row must fit with the width allocated to the containter.
-                       // When the container width = 0, the preferred width of the
-                       // container
-                       // has not yet been calculated so lets ask for the maximum.
-
-                       int targetWidth = target.getSize().width;
-                       Container container = target;
-
-                       while (container.getSize().width == 0
-                                       && container.getParent() != null) {
-                               container = container.getParent();
-                       }
-
-                       targetWidth = container.getSize().width;
-
-                       if (targetWidth == 0)
-                               targetWidth = Integer.MAX_VALUE;
-
-                       int hgap = getHgap();
-                       int vgap = getVgap();
-                       Insets insets = target.getInsets();
-                       int horizontalInsetsAndGap = insets.left + insets.right
-                                       + (hgap * 2);
-                       int maxWidth = targetWidth - horizontalInsetsAndGap;
-
-                       // Fit components into the allowed width
-
-                       Dimension dim = new Dimension(0, 0);
-                       int rowWidth = 0;
-                       int rowHeight = 0;
-
-                       int nmembers = target.getComponentCount();
-
-                       for (int i = 0; i < nmembers; i++) {
-                               Component m = target.getComponent(i);
-
-                               if (m.isVisible()) {
-                                       Dimension d = preferred ? m.getPreferredSize() : m
-                                                       .getMinimumSize();
-
-                                       // Can't add the component to current row. Start a new row.
-
-                                       if (rowWidth + d.width > maxWidth) {
-                                               addRow(dim, rowWidth, rowHeight);
-                                               rowWidth = 0;
-                                               rowHeight = 0;
-                                       }
-
-                                       // Add a horizontal gap for all components after the first
-
-                                       if (rowWidth != 0) {
-                                               rowWidth += hgap;
-                                       }
-
-                                       rowWidth += d.width;
-                                       rowHeight = Math.max(rowHeight, d.height);
-                               }
-                       }
-
-                       addRow(dim, rowWidth, rowHeight);
-
-                       dim.width += horizontalInsetsAndGap;
-                       dim.height += insets.top + insets.bottom + vgap * 2;
-
-                       // When using a scroll pane or the DecoratedLookAndFeel we need to
-                       // make sure the preferred size is less than the size of the
-                       // target containter so shrinking the container size works
-                       // correctly. Removing the horizontal gap is an easy way to do this.
-
-                       Container scrollPane = SwingUtilities.getAncestorOfClass(
-                                       JScrollPane.class, target);
-
-                       if (scrollPane != null && target.isValid()) {
-                               dim.width -= (hgap + 1);
-                       }
-
-                       return dim;
-               }
-       }
-
-       /*
-        * A new row has been completed. Use the dimensions of this row to update
-        * the preferred size for the container.
-        * 
-        * @param dim update the width and height when appropriate
-        * 
-        * @param rowWidth the width of the row to add
-        * 
-        * @param rowHeight the height of the row to add
-        */
-       private void addRow(Dimension dim, int rowWidth, int rowHeight) {
-               dim.width = Math.max(dim.width, rowWidth);
-
-               if (dim.height > 0) {
-                       dim.height += getVgap();
-               }
-
-               dim.height += rowHeight;
-       }
-}
index b58f8fa380042c6165bd6dbd1d2d28909853a33d..d6801c0dfaf7ba8cb26aff123392196241076d4e 100644 (file)
@@ -763,6 +763,22 @@ public abstract class BasicSupport {
                        boolean singleQ = line.startsWith("" + openQuote);
                        boolean doubleQ = line.startsWith("" + openDoubleQuote);
 
                        boolean singleQ = line.startsWith("" + openQuote);
                        boolean doubleQ = line.startsWith("" + openDoubleQuote);
 
+                       // Do not try when more than one quote at a time
+                       // (some stories are not easily readable if we do)
+                       if (singleQ
+                                       && line.indexOf(closeQuote, 1) < line
+                                                       .lastIndexOf(closeQuote)) {
+                               newParas.add(para);
+                               return newParas;
+                       }
+                       if (doubleQ
+                                       && line.indexOf(closeDoubleQuote, 1) < line
+                                                       .lastIndexOf(closeDoubleQuote)) {
+                               newParas.add(para);
+                               return newParas;
+                       }
+                       //
+
                        if (!singleQ && !doubleQ) {
                                line = openDoubleQuote + line + closeDoubleQuote;
                                newParas.add(new Paragraph(ParagraphType.QUOTE, line));
                        if (!singleQ && !doubleQ) {
                                line = openDoubleQuote + line + closeDoubleQuote;
                                newParas.add(new Paragraph(ParagraphType.QUOTE, line));
index 5f049e456c7d0b5930c01deef11227be9f7ecab5..7d4285b33e86109650e397c1d58358d54a9633a9 100644 (file)
@@ -124,6 +124,8 @@ class Fanfiction extends BasicSupport {
        }
 
        private String getAuthor(InputStream in) {
        }
 
        private String getAuthor(InputStream in) {
+               String author = null;
+
                int i = 0;
                @SuppressWarnings("resource")
                Scanner scan = new Scanner(in, "UTF-8");
                int i = 0;
                @SuppressWarnings("resource")
                Scanner scan = new Scanner(in, "UTF-8");
@@ -132,12 +134,13 @@ class Fanfiction extends BasicSupport {
                        String line = scan.next();
                        if (line.contains("xcontrast_txt")) {
                                if ((++i) == 3) {
                        String line = scan.next();
                        if (line.contains("xcontrast_txt")) {
                                if ((++i) == 3) {
-                                       return StringUtils.unhtml(line).trim();
+                                       author = StringUtils.unhtml(line).trim();
+                                       break;
                                }
                        }
                }
 
                                }
                        }
                }
 
-               return null;
+               return fixAuthor(author);
        }
 
        private String getDate(InputStream in) {
        }
 
        private String getDate(InputStream in) {