Fix CBZ importer, improve GUI
authorNiki Roo <niki@nikiroo.be>
Thu, 16 Feb 2017 19:31:36 +0000 (20:31 +0100)
committerNiki Roo <niki@nikiroo.be>
Thu, 16 Feb 2017 19:31:36 +0000 (20:31 +0100)
- CBZ importer was not sorting the images, they were often reversed
- The GUI is improved (~fixed book size, scroll, wrapping, better
  highlight colours)

src/be/nikiroo/fanfix/reader/LocalReaderBook.java
src/be/nikiroo/fanfix/reader/LocalReaderFrame.java
src/be/nikiroo/fanfix/reader/WrapLayout.java [new file with mode: 0644]
src/be/nikiroo/fanfix/supported/Cbz.java

index 7fc8171c3c17cbe155f4653e2a1ef5470d3f8493..ef6ba48588df4cfb9cc778343e1b6f26fd665f0a 100644 (file)
@@ -2,7 +2,9 @@ package be.nikiroo.fanfix.reader;
 
 import java.awt.BorderLayout;
 import java.awt.Color;
+import java.awt.Graphics;
 import java.awt.Graphics2D;
+import java.awt.Rectangle;
 import java.awt.event.MouseEvent;
 import java.awt.event.MouseListener;
 import java.awt.image.BufferedImage;
@@ -14,6 +16,7 @@ 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;
 
@@ -48,8 +51,8 @@ class LocalReaderBook extends JPanel {
 
        private static final long serialVersionUID = 1L;
        private JLabel icon;
-       private JLabel title;
-       private JLabel author;
+       private JTextArea title;
+       private JTextArea author;
        private boolean selected;
        private boolean hovered;
        private Date lastClick;
@@ -69,8 +72,12 @@ class LocalReaderBook extends JPanel {
                        icon = new JLabel(" [ no cover ] ");
                }
 
-               title = new JLabel(meta.getTitle());
-               author = new JLabel("by " + meta.getAuthor());
+               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());
                this.add(icon, BorderLayout.CENTER);
@@ -97,24 +104,12 @@ class LocalReaderBook extends JPanel {
         */
        public void setSelected(boolean selected) {
                this.selected = selected;
-               fixColor();
+               repaint();
        }
 
        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));
-               }
+               repaint();
        }
 
        private void setupListeners() {
@@ -160,4 +155,22 @@ class LocalReaderBook extends JPanel {
        public void addActionListener(BookActionListner listener) {
                listeners.add(listener);
        }
+
+       @Override
+       public void paint(Graphics g) {
+               super.paint(g);
+
+               Color color = new Color(255, 255, 255, 0);
+               if (selected && !hovered) {
+                       color = new Color(80, 80, 100, 40);
+               } else if (!selected && hovered) {
+                       color = new Color(230, 230, 255, 100);
+               } else if (selected && hovered) {
+                       color = new Color(200, 200, 255, 100);
+               }
+
+               Rectangle clip = g.getClipBounds();
+               g.setColor(color);
+               g.fillRect(clip.x, clip.y, clip.width, clip.height);
+       }
 }
index ed8a039051c8b5a59f3e7228ddd54e814c6fb122..dd9a8f2e4651ccf83c5fa10de6c09dcf660d5fe1 100644 (file)
@@ -1,7 +1,7 @@
 package be.nikiroo.fanfix.reader;
 
+import java.awt.BorderLayout;
 import java.awt.Desktop;
-import java.awt.FlowLayout;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.KeyEvent;
@@ -17,6 +17,7 @@ import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
 import javax.swing.JOptionPane;
 import javax.swing.JPanel;
+import javax.swing.JScrollPane;
 
 import be.nikiroo.fanfix.Instance;
 import be.nikiroo.fanfix.Main;
@@ -38,11 +39,12 @@ class LocalReaderFrame extends JFrame {
 
                setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                setSize(800, 600);
-               setLayout(new FlowLayout());
+               setLayout(new BorderLayout());
 
                books = new ArrayList<LocalReaderBook>();
-               bookPane = new JPanel();
-               add(bookPane);
+               bookPane = new JPanel(new WrapLayout(WrapLayout.LEADING));
+
+               add(new JScrollPane(bookPane), BorderLayout.CENTER);
 
                refreshBooks(type);
                setJMenuBar(createMenu());
diff --git a/src/be/nikiroo/fanfix/reader/WrapLayout.java b/src/be/nikiroo/fanfix/reader/WrapLayout.java
new file mode 100644 (file)
index 0000000..3c23a54
--- /dev/null
@@ -0,0 +1,201 @@
+package be.nikiroo.fanfix.reader;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Insets;
+
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+/**
+ * FlowLayout subclass that fully supports wrapping of components.
+ * 
+ * @author https://tips4java.wordpress.com/2008/11/06/wrap-layout/
+ */
+class WrapLayout extends FlowLayout {
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Constructs a new <code>WrapLayout</code> with a left alignment and a
+        * default 5-unit horizontal and vertical gap.
+        */
+       public WrapLayout() {
+               super();
+       }
+
+       /**
+        * Constructs a new <code>FlowLayout</code> with the specified alignment and
+        * a default 5-unit horizontal and vertical gap. The value of the alignment
+        * argument must be one of <code>WrapLayout</code>, <code>WrapLayout</code>,
+        * or <code>WrapLayout</code>.
+        * 
+        * @param align
+        *            the alignment value
+        */
+       public WrapLayout(int align) {
+               super(align);
+       }
+
+       /**
+        * Creates a new flow layout manager with the indicated alignment and the
+        * indicated horizontal and vertical gaps.
+        * <p>
+        * The value of the alignment argument must be one of
+        * <code>WrapLayout</code>, <code>WrapLayout</code>, or
+        * <code>WrapLayout</code>.
+        * 
+        * @param align
+        *            the alignment value
+        * @param hgap
+        *            the horizontal gap between components
+        * @param vgap
+        *            the vertical gap between components
+        */
+       public WrapLayout(int align, int hgap, int vgap) {
+               super(align, hgap, vgap);
+       }
+
+       /**
+        * Returns the preferred dimensions for this layout given the <i>visible</i>
+        * components in the specified target container.
+        * 
+        * @param target
+        *            the component which needs to be laid out
+        * @return the preferred dimensions to lay out the subcomponents of the
+        *         specified container
+        */
+       @Override
+       public Dimension preferredLayoutSize(Container target) {
+               return layoutSize(target, true);
+       }
+
+       /**
+        * Returns the minimum dimensions needed to layout the <i>visible</i>
+        * components contained in the specified target container.
+        * 
+        * @param target
+        *            the component which needs to be laid out
+        * @return the minimum dimensions to lay out the subcomponents of the
+        *         specified container
+        */
+       @Override
+       public Dimension minimumLayoutSize(Container target) {
+               Dimension minimum = layoutSize(target, false);
+               minimum.width -= (getHgap() + 1);
+               return minimum;
+       }
+
+       /**
+        * Returns the minimum or preferred dimension needed to layout the target
+        * container.
+        *
+        * @param target
+        *            target to get layout size for
+        * @param preferred
+        *            should preferred size be calculated
+        * @return the dimension to layout the target container
+        */
+       private Dimension layoutSize(Container target, boolean preferred) {
+               synchronized (target.getTreeLock()) {
+                       // Each row must fit with the width allocated to the containter.
+                       // When the container width = 0, the preferred width of the
+                       // container
+                       // has not yet been calculated so lets ask for the maximum.
+
+                       int targetWidth = target.getSize().width;
+                       Container container = target;
+
+                       while (container.getSize().width == 0
+                                       && container.getParent() != null) {
+                               container = container.getParent();
+                       }
+
+                       targetWidth = container.getSize().width;
+
+                       if (targetWidth == 0)
+                               targetWidth = Integer.MAX_VALUE;
+
+                       int hgap = getHgap();
+                       int vgap = getVgap();
+                       Insets insets = target.getInsets();
+                       int horizontalInsetsAndGap = insets.left + insets.right
+                                       + (hgap * 2);
+                       int maxWidth = targetWidth - horizontalInsetsAndGap;
+
+                       // Fit components into the allowed width
+
+                       Dimension dim = new Dimension(0, 0);
+                       int rowWidth = 0;
+                       int rowHeight = 0;
+
+                       int nmembers = target.getComponentCount();
+
+                       for (int i = 0; i < nmembers; i++) {
+                               Component m = target.getComponent(i);
+
+                               if (m.isVisible()) {
+                                       Dimension d = preferred ? m.getPreferredSize() : m
+                                                       .getMinimumSize();
+
+                                       // Can't add the component to current row. Start a new row.
+
+                                       if (rowWidth + d.width > maxWidth) {
+                                               addRow(dim, rowWidth, rowHeight);
+                                               rowWidth = 0;
+                                               rowHeight = 0;
+                                       }
+
+                                       // Add a horizontal gap for all components after the first
+
+                                       if (rowWidth != 0) {
+                                               rowWidth += hgap;
+                                       }
+
+                                       rowWidth += d.width;
+                                       rowHeight = Math.max(rowHeight, d.height);
+                               }
+                       }
+
+                       addRow(dim, rowWidth, rowHeight);
+
+                       dim.width += horizontalInsetsAndGap;
+                       dim.height += insets.top + insets.bottom + vgap * 2;
+
+                       // When using a scroll pane or the DecoratedLookAndFeel we need to
+                       // make sure the preferred size is less than the size of the
+                       // target containter so shrinking the container size works
+                       // correctly. Removing the horizontal gap is an easy way to do this.
+
+                       Container scrollPane = SwingUtilities.getAncestorOfClass(
+                                       JScrollPane.class, target);
+
+                       if (scrollPane != null && target.isValid()) {
+                               dim.width -= (hgap + 1);
+                       }
+
+                       return dim;
+               }
+       }
+
+       /*
+        * A new row has been completed. Use the dimensions of this row to update
+        * the preferred size for the container.
+        * 
+        * @param dim update the width and height when appropriate
+        * 
+        * @param rowWidth the width of the row to add
+        * 
+        * @param rowHeight the height of the row to add
+        */
+       private void addRow(Dimension dim, int rowWidth, int rowHeight) {
+               dim.width = Math.max(dim.width, rowWidth);
+
+               if (dim.height > 0) {
+                       dim.height += getVgap();
+               }
+
+               dim.height += rowHeight;
+       }
+}
index f9eee08d51428cab98f2c3fbe9b6561cd3087f56..1080ad2755adebb174ad7a3e04f4053c9e0b35f4 100644 (file)
@@ -5,6 +5,8 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
@@ -60,6 +62,7 @@ class Cbz extends Epub {
 
                ZipInputStream zipIn = new ZipInputStream(getInput());
 
+               List<String> images = new ArrayList<String>();
                for (ZipEntry entry = zipIn.getNextEntry(); entry != null; entry = zipIn
                                .getNextEntry()) {
                        if (!entry.isDirectory()
@@ -73,12 +76,10 @@ class Cbz extends Epub {
                                }
 
                                if (imageEntry) {
+                                       String uuid = meta.getUuid() + "_" + entry.getName();
+                                       images.add(uuid);
                                        try {
-                                               String uuid = meta.getUuid() + "_" + entry.getName();
-
                                                Instance.getCache().addToCache(zipIn, uuid);
-                                               chap.getParagraphs().add(
-                                                               new Paragraph(new File(uuid).toURI().toURL()));
                                        } catch (Exception e) {
                                                Instance.syserr(e);
                                        }
@@ -86,6 +87,18 @@ class Cbz extends Epub {
                        }
                }
 
+               // ZIP order is not sure
+               Collections.sort(images);
+
+               for (String uuid : images) {
+                       try {
+                               chap.getParagraphs().add(
+                                               new Paragraph(new File(uuid).toURI().toURL()));
+                       } catch (Exception e) {
+                               Instance.syserr(e);
+                       }
+               }
+
                return story;
        }
 }