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.
 
+![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.
@@ -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.
 
-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
 
-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```
 
-__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
@@ -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
-  - [ ] 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
@@ -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
-- [ ] 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 ?
+- [ ] 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.UiConfig;
+import be.nikiroo.fanfix.bundles.UiConfigBundle;
 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;
+       private static UiConfigBundle uiconfig;
        private static StringIdBundle trans;
        private static Cache cache;
        private static Library lib;
@@ -45,6 +48,12 @@ public class Instance {
                        } 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);
@@ -55,6 +64,7 @@ public class Instance {
                        Bundles.setDirectory(configDir);
                }
 
+               uiconfig = new UiConfigBundle();
                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);
-               readerTmp = getFile(Config.CACHE_DIR_LOCAL_READER);
+               readerTmp = getFile(UiConfig.CACHE_DIR_LOCAL_READER);
 
                if (checkEnv("NOUTF")) {
                        trans.setUnicode(false);
@@ -121,6 +131,15 @@ public class Instance {
                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.
         * 
@@ -186,8 +205,25 @@ public class Instance {
         * @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;
-               String path = config.getString(id);
                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
         */
-       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);
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.utils.UIUtils;
 
 /**
  * Main program entry point.
@@ -188,6 +189,7 @@ public class Main {
                        case SET_READER:
                                break;
                        case START:
+                               UIUtils.setLookAndFeel();
                                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, //
-       @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")
@@ -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, //
-       @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
-        * <tt>.properties</tt> file.
+        * <tt>.properties</tt> file
         */
        config,
-       /** Translation resources. */
+       /** Translation 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-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
@@ -59,9 +56,3 @@ CHAPTER_EN = Chapter
 # (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\
-\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\
@@ -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\
-\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
@@ -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\
-\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
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.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;
 
@@ -23,14 +22,14 @@ class LocalReader extends BasicReader {
                }
 
                // 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;
                }
 
-               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;
                }
@@ -47,13 +46,12 @@ class LocalReader extends BasicReader {
        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) {
-                               story = lib.save(story);
-                               return story.getMeta().getLuid();
+                               story = lib.save(story, 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 {
-               MetaData meta = lib.getInfo(luid);
                File file = lib.getFile(luid);
                if (file == null) {
-                       luid = imprt(luid);
+                       imprt(luid);
                        file = lib.getFile(luid);
-                       meta = lib.getInfo(luid);
                }
 
                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.Polygon;
 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.JTextArea;
 
 import be.nikiroo.fanfix.data.MetaData;
 
@@ -49,10 +49,20 @@ class LocalReaderBook extends JPanel {
                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 JTextArea title;
-       private JTextArea author;
+       private JLabel tt;
        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) {
-                       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();
-                       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 {
+                       // TODO: a big black "X" ?
                        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(title, BorderLayout.SOUTH);
+               this.add(tt, BorderLayout.SOUTH);
 
                setupListeners();
                setSelected(false);
@@ -160,6 +181,23 @@ class LocalReaderBook extends JPanel {
        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);
index dd9a8f2e4651ccf83c5fa10de6c09dcf660d5fe1..a7c743cf82858e6af475f25cda6e4b1f244880c2 100644 (file)
@@ -1,6 +1,7 @@
 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;
@@ -21,8 +22,10 @@ import javax.swing.JScrollPane;
 
 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.utils.WrapLayout;
 
 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 Color color;
 
        public LocalReaderFrame(LocalReader reader, String type) {
-               super("HTML reader");
+               super("Fanfix Library");
 
                this.reader = reader;
 
@@ -42,9 +46,29 @@ class LocalReaderFrame extends JFrame {
                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());
@@ -59,6 +83,10 @@ class LocalReaderFrame extends JFrame {
                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() {
@@ -95,13 +123,18 @@ class LocalReaderFrame extends JFrame {
                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);
 
+                       // 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));
index 5f049e456c7d0b5930c01deef11227be9f7ecab5..7d4285b33e86109650e397c1d58358d54a9633a9 100644 (file)
@@ -124,6 +124,8 @@ class Fanfiction extends BasicSupport {
        }
 
        private String getAuthor(InputStream in) {
+               String author = null;
+
                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) {
-                                       return StringUtils.unhtml(line).trim();
+                                       author = StringUtils.unhtml(line).trim();
+                                       break;
                                }
                        }
                }
 
-               return null;
+               return fixAuthor(author);
        }
 
        private String getDate(InputStream in) {