From 14b574483b51d3859acef6a269f8841b5a4eb5f8 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Sat, 19 Aug 2017 13:15:14 +0200 Subject: [PATCH] New one-item-per-source-type mode --- changelog.md | 3 + src/be/nikiroo/fanfix/bundles/UiConfig.java | 2 + src/be/nikiroo/fanfix/bundles/ui.properties | 3 + .../fanfix/bundles/ui_description.properties | 3 + .../nikiroo/fanfix/library/BasicLibrary.java | 31 ++++++- .../nikiroo/fanfix/library/LocalLibrary.java | 48 ++++++++++ .../nikiroo/fanfix/library/RemoteLibrary.java | 6 ++ .../nikiroo/fanfix/reader/GuiReaderBook.java | 58 +++++++----- .../nikiroo/fanfix/reader/GuiReaderFrame.java | 89 +++++++++++++++++-- 9 files changed, 213 insertions(+), 30 deletions(-) diff --git a/changelog.md b/changelog.md index a28958e..a4881a1 100644 --- a/changelog.md +++ b/changelog.md @@ -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) diff --git a/src/be/nikiroo/fanfix/bundles/UiConfig.java b/src/be/nikiroo/fanfix/bundles/UiConfig.java index e83a460..271abb3 100644 --- a/src/be/nikiroo/fanfix/bundles/UiConfig.java +++ b/src/be/nikiroo/fanfix/bundles/UiConfig.java @@ -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, // } diff --git a/src/be/nikiroo/fanfix/bundles/ui.properties b/src/be/nikiroo/fanfix/bundles/ui.properties index 5f251c5..d98410a 100644 --- a/src/be/nikiroo/fanfix/bundles/ui.properties +++ b/src/be/nikiroo/fanfix/bundles/ui.properties @@ -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 diff --git a/src/be/nikiroo/fanfix/bundles/ui_description.properties b/src/be/nikiroo/fanfix/bundles/ui_description.properties index 4b0792a..147a5a6 100644 --- a/src/be/nikiroo/fanfix/bundles/ui_description.properties +++ b/src/be/nikiroo/fanfix/bundles/ui_description.properties @@ -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 = diff --git a/src/be/nikiroo/fanfix/library/BasicLibrary.java b/src/be/nikiroo/fanfix/library/BasicLibrary.java index de3dbb6..63ffdb6 100644 --- a/src/be/nikiroo/fanfix/library/BasicLibrary.java +++ b/src/be/nikiroo/fanfix/library/BasicLibrary.java @@ -52,6 +52,35 @@ abstract public class BasicLibrary { */ public abstract BufferedImage getCover(String luid); + /** + * Return the cover image associated to this source. + *

+ * 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 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 * MAY not have the cover included). @@ -401,7 +430,7 @@ abstract public class BasicLibrary { if (getInfo(luid) != null) { delete(luid); } - + doSave(story, pg); clearCache(); diff --git a/src/be/nikiroo/fanfix/library/LocalLibrary.java b/src/be/nikiroo/fanfix/library/LocalLibrary.java index d7fd521..bc41157 100644 --- a/src/be/nikiroo/fanfix/library/LocalLibrary.java +++ b/src/be/nikiroo/fanfix/library/LocalLibrary.java @@ -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 stories; // Files: [ infoFile, TargetFile ] + private Map 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(); baseDir.mkdirs(); } @@ -92,10 +100,12 @@ public class LocalLibrary extends BasicLibrary { @Override protected void clearCache() { stories = null; + sourceCovers = new HashMap(); } @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); } diff --git a/src/be/nikiroo/fanfix/library/RemoteLibrary.java b/src/be/nikiroo/fanfix/library/RemoteLibrary.java index 511d1c1..8376639 100644 --- a/src/be/nikiroo/fanfix/library/RemoteLibrary.java +++ b/src/be/nikiroo/fanfix/library/RemoteLibrary.java @@ -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"); + } } diff --git a/src/be/nikiroo/fanfix/reader/GuiReaderBook.java b/src/be/nikiroo/fanfix/reader/GuiReaderBook.java index 6475bd9..29f4c49 100644 --- a/src/be/nikiroo/fanfix/reader/GuiReaderBook.java +++ b/src/be/nikiroo/fanfix/reader/GuiReaderBook.java @@ -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) { diff --git a/src/be/nikiroo/fanfix/reader/GuiReaderFrame.java b/src/be/nikiroo/fanfix/reader/GuiReaderFrame.java index e88c977..3b5fca3 100644 --- a/src/be/nikiroo/fanfix/reader/GuiReaderFrame.java +++ b/src/be/nikiroo/fanfix/reader/GuiReaderFrame.java @@ -141,6 +141,49 @@ class GuiReaderFrame extends JFrame { setVisible(true); } + private void addSourcePanes() { + // Sources -> i18n + GuiReaderGroup bookPane = new GuiReaderGroup(reader, "Sources", color); + + List sources = new ArrayList(); + 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()); } } }); -- 2.27.0