From 4310bae9326894d9a9f5c7d34e552437e1156ddb Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Fri, 3 Mar 2017 18:26:41 +0100 Subject: [PATCH] Version 1.3.1: UI: authors - UI: can now display books by Author --- VERSION | 2 +- changelog.md | 4 + src/be/nikiroo/fanfix/Library.java | 45 +++- src/be/nikiroo/fanfix/reader/CliReader.java | 2 +- .../fanfix/reader/LocalReaderFrame.java | 208 ++++++++++++------ .../fanfix/reader/LocalReaderGroup.java | 161 ++++++++++++++ 6 files changed, 349 insertions(+), 73 deletions(-) create mode 100644 src/be/nikiroo/fanfix/reader/LocalReaderGroup.java diff --git a/VERSION b/VERSION index f0bb29e..3a3cd8c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.0 +1.3.1 diff --git a/changelog.md b/changelog.md index d5fe3e2..edecf23 100644 --- a/changelog.md +++ b/changelog.md @@ -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) diff --git a/src/be/nikiroo/fanfix/Library.java b/src/be/nikiroo/fanfix/Library.java index 6e0262b..ccbd7d4 100644 --- a/src/be/nikiroo/fanfix/Library.java +++ b/src/be/nikiroo/fanfix/Library.java @@ -64,12 +64,53 @@ public class Library { public synchronized List getTypes() { List list = new ArrayList(); for (Entry 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 getAuthors() { + List list = new ArrayList(); + for (Entry 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 getListByAuthor(String author) { + List list = new ArrayList(); + for (Entry 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 getList(String type) { + public synchronized List getListByType(String type) { List list = new ArrayList(); for (Entry entry : getStories().entrySet()) { String storyType = entry.getValue().getParentFile().getName(); diff --git a/src/be/nikiroo/fanfix/reader/CliReader.java b/src/be/nikiroo/fanfix/reader/CliReader.java index 5dc2288..a57de34 100644 --- a/src/be/nikiroo/fanfix/reader/CliReader.java +++ b/src/be/nikiroo/fanfix/reader/CliReader.java @@ -80,7 +80,7 @@ class CliReader extends BasicReader { @Override public void start(String type) { List stories; - stories = Instance.getLibrary().getList(type); + stories = Instance.getLibrary().getListByType(type); for (MetaData story : stories) { String author = ""; diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java index 4e8d9ac..1d303a2 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java @@ -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 stories; - private List books; - private JPanel bookPane; - private String type; + private Map booksByType; + private Map 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(); - 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(); + booksByAuthor = new HashMap(); + + 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 stories = Instance.getLibrary().getListByType( + booksByType.get(group)); + group.refreshBooks(stories); + } + + for (LocalReaderGroup group : booksByAuthor.keySet()) { + List 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 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 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 index 0000000..991aaee --- /dev/null +++ b/src/be/nikiroo/fanfix/reader/LocalReaderGroup.java @@ -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 stories; + private List 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("" + + "
" + "%s" + + "" + "", 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 stories) { + this.stories = stories; + + books = new ArrayList(); + 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 b. An enabled component can respond to user input + * and generate events. Components are enabled initially by default. + *

+ * Disabling this component will also affect its children. + * + * @param b + * If true, 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(); + } +} -- 2.27.0