From: Niki Roo Date: Fri, 17 Feb 2017 19:49:12 +0000 (+0100) Subject: Version 1.0.0 X-Git-Tag: fanfix-1.0.0 X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=commitdiff_plain;h=b4dc6ab518ded2dd92e4cbb02ac615b1d57e8e6d;hp=3d247bc3bb955a9b85686f0db431157cb9dc4a10 Version 1.0.0 - fixes everywhere - a screenshot in the README - update to nikiroo-utils 1.0.0 - a mostly usable UI, not too ugly any more --- diff --git a/README.md b/README.md index 6e2d8b0..03f2ce6 100644 --- 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 b0bb878..3eefcb9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.5 +1.0.0 diff --git a/libs/nikiroo-utils-0.9.8-sources.jar b/libs/nikiroo-utils-1.0.0-sources.jar similarity index 67% rename from libs/nikiroo-utils-0.9.8-sources.jar rename to libs/nikiroo-utils-1.0.0-sources.jar index 3f930a4..ab8360e 100644 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 index 0000000..d4d6c26 Binary files /dev/null and b/screenshots/fanfix.png differ diff --git a/src/be/nikiroo/fanfix/Instance.java b/src/be/nikiroo/fanfix/Instance.java index 9c20682..cd57efe 100644 --- a/src/be/nikiroo/fanfix/Instance.java +++ b/src/be/nikiroo/fanfix/Instance.java @@ -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")) { diff --git a/src/be/nikiroo/fanfix/Library.java b/src/be/nikiroo/fanfix/Library.java index a8d9302..0d9e067 100644 --- a/src/be/nikiroo/fanfix/Library.java +++ b/src/be/nikiroo/fanfix/Library.java @@ -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); diff --git a/src/be/nikiroo/fanfix/Main.java b/src/be/nikiroo/fanfix/Main.java index 1c1bc54..f51071d 100644 --- a/src/be/nikiroo/fanfix/Main.java +++ b/src/be/nikiroo/fanfix/Main.java @@ -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; diff --git a/src/be/nikiroo/fanfix/bundles/Config.java b/src/be/nikiroo/fanfix/bundles/Config.java index 86f480d..48de5b5 100644 --- a/src/be/nikiroo/fanfix/bundles/Config.java +++ b/src/be/nikiroo/fanfix/bundles/Config.java @@ -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, // - } diff --git a/src/be/nikiroo/fanfix/bundles/Target.java b/src/be/nikiroo/fanfix/bundles/Target.java index aace98a..3318e9e 100644 --- a/src/be/nikiroo/fanfix/bundles/Target.java +++ b/src/be/nikiroo/fanfix/bundles/Target.java @@ -11,9 +11,11 @@ import be.nikiroo.utils.resources.Bundle; public enum Target { /** * Configuration options that the user can change in the - * .properties file. + * .properties 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 index 0000000..ee97491 --- /dev/null +++ b/src/be/nikiroo/fanfix/bundles/UiConfig.java @@ -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 index 0000000..2c31d09 --- /dev/null +++ b/src/be/nikiroo/fanfix/bundles/UiConfigBundle.java @@ -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 { + 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"; + } +} diff --git a/src/be/nikiroo/fanfix/bundles/config.properties b/src/be/nikiroo/fanfix/bundles/config.properties index 535fb32..c8a9e71 100644 --- a/src/be/nikiroo/fanfix/bundles/config.properties +++ b/src/be/nikiroo/fanfix/bundles/config.properties @@ -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 diff --git a/src/be/nikiroo/fanfix/bundles/resources.properties b/src/be/nikiroo/fanfix/bundles/resources.properties index 37ecd55..afe07f4 100644 --- a/src/be/nikiroo/fanfix/bundles/resources.properties +++ b/src/be/nikiroo/fanfix/bundles/resources.properties @@ -10,14 +10,14 @@ # (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 index 0000000..f0caf3a --- /dev/null +++ b/src/be/nikiroo/fanfix/bundles/ui.properties @@ -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 diff --git a/src/be/nikiroo/fanfix/reader/LocalReader.java b/src/be/nikiroo/fanfix/reader/LocalReader.java index 86b779c..c17e83e 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReader.java +++ b/src/be/nikiroo/fanfix/reader/LocalReader.java @@ -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; diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderBook.java b/src/be/nikiroo/fanfix/reader/LocalReaderBook.java index ef6ba48..8e353d9 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderBook.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderBook.java @@ -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( + "" + + "" + + "%s" + "
" + "" + + "%s" + "" + "" + "", + 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); diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java index dd9a8f2..a7c743c 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java @@ -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 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(); - 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 index 3c23a54..0000000 --- a/src/be/nikiroo/fanfix/reader/WrapLayout.java +++ /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 WrapLayout with a left alignment and a - * default 5-unit horizontal and vertical gap. - */ - public WrapLayout() { - super(); - } - - /** - * Constructs a new FlowLayout with the specified alignment and - * a default 5-unit horizontal and vertical gap. The value of the alignment - * argument must be one of WrapLayout, WrapLayout, - * or WrapLayout. - * - * @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. - *

- * The value of the alignment argument must be one of - * WrapLayout, WrapLayout, or - * WrapLayout. - * - * @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 visible - * 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 visible - * 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; - } -} diff --git a/src/be/nikiroo/fanfix/supported/BasicSupport.java b/src/be/nikiroo/fanfix/supported/BasicSupport.java index b58f8fa..d6801c0 100644 --- a/src/be/nikiroo/fanfix/supported/BasicSupport.java +++ b/src/be/nikiroo/fanfix/supported/BasicSupport.java @@ -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)); diff --git a/src/be/nikiroo/fanfix/supported/Fanfiction.java b/src/be/nikiroo/fanfix/supported/Fanfiction.java index 5f049e4..7d4285b 100644 --- a/src/be/nikiroo/fanfix/supported/Fanfiction.java +++ b/src/be/nikiroo/fanfix/supported/Fanfiction.java @@ -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) {