Lot of fixes + first (bad, ugly) working GUI fanfix-0.9.3
authorNiki Roo <niki@nikiroo.be>
Tue, 14 Feb 2017 19:33:28 +0000 (20:33 +0100)
committerNiki Roo <niki@nikiroo.be>
Tue, 14 Feb 2017 19:33:28 +0000 (20:33 +0100)
The GUI is now launched at startup if no arguments were passed, too

16 files changed:
README.md
VERSION
src/be/nikiroo/fanfix/Library.java
src/be/nikiroo/fanfix/Main.java
src/be/nikiroo/fanfix/output/Html.java
src/be/nikiroo/fanfix/output/html.style.css [new file with mode: 0644]
src/be/nikiroo/fanfix/reader/BasicReader.java
src/be/nikiroo/fanfix/reader/CliReader.java
src/be/nikiroo/fanfix/reader/LocalReader.java
src/be/nikiroo/fanfix/reader/LocalReaderBook.java [new file with mode: 0644]
src/be/nikiroo/fanfix/reader/LocalReaderFrame.java
src/be/nikiroo/fanfix/supported/BasicSupport.java
src/be/nikiroo/fanfix/supported/Fanfiction.java
src/be/nikiroo/fanfix/supported/Fimfiction.java
src/be/nikiroo/fanfix/supported/InfoReader.java
src/be/nikiroo/fanfix/supported/Text.java

index 92f592938ba8f287211cc78a4d2b20ee2be4f603..6e2d8b00b0e134e9a8c5fc40c2b8acdeff23b29b 100644 (file)
--- a/README.md
+++ b/README.md
@@ -9,7 +9,8 @@ To help organize your stories, it can also work as a local library.
 In this mode, you can:
 - Import a story from its URL (or even another file)
 - Export a story to a file (.epub or .cbz)
-- Display a story from the local library
+- Display a story from the local library in text format in the console
+- Display a story from the local library graphically **by calling a native program to handle it** (though Fanfix can automatically process it into HTML so any browser can open it)
 
 ## Supported websites
 
@@ -81,7 +82,23 @@ Note that calling ```make libs``` will export the libraries into the src/ direct
 ## TODO
 
 Currently missing, but either in progress or planned:
-- A GUI (work in progress)
-- Some readers other than CLI (TUI, GUI)
-- Check if it can work on Android
-- French translation
+- [ ] A GUI (work in progress)
+  - [x] Make one
+  - [x] Make it run when no args passed
+  - [ ] Fix the UI, it is ugly
+  - [ ] Work on the UI thread is BAD
+  - [ ] Allow export
+  - [ ] Show a list of types
+    - [x] ..in the menu
+    - [ ] ..as a screen view
+  - options screen
+- [ ] A TUI reader
+- [ ] Check if it can work on Android
+- [ ] Translations
+  - [x] i18n system in place
+  - [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 ?
+  - [ ] Make a wrapper for firefox to create a new, empty profile ?
+
diff --git a/VERSION b/VERSION
index 2003b639c40025a4216b7b765e800b872a9052cd..965065db5b84c28a60e0e932153c4f59ac44c4da 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.9.2
+0.9.3
index 15b81e44db7aca3caf8869f8f0b53539e1ddb8aa..b5f246c6abe8eaa3f9a034da164ee99f290434c8 100644 (file)
@@ -52,6 +52,23 @@ public class Library {
                dir.mkdirs();
        }
 
+       /**
+        * List all the known types of stories.
+        * 
+        * @return the types
+        */
+       public List<String> getTypes() {
+               List<String> list = new ArrayList<String>();
+               for (Entry<MetaData, File> entry : getStories().entrySet()) {
+                       String storyType = entry.getValue().getParentFile().getName();
+                       if (!list.contains(storyType)) {
+                               list.add(storyType);
+                       }
+               }
+
+               return list;
+       }
+
        /**
         * List all the stories of the given source type in the {@link Library}, or
         * all the stories if NULL is passed as a type.
@@ -61,13 +78,11 @@ public class Library {
         * 
         * @return the stories
         */
-       public List<MetaData> getList(SupportType type) {
-               String typeString = type == null ? null : type.getSourceName();
-
+       public List<MetaData> getList(String type) {
                List<MetaData> list = new ArrayList<MetaData>();
                for (Entry<MetaData, File> entry : getStories().entrySet()) {
                        String storyType = entry.getValue().getParentFile().getName();
-                       if (typeString == null || typeString.equalsIgnoreCase(storyType)) {
+                       if (type == null || type.equalsIgnoreCase(storyType)) {
                                list.add(entry.getKey());
                        }
                }
index 46eb0b5ab9fc08becb2b55924aa9ef2bb6b9f092..1c1bc545d59a88e7aeda74dbe79dfa871437581a 100644 (file)
@@ -22,7 +22,7 @@ import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
  */
 public class Main {
        private enum MainAction {
-               IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, SET_READER
+               IMPORT, EXPORT, CONVERT, READ, READ_URL, LIST, HELP, SET_READER, START,
        }
 
        /**
@@ -46,9 +46,9 @@ public class Main {
         * target</li>
         * <li>--read [id] ([chapter number]): read the given story from the library
         * </li>
-        * <li>--read-url [URL] ([cahpter number]): convert on the fly and read the
+        * <li>--read-url [URL] ([chapter number]): convert on the fly and read the
         * story, without saving it</li>
-        * <li>--list: list the stories present in the library</li>
+        * <li>--list ([type]): list the stories present in the library</li>
         * <li>--set-reader [reader type]: set the reader type to CLI or LOCAL for
         * this command</li>
         * </ul>
@@ -62,7 +62,7 @@ public class Main {
                String typeString = null;
                String chapString = null;
                String target = null;
-               MainAction action = MainAction.HELP;
+               MainAction action = MainAction.START;
                Boolean plusInfo = null;
 
                boolean noMoreActions = false;
@@ -154,6 +154,9 @@ public class Main {
                        case SET_READER:
                                exitCode = setReaderType(args[i]);
                                break;
+                       case START:
+                               exitCode = 255; // not supposed to be selected by user
+                               break;
                        }
                }
 
@@ -184,6 +187,10 @@ public class Main {
                                break;
                        case SET_READER:
                                break;
+                       case START:
+                               BasicReader.setDefaultReaderType(ReaderType.LOCAL);
+                               BasicReader.getReader().start(null);
+                               break;
                        }
                }
 
@@ -232,7 +239,7 @@ public class Main {
         * 
         * @return the exit return code (0 = success)
         */
-       private static int imprt(String urlString) {
+       public static int imprt(String urlString) {
                try {
                        Story story = Instance.getLibrary().imprt(getUrl(urlString));
                        System.out.println(story.getMeta().getLuid() + ": \""
@@ -257,7 +264,7 @@ public class Main {
         * 
         * @return the exit return code (0 = success)
         */
-       private static int export(String luid, String typeString, String target) {
+       public static int export(String luid, String typeString, String target) {
                OutputType type = OutputType.valueOfNullOkUC(typeString);
                if (type == null) {
                        Instance.syserr(new Exception(trans(StringId.OUTPUT_DESC,
@@ -280,23 +287,13 @@ public class Main {
         * is passed, in which case all stories will be listed).
         * 
         * @param typeString
-        *            the {@link SupportType} to list the known stories of, or NULL
-        *            to list all stories
+        *            the type to list the known stories of, or NULL to list all
+        *            stories
         * 
         * @return the exit return code (0 = success)
         */
-       private static int list(String typeString) {
-               SupportType type = null;
-               try {
-                       type = SupportType.valueOfNullOkUC(typeString);
-               } catch (Exception e) {
-                       Instance.syserr(new Exception(
-                                       trans(StringId.INPUT_DESC, typeString), e));
-                       return 1;
-               }
-
+       private static int list(String type) {
                BasicReader.getReader().start(type);
-
                return 0;
        }
 
@@ -379,7 +376,7 @@ public class Main {
                        } else {
                                try {
                                        BasicSupport support = BasicSupport.getSupport(source);
-                                       
+
                                        if (support != null) {
                                                Story story = support.process(source);
 
index 29a0f32b6404e6b1c0822e6e45d11b6783601905..6e0bb6d22d0d0e706e99b7dc605bdc590c1b596b 100644 (file)
@@ -3,16 +3,22 @@ package be.nikiroo.fanfix.output;
 import java.io.File;
 import java.io.FileWriter;
 import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.ImageIO;
 
 import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.bundles.StringId;
+import be.nikiroo.fanfix.bundles.Config;
 import be.nikiroo.fanfix.data.Chapter;
+import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.data.Paragraph;
 import be.nikiroo.fanfix.data.Paragraph.ParagraphType;
 import be.nikiroo.fanfix.data.Story;
+import be.nikiroo.utils.IOUtils;
 import be.nikiroo.utils.StringUtils;
 
 class Html extends BasicOutput {
+       private File dir;
        protected FileWriter writer;
        private boolean inDialogue = false;
        private boolean inNormal = false;
@@ -20,12 +26,13 @@ class Html extends BasicOutput {
        @Override
        public File process(Story story, File targetDir, String targetName)
                        throws IOException {
+               String targetNameOrig = targetName;
+
                File target = new File(targetDir, targetName);
                target.mkdir();
+               dir = target;
 
                targetName = new File(targetName, "index").getPath();
-
-               String targetNameOrig = targetName;
                targetName += getDefaultExtension();
 
                target = new File(targetDir, targetName);
@@ -38,6 +45,13 @@ class Html extends BasicOutput {
                        writer = null;
                }
 
+               String format = Instance.getConfig()
+                               .getString(Config.IMAGE_FORMAT_COVER).toLowerCase();
+               if (story.getMeta().getCover() != null) {
+                       ImageIO.write(story.getMeta().getCover(), format, new File(dir,
+                                       "cover." + format));
+               }
+
                return target;
        }
 
@@ -49,8 +63,39 @@ class Html extends BasicOutput {
        @Override
        protected void writeStoryHeader(Story story) throws IOException {
                String title = "";
+               String tags = "";
+               String author = "";
+               Chapter resume = null;
                if (story.getMeta() != null) {
-                       title = story.getMeta().getTitle();
+                       MetaData meta = story.getMeta();
+                       title = meta.getTitle();
+                       resume = meta.getResume();
+                       if (meta.getTags() != null) {
+                               for (String tag : meta.getTags()) {
+                                       if (!tags.isEmpty()) {
+                                               tags += ", ";
+                                       }
+                                       tags += tag;
+                               }
+
+                               if (!tags.isEmpty()) {
+                                       tags = "(" + tags + ")";
+                               }
+                       }
+                       author = meta.getAuthor();
+               }
+
+               String format = Instance.getConfig()
+                               .getString(Config.IMAGE_FORMAT_COVER).toLowerCase();
+
+               InputStream inStyle = getClass().getResourceAsStream("html.style.css");
+               if (inStyle == null) {
+                       throw new IOException("Cannot find style.css resource");
+               }
+               try {
+                       IOUtils.write(inStyle, new File(dir, "style.css"));
+               } finally {
+                       inStyle.close();
                }
 
                writer.write("<!DOCTYPE html>");
@@ -58,11 +103,39 @@ class Html extends BasicOutput {
                writer.write("\n<head>");
                writer.write("\n        <meta http-equiv='content-type' content='text/html; charset=utf-8'>");
                writer.write("\n        <meta name='viewport' content='width=device-width, initial-scale=1.0'>");
+               writer.write("\n        <link rel='stylesheet' type='text/css' href='style.css'>");
                writer.write("\n        <title>" + StringUtils.xmlEscape(title) + "</title>");
                writer.write("\n</head>");
                writer.write("\n<body>\n");
 
-               writer.write("<h1>" + StringUtils.xmlEscape(title) + "</h1>\n\n");
+               writer.write("\n        <div class=\"titlepage\">");
+               writer.write("\n                <h1>" + StringUtils.xmlEscape(title) + "</h1>");
+               writer.write("\n                        <div class=\"type\">" + StringUtils.xmlEscape(tags)
+                               + "</div>");
+               writer.write("\n                <div class=\"cover\">");
+               writer.write("\n                        <img src=\"cover." + format + "\"></img>");
+               writer.write("\n                </div>");
+               writer.write("\n                <div class=\"author\">"
+                               + StringUtils.xmlEscape(author) + "</div>");
+               writer.write("\n        </div>");
+
+               writer.write("\n        <hr/><br/>");
+
+               if (resume != null) {
+                       for (Paragraph para : resume) {
+                               writeParagraph(para);
+                       }
+                       if (inDialogue) {
+                               writer.write("          </div>\n");
+                               inDialogue = false;
+                       }
+                       if (inNormal) {
+                               writer.write("          </div>\n");
+                               inNormal = false;
+                       }
+               }
+
+               writer.write("\n        <br/>");
        }
 
        @Override
@@ -72,21 +145,40 @@ class Html extends BasicOutput {
 
        @Override
        protected void writeChapterHeader(Chapter chap) throws IOException {
-               String txt;
+               String nameOrNumber;
                if (chap.getName() != null && !chap.getName().isEmpty()) {
-                       txt = Instance.getTrans().getString(StringId.CHAPTER_NAMED,
-                                       chap.getNumber(), chap.getName());
+                       nameOrNumber = chap.getName();
                } else {
-                       txt = Instance.getTrans().getString(StringId.CHAPTER_UNNAMED,
-                                       chap.getNumber());
+                       nameOrNumber = Integer.toString(chap.getNumber());
                }
 
-               writer.write("<h1>" + StringUtils.xmlEscape(txt) + "</h1>\n\n");
+               writer.write("\n        <h2>");
+               writer.write("\n                <span class='chap'>Chapter <span class='chapnumber'>"
+                               + chap.getNumber() + "</span>:</span> ");
+               writer.write("\n                <span class='chaptitle'>"
+                               + StringUtils.xmlEscape(nameOrNumber) + "</span>");
+               writer.write("\n        </h2>");
+               writer.write("\n        ");
+               writer.write("\n        <div class='chapter_content'>\n");
 
                inDialogue = false;
                inNormal = false;
        }
 
+       @Override
+       protected void writeChapterFooter(Chapter chap) throws IOException {
+               if (inDialogue) {
+                       writer.write("          </div>\n");
+                       inDialogue = false;
+               }
+               if (inNormal) {
+                       writer.write("          </div>\n");
+                       inNormal = false;
+               }
+
+               writer.write("\n        </div>");
+       }
+
        @Override
        protected void writeParagraphHeader(Paragraph para) throws IOException {
                if (para.getType() == ParagraphType.QUOTE && !inDialogue) {
diff --git a/src/be/nikiroo/fanfix/output/html.style.css b/src/be/nikiroo/fanfix/output/html.style.css
new file mode 100644 (file)
index 0000000..fd7cdec
--- /dev/null
@@ -0,0 +1,105 @@
+html {
+       text-align: justify;
+       max-width: 800px;
+       margin: auto;
+}
+
+.titlepage {
+       padding-left: 10%;
+       padding-right: 10%;
+       width: 80%;
+}
+
+h1 {
+       padding-bottom: 0;
+       margin-bottom: 0;
+       text-align: left;
+}
+
+.type {
+       position: relative;
+       font-size: large;
+       color: #666666;
+       font-weight: bold;
+       padding-bottom: 10px;
+       text-align: left;
+}
+
+.cover, .page-image {
+       width: 100%;
+}
+
+.cover img {
+       height: 45%;
+       max-width: 100%;
+       margin: auto;
+}
+
+.author {
+       text-align: right;
+       font-size: large;
+       font-style: italic;
+}
+
+.book, .chapter_content {
+       text-indent: 40px;
+       padding-top: 40px;
+       padding-left: 5%;
+       padding-right: 5%;
+       width: 90%;
+}
+
+h2 {
+       border: 1px solid black;
+       color: #222222;
+       padding-left: 10px;
+       padding-right: 10px;
+       display: block;
+       padding-bottom: 0;
+       margin-bottom: 0;
+}
+
+h2 .chap {
+       color: #000000;
+       font-size: large;
+       font-variant: small-caps;
+       display: block;
+}
+
+h2 .chap:first-letter {
+       font-weight: bold;
+}
+
+h2 .chapnumber {
+       color: #000000;
+       font-size: xx-large;
+}
+
+h2 .chaptitle {
+       color: #444444;
+       font-size: large;
+       font-style: italic;
+       padding-bottom: 5px;
+       text-align: right;
+       display: block;
+}
+
+.normals {
+       /* padding-bottom: 20px; */
+       
+}
+
+.normal {
+       /* padding-bottom: 20px; */
+       
+}
+
+.dialogues {
+       /* padding-top: 10px;
+       padding-bottom: 10px; */
+       
+}
+
+.dialogue {
+       font-style: italic;
+}
\ No newline at end of file
index 3403f872a2fbbfc12fe17f646b6792d768fb4513..eabbe7e4408ddf758c623fd65c835e1a3643a95a 100644 (file)
@@ -8,7 +8,6 @@ import be.nikiroo.fanfix.Library;
 import be.nikiroo.fanfix.bundles.Config;
 import be.nikiroo.fanfix.data.Story;
 import be.nikiroo.fanfix.supported.BasicSupport;
-import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
 
 /**
  * The class that handles the different {@link Story} readers you can use.
@@ -147,7 +146,7 @@ public abstract class BasicReader {
         *            the type of {@link Story} to take into account, or NULL for
         *            all
         */
-       public abstract void start(SupportType type);
+       public abstract void start(String type);
 
        /**
         * Return a new {@link BasicReader} ready for use if one is configured.
@@ -157,13 +156,18 @@ public abstract class BasicReader {
         * @return a {@link BasicReader}, or NULL if none configured
         */
        public static BasicReader getReader() {
-               if (defaultType != null) {
-                       switch (defaultType) {
-                       // case LOCAL:
-                       // return new LocalReader().setType(ReaderType.LOCAL);
-                       case CLI:
-                               return new CliReader().setType(ReaderType.CLI);
+               try {
+                       if (defaultType != null) {
+                               switch (defaultType) {
+                               case LOCAL:
+                                       return new LocalReader().setType(ReaderType.LOCAL);
+                               case CLI:
+                                       return new CliReader().setType(ReaderType.CLI);
+                               }
                        }
+               } catch (IOException e) {
+                       Instance.syserr(new Exception("Cannot create a reader of type: "
+                                       + defaultType, e));
                }
 
                return null;
index b3dd3edc79300b28c2ea779c91c887bbbbd9d704..f9e4b700f804780861780a695dfbc98a56f36a60 100644 (file)
@@ -9,7 +9,6 @@ import be.nikiroo.fanfix.data.Chapter;
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.data.Paragraph;
 import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
 
 /**
  * Command line {@link Story} reader.
@@ -75,7 +74,7 @@ class CliReader extends BasicReader {
        }
 
        @Override
-       public void start(SupportType type) {
+       public void start(String type) {
                List<MetaData> stories;
                stories = Instance.getLibrary().getList(type);
 
index 26f48f5a21210fe4d6026b44d0e2bdae3cf3e2c1..bfb8b97276d9d71b9d76782b592409e1c02c8c73 100644 (file)
@@ -10,7 +10,6 @@ import be.nikiroo.fanfix.bundles.Config;
 import be.nikiroo.fanfix.data.MetaData;
 import be.nikiroo.fanfix.data.Story;
 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
-import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
 
 class LocalReader extends BasicReader {
        private Library lib;
@@ -76,8 +75,8 @@ class LocalReader extends BasicReader {
        }
 
        @Override
-       public void start(SupportType type) {
-               final SupportType typeFinal = type;
+       public void start(String type) {
+               final String typeFinal = type;
                EventQueue.invokeLater(new Runnable() {
                        public void run() {
                                new LocalReaderFrame(LocalReader.this, typeFinal)
@@ -85,8 +84,4 @@ class LocalReader extends BasicReader {
                        }
                });
        }
-
-       public static void main(String[] args) throws IOException {
-               new LocalReader().start(null);
-       }
 }
diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderBook.java b/src/be/nikiroo/fanfix/reader/LocalReaderBook.java
new file mode 100644 (file)
index 0000000..7fc8171
--- /dev/null
@@ -0,0 +1,163 @@
+package be.nikiroo.fanfix.reader;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.EventListener;
+import java.util.List;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import be.nikiroo.fanfix.data.MetaData;
+
+/**
+ * A book item presented in a {@link LocalReaderFrame}.
+ * 
+ * @author niki
+ */
+class LocalReaderBook extends JPanel {
+       /**
+        * Action on a book item.
+        * 
+        * @author niki
+        */
+       interface BookActionListner extends EventListener {
+               /**
+                * The book was selected (single click).
+                * 
+                * @param book
+                *            the {@link LocalReaderBook} itself
+                */
+               public void select(LocalReaderBook book);
+
+               /**
+                * The book was double-clicked.
+                * 
+                * @param book
+                *            the {@link LocalReaderBook} itself
+                */
+               public void action(LocalReaderBook book);
+       }
+
+       private static final long serialVersionUID = 1L;
+       private JLabel icon;
+       private JLabel title;
+       private JLabel author;
+       private boolean selected;
+       private boolean hovered;
+       private Date lastClick;
+       private long doubleClickDelay = 200; // in ms
+       private List<BookActionListner> listeners;
+
+       public LocalReaderBook(MetaData meta) {
+               if (meta.getCover() != null) {
+                       BufferedImage resizedImage = new BufferedImage(100, 150,
+                                       BufferedImage.TYPE_4BYTE_ABGR);
+                       Graphics2D g = resizedImage.createGraphics();
+                       g.drawImage(meta.getCover(), 0, 0, 100, 150, null);
+                       g.dispose();
+
+                       icon = new JLabel(new ImageIcon(resizedImage));
+               } else {
+                       icon = new JLabel(" [ no cover ] ");
+               }
+
+               title = new JLabel(meta.getTitle());
+               author = new JLabel("by " + meta.getAuthor());
+
+               this.setLayout(new BorderLayout());
+               this.add(icon, BorderLayout.CENTER);
+               this.add(title, BorderLayout.SOUTH);
+
+               setupListeners();
+               setSelected(false);
+       }
+
+       /**
+        * The book current selection state.
+        * 
+        * @return the selected
+        */
+       public boolean isSelected() {
+               return selected;
+       }
+
+       /**
+        * The book current selection state.
+        * 
+        * @param selected
+        *            the selected to set
+        */
+       public void setSelected(boolean selected) {
+               this.selected = selected;
+               fixColor();
+       }
+
+       private void setHovered(boolean hovered) {
+               this.hovered = hovered;
+               fixColor();
+       }
+
+       private void fixColor() {
+               if (selected && !hovered) {
+                       setBackground(new Color(180, 180, 255));
+               } else if (!selected && hovered) {
+                       setBackground(new Color(230, 230, 255));
+               } else if (selected && hovered) {
+                       setBackground(new Color(200, 200, 255));
+               } else {
+                       setBackground(new Color(255, 255, 255));
+               }
+       }
+
+       private void setupListeners() {
+               listeners = new ArrayList<LocalReaderBook.BookActionListner>();
+               addMouseListener(new MouseListener() {
+                       public void mouseReleased(MouseEvent e) {
+                       }
+
+                       public void mousePressed(MouseEvent e) {
+                       }
+
+                       public void mouseExited(MouseEvent e) {
+                               setHovered(false);
+                       }
+
+                       public void mouseEntered(MouseEvent e) {
+                               setHovered(true);
+                       }
+
+                       public void mouseClicked(MouseEvent e) {
+                               Date now = new Date();
+                               if (lastClick != null
+                                               && now.getTime() - lastClick.getTime() < doubleClickDelay) {
+                                       click(true);
+                               } else {
+                                       click(false);
+                               }
+                               lastClick = now;
+                       }
+               });
+       }
+
+       private void click(boolean doubleClick) {
+               for (BookActionListner listener : listeners) {
+                       if (doubleClick) {
+                               listener.action(this);
+                       } else {
+                               listener.select(this);
+                       }
+               }
+       }
+
+       public void addActionListener(BookActionListner listener) {
+               listeners.add(listener);
+       }
+}
index f196f95e1b8caa38d50f0ed2a2f13e94615f3d37..ed8a039051c8b5a59f3e7228ddd54e814c6fb122 100644 (file)
@@ -4,21 +4,34 @@ import java.awt.Desktop;
 import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowEvent;
+import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
-import javax.swing.JButton;
 import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
 
 import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.Main;
 import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.supported.BasicSupport.SupportType;
+import be.nikiroo.fanfix.reader.LocalReaderBook.BookActionListner;
 
 class LocalReaderFrame extends JFrame {
        private static final long serialVersionUID = 1L;
        private LocalReader reader;
+       private List<MetaData> stories;
+       private List<LocalReaderBook> books;
+       private JPanel bookPane;
+       private String type;
 
-       public LocalReaderFrame(LocalReader reader, SupportType type) {
+       public LocalReaderFrame(LocalReader reader, String type) {
                super("HTML reader");
 
                this.reader = reader;
@@ -27,29 +40,96 @@ class LocalReaderFrame extends JFrame {
                setSize(800, 600);
                setLayout(new FlowLayout());
 
-               // TODO: list all stories, list all TMP stories (and format?)
+               books = new ArrayList<LocalReaderBook>();
+               bookPane = new JPanel();
+               add(bookPane);
 
-               List<MetaData> stories = Instance.getLibrary().getList(type);
-               for (MetaData story : stories) {
-                       JButton button = new JButton(story.getTitle());
-                       final String luid = story.getLuid();
-                       button.addActionListener(new ActionListener() {
-                               public void actionPerformed(ActionEvent e) {
+               refreshBooks(type);
+               setJMenuBar(createMenu());
+
+               setVisible(true);
+       }
+
+       private void refreshBooks(String type) {
+               this.type = type;
+               stories = Instance.getLibrary().getList(type);
+               books.clear();
+               bookPane.removeAll();
+               for (MetaData meta : stories) {
+                       LocalReaderBook book = new LocalReaderBook(meta);
+                       books.add(book);
+                       final String luid = meta.getLuid();
+                       book.addActionListener(new BookActionListner() {
+                               public void select(LocalReaderBook book) {
+                                       for (LocalReaderBook abook : books) {
+                                               abook.setSelected(abook == book);
+                                       }
+                               }
+
+                               public void action(LocalReaderBook book) {
                                        try {
-                                               // TODO: config option (image, non image): TXT,
-                                               // custom-HTML, CBZ, EPUB
-                                               Desktop.getDesktop().browse(
-                                                               LocalReaderFrame.this.reader.getTarget(luid)
-                                                                               .toURI());
-                                       } catch (IOException e1) {
-                                               e1.printStackTrace();
+                                               File target = LocalReaderFrame.this.reader
+                                                               .getTarget(luid);
+                                               Desktop.getDesktop().browse(target.toURI());
+                                       } catch (IOException e) {
+                                               Instance.syserr(e);
                                        }
                                }
                        });
 
-                       add(button);
+                       bookPane.add(book);
                }
 
-               setVisible(true);
+               bookPane.validate();
+               bookPane.repaint();
+       }
+
+       private JMenuBar createMenu() {
+               JMenuBar bar = new JMenuBar();
+
+               JMenu file = new JMenu("File");
+
+               JMenuItem imprt = new JMenuItem("Import", KeyEvent.VK_I);
+               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);
+                               }
+                       }
+               });
+               JMenu types = new JMenu("Type");
+               List<String> tt = Instance.getLibrary().getTypes();
+               tt.add(0, null);
+               for (final String type : tt) {
+                       JMenuItem item = new JMenuItem(type == null ? "[all]" : type);
+                       item.addActionListener(new ActionListener() {
+                               public void actionPerformed(ActionEvent e) {
+                                       refreshBooks(type);
+                               }
+                       });
+                       types.add(item);
+               }
+               JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X);
+               exit.addActionListener(new ActionListener() {
+                       public void actionPerformed(ActionEvent e) {
+                               LocalReaderFrame.this.dispatchEvent(new WindowEvent(
+                                               LocalReaderFrame.this, WindowEvent.WINDOW_CLOSING));
+                       }
+               });
+
+               file.add(imprt);
+               file.add(types);
+               file.addSeparator();
+               file.add(exit);
+
+               bar.add(file);
+
+               return bar;
        }
 }
index ed7c4db1455bbff75c39c2b1fa95369ba8ba3968..61a4500759bccb26366d23ade04f07053c169dcb 100644 (file)
@@ -514,7 +514,7 @@ public abstract class BasicSupport {
                                String line = scan.next().trim();
                                boolean image = false;
                                if (line.startsWith("[") && line.endsWith("]")) {
-                                       URL url = getImageUrl(source,
+                                       URL url = getImageUrl(this, source,
                                                        line.substring(1, line.length() - 1).trim());
                                        if (url != null) {
                                                paras.add(new Paragraph(url));
@@ -582,7 +582,7 @@ public abstract class BasicSupport {
                                && Instance.getCoverDir() != null) {
                        try {
                                File fileCover = new File(Instance.getCoverDir(), subject);
-                               return getImage(fileCover.toURI().toURL(), subject);
+                               return getImage(null, fileCover.toURI().toURL(), subject);
                        } catch (MalformedURLException e) {
                        }
                }
@@ -603,8 +603,8 @@ public abstract class BasicSupport {
                }
        }
 
-       static BufferedImage getImage(URL source, String line) {
-               URL url = getImageUrl(source, line);
+       static BufferedImage getImage(BasicSupport support, URL source, String line) {
+               URL url = getImageUrl(support, source, line);
                if (url != null) {
                        InputStream in = null;
                        try {
@@ -636,7 +636,7 @@ public abstract class BasicSupport {
         * @return the image URL if found, or NULL
         * 
         */
-       static URL getImageUrl(URL source, String line) {
+       static URL getImageUrl(BasicSupport support, URL source, String line) {
                URL url = null;
 
                if (line != null) {
@@ -645,11 +645,11 @@ public abstract class BasicSupport {
                        if (source != null) {
                                path = new File(source.getFile()).getParent();
                                try {
-                                       String urlBase = new File(new File(path), line.trim())
-                                                       .toURI().toURL().toString();
+                                       String basePath = new File(new File(path), line.trim())
+                                                       .getAbsolutePath();
                                        for (String ext : getImageExt(true)) {
-                                               if (new File(urlBase + ext).exists()) {
-                                                       url = new File(urlBase + ext).toURI().toURL();
+                                               if (new File(basePath + ext).exists()) {
+                                                       url = new File(basePath + ext).toURI().toURL();
                                                }
                                        }
                                } catch (Exception e) {
@@ -663,6 +663,7 @@ public abstract class BasicSupport {
                                        for (String ext : getImageExt(true)) {
                                                if (Instance.getCache().check(new URL(line + ext))) {
                                                        url = new URL(line + ext);
+                                                       break;
                                                }
                                        }
 
@@ -671,8 +672,7 @@ public abstract class BasicSupport {
                                                for (String ext : getImageExt(true)) {
                                                        try {
                                                                url = new URL(line + ext);
-                                                               Instance.getCache().refresh(url,
-                                                                               getSupport(url), true);
+                                                               Instance.getCache().refresh(url, support, true);
                                                                break;
                                                        } catch (IOException e) {
                                                                // no image with this ext
@@ -688,7 +688,7 @@ public abstract class BasicSupport {
                        // refresh the cached file
                        if (url != null) {
                                try {
-                                       Instance.getCache().refresh(url, getSupport(url), true);
+                                       Instance.getCache().refresh(url, support, true);
                                } catch (IOException e) {
                                        // woops, broken image
                                        url = null;
index 2cbe696052dc16ec61e3ba333ac50c8180486aaf..5f049e456c7d0b5930c01deef11227be9f7ecab5 100644 (file)
@@ -196,7 +196,7 @@ class Fanfiction extends BasicSupport {
                                                                        + "/" + url.getPath() + "/" + line;
                                                }
 
-                                               return getImage(null, line);
+                                               return getImage(this, null, line);
                                        }
                                }
                        }
@@ -220,39 +220,62 @@ class Fanfiction extends BasicSupport {
                String line = getLine(in, "id=chap_select", 0);
                String key = "<option  value=";
                int i = 1;
-               for (pos = line.indexOf(key); pos >= 0; pos = line.indexOf(key, pos), i++) {
-                       pos = line.indexOf('>', pos);
-                       if (pos >= 0) {
-                               int endOfName = line.indexOf('<', pos);
-                               if (endOfName >= 0) {
-                                       String name = line.substring(pos + 1, endOfName);
-                                       String chapNum = i + ".";
-                                       if (name.startsWith(chapNum)) {
-                                               name = name.substring(chapNum.length(), name.length());
-                                       }
 
-                                       try {
-                                               final String chapName = name.trim();
-                                               final URL chapURL = new URL(base + i + suffix);
-                                               urls.add(new Entry<String, URL>() {
-                                                       public URL setValue(URL value) {
-                                                               return null;
-                                                       }
-
-                                                       public URL getValue() {
-                                                               return chapURL;
-                                                       }
-
-                                                       public String getKey() {
-                                                               return chapName;
-                                                       }
-                                               });
-                                       } catch (MalformedURLException e) {
-                                               Instance.syserr(new IOException("Cannot parse chapter "
-                                                               + i + " url: " + (base + i + suffix), e));
+               if (line != null) {
+                       for (pos = line.indexOf(key); pos >= 0; pos = line
+                                       .indexOf(key, pos), i++) {
+                               pos = line.indexOf('>', pos);
+                               if (pos >= 0) {
+                                       int endOfName = line.indexOf('<', pos);
+                                       if (endOfName >= 0) {
+                                               String name = line.substring(pos + 1, endOfName);
+                                               String chapNum = i + ".";
+                                               if (name.startsWith(chapNum)) {
+                                                       name = name.substring(chapNum.length(),
+                                                                       name.length());
+                                               }
+
+                                               try {
+                                                       final String chapName = name.trim();
+                                                       final URL chapURL = new URL(base + i + suffix);
+                                                       urls.add(new Entry<String, URL>() {
+                                                               public URL setValue(URL value) {
+                                                                       return null;
+                                                               }
+
+                                                               public URL getValue() {
+                                                                       return chapURL;
+                                                               }
+
+                                                               public String getKey() {
+                                                                       return chapName;
+                                                               }
+                                                       });
+                                               } catch (MalformedURLException e) {
+                                                       Instance.syserr(new IOException(
+                                                                       "Cannot parse chapter " + i + " url: "
+                                                                                       + (base + i + suffix), e));
+                                               }
                                        }
                                }
                        }
+               } else {
+                       // only one chapter:
+                       final String chapName = getTitle(reset(in));
+                       final URL chapURL = source;
+                       urls.add(new Entry<String, URL>() {
+                               public URL setValue(URL value) {
+                                       return null;
+                               }
+
+                               public URL getValue() {
+                                       return chapURL;
+                               }
+
+                               public String getKey() {
+                                       return chapName;
+                               }
+                       });
                }
 
                return urls;
index 03a7cc21dc7f94643326e7baa37c35ec860f121f..377e369e1a506cf4eb928dfc793772ab8a46292e 100644 (file)
@@ -172,7 +172,7 @@ class Fimfiction extends BasicSupport {
                        }
                }
 
-               return getImage(null, cover);
+               return getImage(this, null, cover);
        }
 
        @Override
index f84f01b27ec721c971e2dfb18381be57fcaa3df9..c23024059dcae1b4dc8fe6b20b3f9a982f2212ae 100644 (file)
@@ -5,6 +5,7 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -22,7 +23,7 @@ public class InfoReader {
                        InputStream in = new MarkableFileInputStream(new FileInputStream(
                                        infoFile));
                        try {
-                               return createMeta(in);
+                               return createMeta(infoFile.toURI().toURL(), in);
                        } finally {
                                in.close();
                                in = null;
@@ -34,7 +35,8 @@ public class InfoReader {
                }
        }
 
-       private static MetaData createMeta(InputStream in) throws IOException {
+       private static MetaData createMeta(URL sourceInfoFile, InputStream in)
+                       throws IOException {
                MetaData meta = new MetaData();
 
                meta.setTitle(getInfoTag(in, "TITLE"));
@@ -50,7 +52,8 @@ public class InfoReader {
                meta.setSubject(getInfoTag(in, "SUBJECT"));
                meta.setType(getInfoTag(in, "TYPE"));
                meta.setImageDocument(getInfoTagBoolean(in, "IMAGES_DOCUMENT", false));
-               meta.setCover(BasicSupport.getImage(null, getInfoTag(in, "COVER")));
+               meta.setCover(BasicSupport.getImage(null, sourceInfoFile,
+                               getInfoTag(in, "COVER")));
 
                if (meta.getCover() == null) {
                        meta.setCover(BasicSupport.getDefaultCover(meta.getSubject()));
index f7ecadad15f8217dd5d1a1c43887d056f2d383e1..2dd052470278886f30b715f541ca571d7967ae50 100644 (file)
@@ -160,7 +160,7 @@ class Text extends BasicSupport {
                        }
                }
 
-               return getImage(source, path);
+               return getImage(this, source, path);
        }
 
        @Override