Version 1.3.1: UI: authors
authorNiki Roo <niki@nikiroo.be>
Fri, 3 Mar 2017 17:26:41 +0000 (18:26 +0100)
committerNiki Roo <niki@nikiroo.be>
Fri, 3 Mar 2017 17:26:41 +0000 (18:26 +0100)
- UI: can now display books by Author

VERSION
changelog.md
src/be/nikiroo/fanfix/Library.java
src/be/nikiroo/fanfix/reader/CliReader.java
src/be/nikiroo/fanfix/reader/LocalReaderFrame.java
src/be/nikiroo/fanfix/reader/LocalReaderGroup.java [new file with mode: 0644]

diff --git a/VERSION b/VERSION
index f0bb29e76388856b273698ae6064b0380ce5e5d2..3a3cd8cc8b079cb410a465d2925b9cbd703115cb 100644 (file)
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.3.0
+1.3.1
index d5fe3e26edcef6e99babfb366a5fb5d9a33e345e..edecf23f6705e0d4c3e78e34d0cff4a6e3c0c0bb 100644 (file)
@@ -1,5 +1,9 @@
 # Fanfix
 
+## Version 1.3.1
+
+- UI: can now display books by Author
+
 ## Version 1.3.0
 
 - now supports YiffStar (SoFurry.com)
index 6e0262b43b1c78ac58406f65807cb2399a027031..ccbd7d40b67b7e6e78c8b43fd95a999c9eb49285 100644 (file)
@@ -64,12 +64,53 @@ public class Library {
        public synchronized List<String> getTypes() {
                List<String> list = new ArrayList<String>();
                for (Entry<MetaData, File> entry : getStories().entrySet()) {
-                       String storyType = entry.getValue().getParentFile().getName();
+                       String storyType = entry.getKey().getSource();
                        if (!list.contains(storyType)) {
                                list.add(storyType);
                        }
                }
 
+               Collections.sort(list);
+               return list;
+       }
+
+       /**
+        * List all the known authors of stories.
+        * 
+        * @return the authors
+        */
+       public synchronized List<String> getAuthors() {
+               List<String> list = new ArrayList<String>();
+               for (Entry<MetaData, File> entry : getStories().entrySet()) {
+                       String storyAuthor = entry.getKey().getAuthor();
+                       if (!list.contains(storyAuthor)) {
+                               list.add(storyAuthor);
+                       }
+               }
+
+               Collections.sort(list);
+               return list;
+       }
+
+       /**
+        * List all the stories of the given author in the {@link Library}, or all
+        * the stories if NULL is passed as an author.
+        * 
+        * @param author
+        *            the author of the stories to retrieve, or NULL for all
+        * 
+        * @return the stories
+        */
+       public synchronized List<MetaData> getListByAuthor(String author) {
+               List<MetaData> list = new ArrayList<MetaData>();
+               for (Entry<MetaData, File> entry : getStories().entrySet()) {
+                       String storyAuthor = entry.getKey().getAuthor();
+                       if (author == null || author.equalsIgnoreCase(storyAuthor)) {
+                               list.add(entry.getKey());
+                       }
+               }
+
+               Collections.sort(list);
                return list;
        }
 
@@ -82,7 +123,7 @@ public class Library {
         * 
         * @return the stories
         */
-       public synchronized List<MetaData> getList(String type) {
+       public synchronized List<MetaData> getListByType(String type) {
                List<MetaData> list = new ArrayList<MetaData>();
                for (Entry<MetaData, File> entry : getStories().entrySet()) {
                        String storyType = entry.getValue().getParentFile().getName();
index 5dc228810cfcac16fef2a1e8abc44681b5d96cc5..a57de34a8e3beb403d7f2fdec1052d225aafd617 100644 (file)
@@ -80,7 +80,7 @@ class CliReader extends BasicReader {
        @Override
        public void start(String type) {
                List<MetaData> stories;
-               stories = Instance.getLibrary().getList(type);
+               stories = Instance.getLibrary().getListByType(type);
 
                for (MetaData story : stories) {
                        String author = "";
index 4e8d9ace0f801b45f3089152c534a45dc6ef3aaa..1d303a2c809e6df9866c52c2b81c9ccb74e9bc13 100644 (file)
@@ -13,12 +13,12 @@ import java.awt.event.WindowEvent;
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 
+import javax.swing.BoxLayout;
 import javax.swing.JFileChooser;
 import javax.swing.JFrame;
 import javax.swing.JMenu;
@@ -42,7 +42,6 @@ import be.nikiroo.fanfix.reader.LocalReaderBook.BookActionListener;
 import be.nikiroo.utils.Progress;
 import be.nikiroo.utils.Version;
 import be.nikiroo.utils.ui.ProgressBar;
-import be.nikiroo.utils.ui.WrapLayout;
 
 /**
  * A {@link Frame} that will show a {@link LocalReaderBook} item for each
@@ -55,10 +54,9 @@ import be.nikiroo.utils.ui.WrapLayout;
 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;
+       private Map<LocalReaderGroup, String> booksByType;
+       private Map<LocalReaderGroup, String> booksByAuthor;
+       private JPanel pane;
        private Color color;
        private ProgressBar pgBar;
        private JMenuBar bar;
@@ -82,80 +80,128 @@ class LocalReaderFrame extends JFrame {
                setSize(800, 600);
                setLayout(new BorderLayout());
 
-               books = new ArrayList<LocalReaderBook>();
-               bookPane = new JPanel(new WrapLayout(WrapLayout.LEADING, 5, 5));
+               pane = new JPanel();
+               pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
 
                color = Instance.getUiConfig().getColor(UiConfig.BACKGROUND_COLOR);
-
                if (color != null) {
                        setBackground(color);
-                       bookPane.setBackground(color);
+                       pane.setBackground(color);
                }
 
-               JScrollPane scroll = new JScrollPane(bookPane);
+               JScrollPane scroll = new JScrollPane(pane);
                scroll.getVerticalScrollBar().setUnitIncrement(16);
                add(scroll, BorderLayout.CENTER);
 
                pgBar = new ProgressBar();
                add(pgBar, BorderLayout.SOUTH);
 
-               refreshBooks(type);
                setJMenuBar(createMenu());
 
+               booksByType = new HashMap<LocalReaderGroup, String>();
+               booksByAuthor = new HashMap<LocalReaderGroup, String>();
+
+               addBookPane(type, true);
+               refreshBooks();
+
                setVisible(true);
        }
 
        /**
-        * Refresh the list of {@link LocalReaderBook}s from disk.
+        * Add a new {@link LocalReaderGroup} on the frame to display the books of
+        * the selected type or author.
         * 
+        * @param value
+        *            the author or the type
         * @param type
-        *            the type of {@link Story} to load, or NULL for all types
+        *            TRUE for type, FALSE for author
         */
-       private void refreshBooks(String type) {
-               this.type = type;
-               stories = Instance.getLibrary().getList(type);
-               books.clear();
-               bookPane.invalidate();
-               bookPane.removeAll();
-               for (MetaData meta : stories) {
-                       LocalReaderBook book = new LocalReaderBook(meta,
-                                       reader.isCached(meta.getLuid()));
-                       if (color != null) {
-                               book.setBackground(color);
+       private void addBookPane(String value, boolean type) {
+               if (value == null) {
+                       if (type) {
+                               for (String tt : Instance.getLibrary().getTypes()) {
+                                       if (tt != null) {
+                                               addBookPane(tt, type);
+                                       }
+                               }
+                       } else {
+                               for (String tt : Instance.getLibrary().getAuthors()) {
+                                       if (tt != null) {
+                                               addBookPane(tt, type);
+                                       }
+                               }
                        }
 
-                       books.add(book);
+                       return;
+               }
 
-                       book.addActionListener(new BookActionListener() {
-                               public void select(LocalReaderBook book) {
-                                       selectedBook = book;
-                                       for (LocalReaderBook abook : books) {
-                                               abook.setSelected(abook == book);
-                                       }
-                               }
+               LocalReaderGroup bookPane = new LocalReaderGroup(reader, value, color);
+               if (type) {
+                       booksByType.put(bookPane, value);
+               } else {
+                       booksByAuthor.put(bookPane, value);
+               }
 
-                               public void popupRequested(LocalReaderBook book, MouseEvent e) {
-                                       JPopupMenu popup = new JPopupMenu();
-                                       popup.add(createMenuItemOpenBook());
-                                       popup.addSeparator();
-                                       popup.add(createMenuItemExport());
-                                       popup.add(createMenuItemClearCache());
-                                       popup.add(createMenuItemRedownload());
-                                       popup.addSeparator();
-                                       popup.add(createMenuItemDelete());
-                                       popup.show(e.getComponent(), e.getX(), e.getY());
-                               }
+               this.invalidate();
+               pane.invalidate();
+               pane.add(bookPane);
+               pane.validate();
+               this.validate();
 
-                               public void action(final LocalReaderBook book) {
-                                       openBook(book);
-                               }
-                       });
+               bookPane.setActionListener(new BookActionListener() {
+                       public void select(LocalReaderBook book) {
+                               selectedBook = book;
+                       }
+
+                       public void popupRequested(LocalReaderBook book, MouseEvent e) {
+                               JPopupMenu popup = new JPopupMenu();
+                               popup.add(createMenuItemOpenBook());
+                               popup.addSeparator();
+                               popup.add(createMenuItemExport());
+                               popup.add(createMenuItemClearCache());
+                               popup.add(createMenuItemRedownload());
+                               popup.addSeparator();
+                               popup.add(createMenuItemDelete());
+                               popup.show(e.getComponent(), e.getX(), e.getY());
+                       }
+
+                       public void action(final LocalReaderBook book) {
+                               openBook(book);
+                       }
+               });
+       }
 
-                       bookPane.add(book);
+       private void removeBookPanes() {
+               booksByType.clear();
+               booksByAuthor.clear();
+               pane.invalidate();
+               this.invalidate();
+               pane.removeAll();
+               pane.validate();
+               this.validate();
+       }
+
+       /**
+        * Refresh the list of {@link LocalReaderBook}s from disk.
+        * 
+        * @param type
+        *            the type of {@link Story} to load, or NULL for all types
+        */
+       private void refreshBooks() {
+               for (LocalReaderGroup group : booksByType.keySet()) {
+                       List<MetaData> stories = Instance.getLibrary().getListByType(
+                                       booksByType.get(group));
+                       group.refreshBooks(stories);
+               }
+
+               for (LocalReaderGroup group : booksByAuthor.keySet()) {
+                       List<MetaData> stories = Instance.getLibrary().getListByAuthor(
+                                       booksByAuthor.get(group));
+                       group.refreshBooks(stories);
                }
 
-               bookPane.validate();
-               bookPane.repaint();
+               pane.repaint();
+               this.repaint();
        }
 
        /**
@@ -209,26 +255,52 @@ class LocalReaderFrame extends JFrame {
 
                bar.add(edit);
 
-               JMenu view = new JMenu("View");
-               view.setMnemonic(KeyEvent.VK_V);
+               JMenu sources = new JMenu("Sources");
+               sources.setMnemonic(KeyEvent.VK_S);
 
                List<String> tt = Instance.getLibrary().getTypes();
                tt.add(0, null);
                for (final String type : tt) {
-                       JMenuItem item = new JMenuItem(type == null ? "All books" : type);
+                       JMenuItem item = new JMenuItem(type == null ? "All" : type);
                        item.addActionListener(new ActionListener() {
                                public void actionPerformed(ActionEvent e) {
-                                       refreshBooks(type);
+                                       removeBookPanes();
+                                       addBookPane(type, true);
+                                       refreshBooks();
                                }
                        });
-                       view.add(item);
+                       sources.add(item);
 
                        if (type == null) {
-                               view.addSeparator();
+                               sources.addSeparator();
                        }
                }
 
-               bar.add(view);
+               bar.add(sources);
+
+               JMenu authors = new JMenu("Authors");
+               authors.setMnemonic(KeyEvent.VK_A);
+
+               List<String> aa = Instance.getLibrary().getAuthors();
+               aa.add(0, null);
+               for (final String author : aa) {
+                       JMenuItem item = new JMenuItem(author == null ? "All"
+                                       : author.isEmpty() ? "[unknown]" : author);
+                       item.addActionListener(new ActionListener() {
+                               public void actionPerformed(ActionEvent e) {
+                                       removeBookPanes();
+                                       addBookPane(author, false);
+                                       refreshBooks();
+                               }
+                       });
+                       authors.add(item);
+
+                       if (author == null || author.isEmpty()) {
+                               authors.addSeparator();
+                       }
+               }
+
+               bar.add(authors);
 
                return bar;
        }
@@ -383,7 +455,7 @@ class LocalReaderFrame extends JFrame {
                                                        selectedBook = null;
                                                        SwingUtilities.invokeLater(new Runnable() {
                                                                public void run() {
-                                                                       refreshBooks(type);
+                                                                       refreshBooks();
                                                                }
                                                        });
                                                }
@@ -552,10 +624,10 @@ class LocalReaderFrame extends JFrame {
 
                                                        setEnabled(true);
                                                } else {
-                                                       refreshBooks(type);
+                                                       refreshBooks();
                                                        if (onSuccess != null) {
                                                                onSuccess.run();
-                                                               refreshBooks(type);
+                                                               refreshBooks();
                                                        }
                                                }
                                        }
@@ -577,15 +649,13 @@ class LocalReaderFrame extends JFrame {
         */
        @Override
        public void setEnabled(boolean b) {
-               for (LocalReaderBook book : books) {
-                       book.setEnabled(b);
-                       book.repaint();
-               }
-
                bar.setEnabled(b);
-               bookPane.setEnabled(b);
-               bookPane.repaint();
-
+               for (LocalReaderGroup group : booksByType.keySet()) {
+                       group.setEnabled(b);
+               }
+               for (LocalReaderGroup group : booksByAuthor.keySet()) {
+                       group.setEnabled(b);
+               }
                super.setEnabled(b);
                repaint();
        }
diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderGroup.java b/src/be/nikiroo/fanfix/reader/LocalReaderGroup.java
new file mode 100644 (file)
index 0000000..991aaee
--- /dev/null
@@ -0,0 +1,161 @@
+package be.nikiroo.fanfix.reader;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import be.nikiroo.fanfix.data.MetaData;
+import be.nikiroo.fanfix.reader.LocalReaderBook.BookActionListener;
+import be.nikiroo.utils.ui.WrapLayout;
+
+/**
+ * A group of {@link LocalReaderBook}s for display.
+ * 
+ * @author niki
+ */
+public class LocalReaderGroup extends JPanel {
+       private static final long serialVersionUID = 1L;
+       private BookActionListener action;
+       private Color backgroundColor;
+       private LocalReader reader;
+       private List<MetaData> stories;
+       private List<LocalReaderBook> books;
+       private JPanel pane;
+
+       /**
+        * Create a new {@link LocalReaderGroup}.
+        * 
+        * @param reader
+        *            the {@link LocalReaderBook} used to probe some information
+        *            about the stories
+        * @param title
+        *            the title of this group
+        * @param backgroundColor
+        *            the background colour to use (or NULL for default)
+        */
+       public LocalReaderGroup(LocalReader reader, String title,
+                       Color backgroundColor) {
+               this.reader = reader;
+               this.backgroundColor = backgroundColor;
+
+               this.pane = new JPanel();
+
+               pane.setLayout(new WrapLayout(WrapLayout.LEADING, 5, 5));
+               if (backgroundColor != null) {
+                       pane.setBackground(backgroundColor);
+                       setBackground(backgroundColor);
+               }
+
+               setLayout(new BorderLayout(0, 10));
+               add(pane, BorderLayout.CENTER);
+
+               if (title != null) {
+                       if (title.isEmpty()) {
+                               title = "[unknown]";
+                       }
+
+                       JLabel label = new JLabel();
+                       label.setText(String.format("<html>"
+                                       + "<body style='text-align: center'><br>" + "%s"
+                                       + "</body>" + "</html>", title));
+                       label.setHorizontalAlignment(JLabel.CENTER);
+                       add(label, BorderLayout.NORTH);
+               }
+       }
+
+       /**
+        * Set the {@link ActionListener} that will be fired on each
+        * {@link LocalReaderBook} action.
+        * 
+        * @param action
+        *            the action
+        */
+       public void setActionListener(BookActionListener action) {
+               this.action = action;
+               refreshBooks(stories);
+       }
+
+       /**
+        * Refresh the list of {@link LocalReaderBook}s displayed in the control.
+        * 
+        * @param stories
+        *            the stories
+        */
+       public void refreshBooks(List<MetaData> stories) {
+               this.stories = stories;
+
+               books = new ArrayList<LocalReaderBook>();
+               invalidate();
+               pane.invalidate();
+               pane.removeAll();
+
+               if (stories != null) {
+                       for (MetaData meta : stories) {
+                               LocalReaderBook book = new LocalReaderBook(meta,
+                                               reader.isCached(meta.getLuid()));
+                               if (backgroundColor != null) {
+                                       book.setBackground(backgroundColor);
+                               }
+
+                               books.add(book);
+
+                               book.addActionListener(new BookActionListener() {
+                                       public void select(LocalReaderBook book) {
+                                               for (LocalReaderBook abook : books) {
+                                                       abook.setSelected(abook == book);
+                                               }
+                                       }
+
+                                       public void popupRequested(LocalReaderBook book,
+                                                       MouseEvent e) {
+                                       }
+
+                                       public void action(LocalReaderBook book) {
+                                       }
+                               });
+
+                               if (action != null) {
+                                       book.addActionListener(action);
+                               }
+
+                               pane.add(book);
+                       }
+               }
+
+               pane.validate();
+               pane.repaint();
+               validate();
+               repaint();
+       }
+
+       /**
+        * Enables or disables this component, depending on the value of the
+        * parameter <code>b</code>. An enabled component can respond to user input
+        * and generate events. Components are enabled initially by default.
+        * <p>
+        * Disabling this component will also affect its children.
+        * 
+        * @param b
+        *            If <code>true</code>, this component is enabled; otherwise
+        *            this component is disabled
+        */
+       @Override
+       public void setEnabled(boolean b) {
+               if (books != null) {
+                       for (LocalReaderBook book : books) {
+                               book.setEnabled(b);
+                               book.repaint();
+                       }
+               }
+
+               pane.setEnabled(b);
+               super.setEnabled(b);
+               repaint();
+       }
+}