New one-item-per-source-type mode
authorNiki Roo <niki@nikiroo.be>
Sat, 19 Aug 2017 11:15:14 +0000 (13:15 +0200)
committerNiki Roo <niki@nikiroo.be>
Sat, 19 Aug 2017 11:15:14 +0000 (13:15 +0200)
changelog.md
src/be/nikiroo/fanfix/bundles/UiConfig.java
src/be/nikiroo/fanfix/bundles/ui.properties
src/be/nikiroo/fanfix/bundles/ui_description.properties
src/be/nikiroo/fanfix/library/BasicLibrary.java
src/be/nikiroo/fanfix/library/LocalLibrary.java
src/be/nikiroo/fanfix/library/RemoteLibrary.java
src/be/nikiroo/fanfix/reader/GuiReaderBook.java
src/be/nikiroo/fanfix/reader/GuiReaderFrame.java

index a28958edd1f95ff6ccf6442fbd75dc2e99157ede..a4881a1cbe84efc7d49c2f7f71810deb606ef1b0 100644 (file)
@@ -1,5 +1,8 @@
 # Fanfix
 
+## Version WIP
+- New option (disabled by default) to show one item per source type in GUI instead of one item per story when showing ALL sources (which is also the start page)
+
 ## Version 1.6.0
 
 - TUI (text with windows and menus) -- not compiled by default (configure.sh)
index e83a46067324339ee2f592e06093da984448ccba..271abb3ec8e981957da2ebb4cdfb1ee596a63275 100644 (file)
@@ -22,4 +22,6 @@ public enum UiConfig {
        NON_IMAGES_DOCUMENT_READER, //
        @Meta(format = Format.COLOR, description = "The background colour if you don't want the default system one")
        BACKGROUND_COLOR, //
+       @Meta(format = Format.BOOLEAN, description = "Show one item per source type when in ALL sources mode instead of one per story")
+       SOURCE_PAGE, //
 }
index 5f251c5fbf889f4c0bda358f335decefde408b3e..d98410a8da5c9130a00e779c59a60bc8d027a961 100644 (file)
@@ -22,3 +22,6 @@ NON_IMAGES_DOCUMENT_READER =
 # The background colour if you don't want the default system one
 # (FORMAT: COLOR) 
 BACKGROUND_COLOR = #FFFFFF
+# Show one item per source type when in ALL sources mode instead of one per story
+# (FORMAT: BOOLEAN) 
+SOURCE_PAGE = false
index 4b0792aacb67dd6b2bf62e07292e783912378eb7..147a5a6f3353a1036d57501a8ad72bb63b02aeb5 100644 (file)
@@ -27,3 +27,6 @@ NON_IMAGES_DOCUMENT_READER =
 # The background colour if you don't want the default system one
 # (FORMAT: COLOR) 
 BACKGROUND_COLOR = 
+# Show one item per source type when in ALL sources mode instead of one per story
+# (FORMAT: BOOLEAN) 
+SOURCE_PAGE = 
index de3dbb64890dd53df4bad285c198693e8f061348..63ffdb655bafa658b4bad2dcec37e5671fd4d867 100644 (file)
@@ -52,6 +52,35 @@ abstract public class BasicLibrary {
         */
        public abstract BufferedImage getCover(String luid);
 
+       /**
+        * Return the cover image associated to this source.
+        * <p>
+        * By default, return the cover of the first story with this source.
+        * 
+        * @param source
+        *            the source
+        * 
+        * @return the cover image or NULL
+        */
+       public BufferedImage getSourceCover(String source) {
+               List<MetaData> metas = getListBySource(source);
+               if (metas.size() > 0) {
+                       return getCover(metas.get(0).getLuid());
+               }
+
+               return null;
+       }
+
+       /**
+        * Fix the source cover to the given story cover.
+        * 
+        * @param source
+        *            the source to change
+        * @param luid
+        *            the story LUID
+        */
+       public abstract void setSourceCover(String source, String luid);
+
        /**
         * Return the list of stories (represented by their {@link MetaData}, which
         * <b>MAY</b> not have the cover included).
@@ -401,7 +430,7 @@ abstract public class BasicLibrary {
                if (getInfo(luid) != null) {
                        delete(luid);
                }
-               
+
                doSave(story, pg);
 
                clearCache();
index d7fd5212ed8bddaec5afca42179f37c9b4d80ee6..bc41157328e7a0656b065c7b45ba2afc07872ba0 100644 (file)
@@ -3,12 +3,16 @@ package be.nikiroo.fanfix.library;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.FileFilter;
+import java.io.FileInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import javax.imageio.ImageIO;
+
 import be.nikiroo.fanfix.Instance;
 import be.nikiroo.fanfix.bundles.Config;
 import be.nikiroo.fanfix.data.MetaData;
@@ -18,6 +22,8 @@ import be.nikiroo.fanfix.output.BasicOutput.OutputType;
 import be.nikiroo.fanfix.output.InfoCover;
 import be.nikiroo.fanfix.supported.InfoReader;
 import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.ImageUtils;
+import be.nikiroo.utils.MarkableFileInputStream;
 import be.nikiroo.utils.Progress;
 
 /**
@@ -28,6 +34,7 @@ import be.nikiroo.utils.Progress;
 public class LocalLibrary extends BasicLibrary {
        private int lastId;
        private Map<MetaData, File[]> stories; // Files: [ infoFile, TargetFile ]
+       private Map<String, BufferedImage> sourceCovers;
 
        private File baseDir;
        private OutputType text;
@@ -50,6 +57,7 @@ public class LocalLibrary extends BasicLibrary {
 
                this.lastId = 0;
                this.stories = null;
+               this.sourceCovers = new HashMap<String, BufferedImage>();
 
                baseDir.mkdirs();
        }
@@ -92,10 +100,12 @@ public class LocalLibrary extends BasicLibrary {
        @Override
        protected void clearCache() {
                stories = null;
+               sourceCovers = new HashMap<String, BufferedImage>();
        }
 
        @Override
        protected synchronized int getNextId() {
+               getStories(null); // make sure lastId is set
                return ++lastId;
        }
 
@@ -150,6 +160,27 @@ public class LocalLibrary extends BasicLibrary {
                clearCache();
        }
 
+       @Override
+       public BufferedImage getSourceCover(String source) {
+               if (!sourceCovers.containsKey(source)) {
+                       sourceCovers.put(source, super.getSourceCover(source));
+               }
+
+               return sourceCovers.get(source);
+       }
+
+       @Override
+       public void setSourceCover(String source, String luid) {
+               sourceCovers.put(source, getCover(luid));
+               File cover = new File(getExpectedDir(source), ".cover.png");
+               try {
+                       ImageIO.write(sourceCovers.get(source), "png", cover);
+               } catch (IOException e) {
+                       Instance.syserr(e);
+                       sourceCovers.remove(source);
+               }
+       }
+
        /**
         * Return the {@link OutputType} for this {@link Story}.
         * 
@@ -324,10 +355,12 @@ public class LocalLibrary extends BasicLibrary {
                                pgDirs.addProgress(pgFiles, 100);
                                pgDirs.setName("Loading from: " + dir.getName());
 
+                               String source = null;
                                for (File infoFile : infoFiles) {
                                        pgFiles.setName(infoFile.getName());
                                        try {
                                                MetaData meta = InfoReader.readMeta(infoFile, false);
+                                               source = meta.getSource();
                                                try {
                                                        int id = Integer.parseInt(meta.getLuid());
                                                        if (id > lastId) {
@@ -354,6 +387,21 @@ public class LocalLibrary extends BasicLibrary {
                                        pgFiles.add(1);
                                }
 
+                               File cover = new File(dir, ".cover.png");
+                               if (cover.exists()) {
+                                       try {
+                                               InputStream in = new MarkableFileInputStream(
+                                                               new FileInputStream(cover));
+                                               try {
+                                                       sourceCovers.put(source, ImageUtils.fromStream(in));
+                                               } finally {
+                                                       in.close();
+                                               }
+                                       } catch (IOException e) {
+                                               Instance.syserr(e);
+                                       }
+                               }
+
                                pgFiles.setName(null);
                        }
 
index 511d1c1580cbf607069ab4c1b6fb34b6a1602843..83766396499fa783712b0558e51c3ad63def314e 100644 (file)
@@ -153,4 +153,10 @@ public class RemoteLibrary extends BasicLibrary {
                throw new java.lang.InternalError(
                                "No write support allowed on remote Libraries");
        }
+
+       @Override
+       public void setSourceCover(String source, String luid) {
+               throw new java.lang.InternalError(
+                               "No write support allowed on remote Libraries");
+       }
 }
index 6475bd9736cf2ebe975a92295f2513317bf386ed..29f4c49783a0a727d7c9f08011ee5c8408617c9c 100644 (file)
@@ -103,7 +103,7 @@ class GuiReaderBook extends JPanel {
         * @param reader
         *            the associated reader
         * @param meta
-        *            the story {@link MetaData}
+        *            the story {@link MetaData} or source (if no LUID)
         * @param cached
         *            TRUE if it is locally cached
         * @param seeWordCount
@@ -128,6 +128,8 @@ class GuiReaderBook extends JPanel {
 
                if (optSecondary != null && !optSecondary.isEmpty()) {
                        optSecondary = "(" + optSecondary + ")";
+               } else {
+                       optSecondary = "";
                }
 
                icon = new JLabel(generateCoverIcon(meta));
@@ -362,31 +364,39 @@ class GuiReaderBook extends JPanel {
         * Generate a cover icon based upon the given {@link MetaData}.
         * 
         * @param meta
-        *            the {@link MetaData} about the target {@link Story}
+        *            the {@link MetaData} about the target {@link Story} or source
+        *            (if no LUID)
         * 
         * @return the icon
         */
        private ImageIcon generateCoverIcon(MetaData meta) {
-               String id = meta.getUuid() + ".thumb_" + SPINE_WIDTH + "x"
-                               + COVER_WIDTH + "+" + SPINE_HEIGHT + "+" + COVER_HEIGHT + "@"
-                               + HOFFSET;
                BufferedImage resizedImage = null;
-
-               InputStream in = Instance.getCache().getFromCache(id);
-               if (in != null) {
-                       try {
-                               resizedImage = ImageUtils.fromStream(in);
-                               in.close();
-                               in = null;
-                       } catch (IOException e) {
-                               Instance.syserr(e);
+               String id = null;
+
+               if (meta.getLuid() != null) {
+                       id = meta.getUuid() + ".thumb_" + SPINE_WIDTH + "x" + COVER_WIDTH
+                                       + "+" + SPINE_HEIGHT + "+" + COVER_HEIGHT + "@" + HOFFSET;
+                       InputStream in = Instance.getCache().getFromCache(id);
+                       if (in != null) {
+                               try {
+                                       resizedImage = ImageUtils.fromStream(in);
+                                       in.close();
+                                       in = null;
+                               } catch (IOException e) {
+                                       Instance.syserr(e);
+                               }
                        }
                }
 
                if (resizedImage == null) {
                        try {
-                               BufferedImage cover = reader.getLibrary().getCover(
-                                               meta.getLuid());
+                               BufferedImage cover = null;
+                               if (meta.getLuid() == null) {
+                                       cover = reader.getLibrary()
+                                                       .getSourceCover(meta.getSource());
+                               } else {
+                                       cover = reader.getLibrary().getCover(meta.getLuid());
+                               }
 
                                resizedImage = new BufferedImage(SPINE_WIDTH + COVER_WIDTH,
                                                SPINE_HEIGHT + COVER_HEIGHT + HOFFSET,
@@ -404,13 +414,15 @@ class GuiReaderBook extends JPanel {
                                }
                                g.dispose();
 
-                               ByteArrayOutputStream out = new ByteArrayOutputStream();
-                               ImageIO.write(resizedImage, "png", out);
-                               byte[] imageBytes = out.toByteArray();
-                               in = new ByteArrayInputStream(imageBytes);
-                               Instance.getCache().addToCache(in, id);
-                               in.close();
-                               in = null;
+                               if (id != null) {
+                                       ByteArrayOutputStream out = new ByteArrayOutputStream();
+                                       ImageIO.write(resizedImage, "png", out);
+                                       byte[] imageBytes = out.toByteArray();
+                                       InputStream in = new ByteArrayInputStream(imageBytes);
+                                       Instance.getCache().addToCache(in, id);
+                                       in.close();
+                                       in = null;
+                               }
                        } catch (MalformedURLException e) {
                                Instance.syserr(e);
                        } catch (IOException e) {
index e88c9777ee3afc01ab27efb2cb8595991a4c4f90..3b5fca34bc9de09aeb2dadea9d67a3889dad9838 100644 (file)
@@ -141,6 +141,49 @@ class GuiReaderFrame extends JFrame {
                setVisible(true);
        }
 
+       private void addSourcePanes() {
+               // Sources -> i18n
+               GuiReaderGroup bookPane = new GuiReaderGroup(reader, "Sources", color);
+
+               List<MetaData> sources = new ArrayList<MetaData>();
+               for (String source : reader.getLibrary().getSources()) {
+                       MetaData mSource = new MetaData();
+                       mSource.setLuid(null);
+                       mSource.setTitle(source);
+                       mSource.setSource(source);
+                       sources.add(mSource);
+               }
+
+               bookPane.refreshBooks(sources, false);
+
+               this.invalidate();
+               pane.invalidate();
+               pane.add(bookPane);
+               pane.validate();
+               this.validate();
+
+               bookPane.setActionListener(new BookActionListener() {
+                       @Override
+                       public void select(GuiReaderBook book) {
+                               selectedBook = book;
+                       }
+
+                       @Override
+                       public void popupRequested(GuiReaderBook book, MouseEvent e) {
+                               JPopupMenu popup = new JPopupMenu();
+                               popup.add(createMenuItemOpenBook());
+                               popup.show(e.getComponent(), e.getX(), e.getY());
+                       }
+
+                       @Override
+                       public void action(final GuiReaderBook book) {
+                               removeBookPanes();
+                               addBookPane(book.getMeta().getSource(), true);
+                               refreshBooks();
+                       }
+               });
+       }
+
        /**
         * Add a new {@link GuiReaderGroup} on the frame to display the books of the
         * selected type or author.
@@ -154,9 +197,14 @@ class GuiReaderFrame extends JFrame {
        private void addBookPane(String value, boolean type) {
                if (value == null) {
                        if (type) {
-                               for (String tt : reader.getLibrary().getSources()) {
-                                       if (tt != null) {
-                                               addBookPane(tt, type);
+                               if (Instance.getUiConfig().getBoolean(UiConfig.SOURCE_PAGE,
+                                               false)) {
+                                       addSourcePanes();
+                               } else {
+                                       for (String tt : reader.getLibrary().getSources()) {
+                                               if (tt != null) {
+                                                       addBookPane(tt, type);
+                                               }
                                        }
                                }
                        } else {
@@ -196,6 +244,7 @@ class GuiReaderFrame extends JFrame {
                                popup.addSeparator();
                                popup.add(createMenuItemExport());
                                popup.add(createMenuItemMove());
+                               popup.add(createMenuItemSetCover());
                                popup.add(createMenuItemClearCache());
                                popup.add(createMenuItemRedownload());
                                popup.addSeparator();
@@ -584,7 +633,7 @@ class GuiReaderFrame extends JFrame {
                                                                        "Moving story",
                                                                        JOptionPane.QUESTION_MESSAGE, null, null,
                                                                        selectedBook.getMeta().getSource());
-                                                       
+
                                                        if (rep == null) {
                                                                return;
                                                        }
@@ -669,7 +718,7 @@ class GuiReaderFrame extends JFrame {
        }
 
        /**
-        * Create the open menu item.
+        * Create the open menu item for a book or a source (no LUID).
         * 
         * @return the item
         */
@@ -679,7 +728,35 @@ class GuiReaderFrame extends JFrame {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                                if (selectedBook != null) {
-                                       openBook(selectedBook);
+                                       if (selectedBook.getMeta().getLuid() == null) {
+                                               removeBookPanes();
+                                               addBookPane(selectedBook.getMeta().getSource(), true);
+                                               refreshBooks();
+                                       } else {
+                                               openBook(selectedBook);
+                                       }
+                               }
+                       }
+               });
+
+               return open;
+       }
+
+       /**
+        * Create the SetCover menu item for a book to change the linked source
+        * cover.
+        * 
+        * @return the item
+        */
+       private JMenuItem createMenuItemSetCover() {
+               JMenuItem open = new JMenuItem("Set as cover for source", KeyEvent.VK_C);
+               open.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               if (selectedBook != null) {
+                                       reader.getLibrary().setSourceCover(
+                                                       selectedBook.getMeta().getSource(),
+                                                       selectedBook.getMeta().getLuid());
                                }
                        }
                });