From 14bb95fae33d405c0a43682c144d081bfbcad545 Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Tue, 19 Mar 2019 18:48:56 +0100 Subject: [PATCH] gui: code cleanup --- .../fanfix/reader/ui/GuiReaderFrame.java | 731 +++--------------- .../fanfix/reader/ui/GuiReaderMainPanel.java | 625 +++++++++++++++ .../reader/ui/GuiReaderPropertiesFrame.java | 84 ++ 3 files changed, 806 insertions(+), 634 deletions(-) create mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java create mode 100644 src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java index d28f941e..f7fb09f2 100644 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReaderFrame.java @@ -1,41 +1,25 @@ package be.nikiroo.fanfix.reader.ui; import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Font; import java.awt.Frame; -import java.awt.Toolkit; -import java.awt.datatransfer.DataFlavor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; -import java.net.URL; -import java.net.UnknownHostException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import javax.swing.BorderFactory; -import javax.swing.BoxLayout; -import javax.swing.ImageIcon; import javax.swing.JFileChooser; import javax.swing.JFrame; -import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; -import javax.swing.JPanel; import javax.swing.JPopupMenu; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.filechooser.FileFilter; import javax.swing.filechooser.FileNameExtensionFilter; @@ -45,16 +29,14 @@ import be.nikiroo.fanfix.bundles.Config; import be.nikiroo.fanfix.bundles.UiConfig; import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Story; -import be.nikiroo.fanfix.library.BasicLibrary; -import be.nikiroo.fanfix.library.BasicLibrary.Status; import be.nikiroo.fanfix.library.LocalLibrary; import be.nikiroo.fanfix.output.BasicOutput.OutputType; import be.nikiroo.fanfix.reader.BasicReader; -import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener; +import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.FrameHelper; +import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.StoryRunnable; import be.nikiroo.utils.Progress; import be.nikiroo.utils.Version; import be.nikiroo.utils.ui.ConfigEditor; -import be.nikiroo.utils.ui.ProgressBar; /** * A {@link Frame} that will show a {@link GuiReaderBook} item for each @@ -64,31 +46,13 @@ import be.nikiroo.utils.ui.ProgressBar; * * @author niki */ -class GuiReaderFrame extends JFrame { +class GuiReaderFrame extends JFrame implements FrameHelper { private static final long serialVersionUID = 1L; private GuiReader reader; - private Map booksByType; - private Map booksByAuthor; - private JPanel pane; - private Color color; - private ProgressBar pgBar; - private JMenuBar bar; - private GuiReaderBook selectedBook; - private boolean words; // words or authors (secondary info on books) + private GuiReaderMainPanel helpee; - /** - * A {@link Runnable} with a {@link Story} parameter. - * - * @author niki - */ - private interface StoryRunnable { - /** - * Run the action. - * - * @param story - * the story - */ - public void run(Story story); + private enum MoveAction { + SOURCE, TITLE, AUTHOR } /** @@ -105,296 +69,48 @@ class GuiReaderFrame extends JFrame { this.reader = reader; + // TODO: should we still have that?? setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(800, 600); - setLayout(new BorderLayout()); - - pane = new JPanel(); - pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS)); - - Integer icolor = Instance.getUiConfig().getColor( - UiConfig.BACKGROUND_COLOR); - if (icolor != null) { - color = new Color(icolor); - setBackground(color); - pane.setBackground(color); - } - - JScrollPane scroll = new JScrollPane(pane); - scroll.getVerticalScrollBar().setUnitIncrement(16); - add(scroll, BorderLayout.CENTER); - - String message = reader.getLibrary().getLibraryName(); - if (!message.isEmpty()) { - JLabel name = new JLabel(message, SwingConstants.CENTER); - add(name, BorderLayout.NORTH); - } - - pgBar = new ProgressBar(); - add(pgBar, BorderLayout.SOUTH); - - pgBar.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - invalidate(); - pgBar.setProgress(null); - validate(); - setEnabled(true); - } - }); - - pgBar.addUpdateListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - invalidate(); - validate(); - repaint(); - } - }); - booksByType = new HashMap(); - booksByAuthor = new HashMap(); + helpee = new GuiReaderMainPanel(this, type); - pane.setVisible(false); - final Progress pg = new Progress(); - final String typeF = type; - outOfUi(pg, new Runnable() { - @Override - public void run() { - BasicLibrary lib = GuiReaderFrame.this.reader.getLibrary(); - Status status = lib.getStatus(); - - if (status == Status.READY) { - lib.refresh(pg); - invalidate(); - setJMenuBar(createMenu(true)); - if (typeF == null) { - addBookPane(true, false); - } else { - addBookPane(typeF, true); - } - refreshBooks(); - validate(); - pane.setVisible(true); - } else { - invalidate(); - setJMenuBar(createMenu(false)); - validate(); - - String err = lib.getLibraryName() + "\n"; - switch (status) { - case INVALID: - err += "Library not valid"; - break; - - case UNAUTORIZED: - err += "You are not allowed to access this library"; - break; - - case UNAVAILABLE: - err += "Library currently unavailable"; - break; - - default: - err += "An error occured when contacting the library"; - break; - } - - error(err, "Library error", null); - } - } - }); + setSize(800, 600); + setLayout(new BorderLayout()); + add(helpee); setVisible(true); } - private void addListPane(String name, List values, - final boolean type) { - // Sources -> i18n - GuiReaderGroup bookPane = new GuiReaderGroup(reader, name, color); - - List metas = new ArrayList(); - for (String source : values) { - MetaData mSource = new MetaData(); - mSource.setLuid(null); - mSource.setTitle(source); - mSource.setSource(source); - metas.add(mSource); - } - - bookPane.refreshBooks(metas, 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(), type); - refreshBooks(); - } - }); - } - - /** - * Add a new {@link GuiReaderGroup} on the frame to display all the - * sources/types or all the authors, or a listing of all the books sorted - * either by source or author. - *

- * A display of all the sources/types or all the authors will show one icon - * per source/type or author. - *

- * A listing of all the books sorted by source/type or author will display - * all the books. - * - * @param type - * TRUE for type/source, FALSE for author - * @param listMode - * TRUE to get a listing of all the sources or authors, FALSE to - * get one icon per source or author - */ - private void addBookPane(boolean type, boolean listMode) { - if (type) { - if (!listMode) { - addListPane("Sources", reader.getLibrary().getSources(), type); - } else { - for (String tt : reader.getLibrary().getSources()) { - if (tt != null) { - addBookPane(tt, type); - } - } - } - } else { - if (!listMode) { - addListPane("Authors", reader.getLibrary().getAuthors(), type); - } else { - for (String tt : reader.getLibrary().getAuthors()) { - if (tt != null) { - addBookPane(tt, type); - } - } - } - } - } - - /** - * Add a new {@link GuiReaderGroup} on the frame to display the books of the - * selected type or author. - * - * @param value - * the author or the type, or NULL to get all the - * authors-or-types - * @param type - * TRUE for type/source, FALSE for author - * - */ - private void addBookPane(String value, boolean type) { - GuiReaderGroup bookPane = new GuiReaderGroup(reader, value, color); - if (type) { - booksByType.put(bookPane, value); - } else { - booksByAuthor.put(bookPane, value); - } - - 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.addSeparator(); - popup.add(createMenuItemExport()); - popup.add(createMenuItemMoveTo(true)); - popup.add(createMenuItemSetCover()); - popup.add(createMenuItemClearCache()); - popup.add(createMenuItemRedownload()); - popup.addSeparator(); - popup.add(createMenuItemRename(true)); - popup.add(createMenuItemSetAuthor(true)); - popup.addSeparator(); - popup.add(createMenuItemDelete()); - popup.addSeparator(); - popup.add(createMenuItemProperties()); - popup.show(e.getComponent(), e.getX(), e.getY()); - } - - @Override - public void action(final GuiReaderBook book) { - openBook(book); - } - }); - } - - /** - * Clear the pane from any book that may be present, usually prior to adding - * new ones. - */ - private void removeBookPanes() { - booksByType.clear(); - booksByAuthor.clear(); - pane.invalidate(); - this.invalidate(); - pane.removeAll(); - pane.validate(); - this.validate(); + @Override + public JPopupMenu createBookPopup() { + JPopupMenu popup = new JPopupMenu(); + popup.add(createMenuItemOpenBook()); + popup.addSeparator(); + popup.add(createMenuItemExport()); + popup.add(createMenuItemMoveTo(true)); + popup.add(createMenuItemSetCover()); + popup.add(createMenuItemClearCache()); + popup.add(createMenuItemRedownload()); + popup.addSeparator(); + popup.add(createMenuItemRename(true)); + popup.add(createMenuItemSetAuthor(true)); + popup.addSeparator(); + popup.add(createMenuItemDelete()); + popup.addSeparator(); + popup.add(createMenuItemProperties()); + return popup; } - /** - * Refresh the list of {@link GuiReaderBook}s from disk. - */ - private void refreshBooks() { - for (GuiReaderGroup group : booksByType.keySet()) { - List stories = reader.getLibrary().getListBySource( - booksByType.get(group)); - group.refreshBooks(stories, words); - } - - for (GuiReaderGroup group : booksByAuthor.keySet()) { - List stories = reader.getLibrary().getListByAuthor( - booksByAuthor.get(group)); - group.refreshBooks(stories, words); - } - - pane.repaint(); - this.repaint(); + @Override + public JPopupMenu createSourcePopup() { + JPopupMenu popup = new JPopupMenu(); + popup.add(createMenuItemOpenBook()); + return popup; } - /** - * Create the main menu bar. - * - * @param libOk - * the library can be queried - * - * @return the bar - */ - private JMenuBar createMenu(boolean libOk) { - bar = new JMenuBar(); + @Override + public void createMenu(boolean libOk) { + JMenuBar bar = new JMenuBar(); JMenu file = new JMenu("File"); file.setMnemonic(KeyEvent.VK_F); @@ -403,14 +119,14 @@ class GuiReaderFrame extends JFrame { imprt.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - imprt(true); + helpee.imprt(true); } }); JMenuItem imprtF = new JMenuItem("Import File...", KeyEvent.VK_F); imprtF.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - imprt(false); + helpee.imprt(false); } }); JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X); @@ -453,8 +169,8 @@ class GuiReaderFrame extends JFrame { vauthors.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - words = false; - refreshBooks(); + helpee.setWords(false); + helpee.refreshBooks(); } }); view.add(vauthors); @@ -463,8 +179,8 @@ class GuiReaderFrame extends JFrame { vwords.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - words = true; - refreshBooks(); + helpee.setWords(true); + helpee.refreshBooks(); } }); view.add(vwords); @@ -494,7 +210,7 @@ class GuiReaderFrame extends JFrame { options.add(createMenuItemUiConfig()); bar.add(options); - return bar; + setJMenuBar(bar); } // "" = [unknown] @@ -564,9 +280,9 @@ class GuiReaderFrame extends JFrame { return new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - removeBookPanes(); - addBookPane(source, type); - refreshBooks(); + helpee.removeBookPanes(); + helpee.addBookPane(source, type); + helpee.refreshBooks(); } }; } @@ -576,9 +292,9 @@ class GuiReaderFrame extends JFrame { return new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - removeBookPanes(); - addBookPane(type, listMode); - refreshBooks(); + helpee.removeBookPanes(); + helpee.addBookPane(type, listMode); + helpee.refreshBooks(); } }; } @@ -673,6 +389,7 @@ class GuiReaderFrame extends JFrame { export.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + final GuiReaderBook selectedBook = helpee.getSelectedBook(); if (selectedBook != null) { fc.showDialog(GuiReaderFrame.this, "Save"); if (fc.getSelectedFile() != null) { @@ -681,7 +398,7 @@ class GuiReaderFrame extends JFrame { .getAbsolutePath() + type.getDefaultExtension(false); final Progress pg = new Progress(); - outOfUi(pg, new Runnable() { + helpee.outOfUi(pg, new Runnable() { @Override public void run() { try { @@ -734,8 +451,9 @@ class GuiReaderFrame extends JFrame { refresh.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + final GuiReaderBook selectedBook = helpee.getSelectedBook(); if (selectedBook != null) { - outOfUi(null, new Runnable() { + helpee.outOfUi(null, new Runnable() { @Override public void run() { reader.clearLocalReaderCache(selectedBook.getMeta() @@ -776,7 +494,7 @@ class GuiReaderFrame extends JFrame { } JMenuItem item = new JMenuItem("New type..."); - item.addActionListener(createMoveAction("SOURCE", null)); + item.addActionListener(createMoveAction(MoveAction.SOURCE, null)); changeTo.add(item); changeTo.addSeparator(); @@ -784,7 +502,7 @@ class GuiReaderFrame extends JFrame { List list = groupedSources.get(type); if (list.size() == 1 && list.get(0).isEmpty()) { item = new JMenuItem(type); - item.addActionListener(createMoveAction("SOURCE", type)); + item.addActionListener(createMoveAction(MoveAction.SOURCE, type)); changeTo.add(item); } else { JMenu dir = new JMenu(type); @@ -797,7 +515,7 @@ class GuiReaderFrame extends JFrame { } item = new JMenuItem(itemName); - item.addActionListener(createMoveAction("SOURCE", + item.addActionListener(createMoveAction(MoveAction.SOURCE, actualType)); dir.add(item); } @@ -824,7 +542,7 @@ class GuiReaderFrame extends JFrame { JMenuItem newItem = new JMenuItem("New author..."); changeTo.add(newItem); changeTo.addSeparator(); - newItem.addActionListener(createMoveAction("AUTHOR", null)); + newItem.addActionListener(createMoveAction(MoveAction.AUTHOR, null)); // Existing authors if (libOk) { @@ -836,7 +554,8 @@ class GuiReaderFrame extends JFrame { JMenu group = new JMenu(key); for (String value : groupedAuthors.get(key)) { JMenuItem item = new JMenuItem(value); - item.addActionListener(createMoveAction("AUTHOR", value)); + item.addActionListener(createMoveAction( + MoveAction.AUTHOR, value)); group.add(item); } changeTo.add(group); @@ -844,7 +563,8 @@ class GuiReaderFrame extends JFrame { } else if (groupedAuthors.size() == 1) { for (String value : groupedAuthors.values().iterator().next()) { JMenuItem item = new JMenuItem(value); - item.addActionListener(createMoveAction("AUTHOR", value)); + item.addActionListener(createMoveAction(MoveAction.AUTHOR, + value)); changeTo.add(item); } } @@ -865,23 +585,25 @@ class GuiReaderFrame extends JFrame { @SuppressWarnings("unused") boolean libOk) { JMenuItem changeTo = new JMenuItem("Rename..."); changeTo.setMnemonic(KeyEvent.VK_R); - changeTo.addActionListener(createMoveAction("TITLE", null)); + changeTo.addActionListener(createMoveAction(MoveAction.TITLE, null)); return changeTo; } - private ActionListener createMoveAction(final String what, final String type) { + private ActionListener createMoveAction(final MoveAction what, + final String type) { return new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + final GuiReaderBook selectedBook = helpee.getSelectedBook(); if (selectedBook != null) { String changeTo = type; if (type == null) { String init = ""; - if (what.equals("SOURCE")) { + if (what == MoveAction.SOURCE) { init = selectedBook.getMeta().getSource(); - } else if (what.equals("TITLE")) { + } else if (what == MoveAction.TITLE) { init = selectedBook.getMeta().getTitle(); - } else if (what.equals("AUTHOR")) { + } else if (what == MoveAction.AUTHOR) { init = selectedBook.getMeta().getAuthor(); } @@ -898,7 +620,7 @@ class GuiReaderFrame extends JFrame { } final String fChangeTo = changeTo; - outOfUi(null, new Runnable() { + helpee.outOfUi(null, new Runnable() { @Override public void run() { if (what.equals("SOURCE")) { @@ -912,12 +634,12 @@ class GuiReaderFrame extends JFrame { .getLuid(), fChangeTo); } - selectedBook = null; + helpee.unsetSelectedBook(); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - setJMenuBar(createMenu(true)); + createMenu(true); } }); } @@ -937,13 +659,14 @@ class GuiReaderFrame extends JFrame { refresh.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + final GuiReaderBook selectedBook = helpee.getSelectedBook(); if (selectedBook != null) { final MetaData meta = selectedBook.getMeta(); - imprt(meta.getUrl(), new StoryRunnable() { + helpee.imprt(meta.getUrl(), new StoryRunnable() { @Override public void run(Story story) { reader.delete(meta.getLuid()); - GuiReaderFrame.this.selectedBook = null; + helpee.unsetSelectedBook(); MetaData newMeta = story.getMeta(); if (!newMeta.getSource().equals(meta.getSource())) { reader.changeSource(newMeta.getLuid(), @@ -968,12 +691,13 @@ class GuiReaderFrame extends JFrame { delete.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + final GuiReaderBook selectedBook = helpee.getSelectedBook(); if (selectedBook != null) { - outOfUi(null, new Runnable() { + helpee.outOfUi(null, new Runnable() { @Override public void run() { reader.delete(selectedBook.getMeta().getLuid()); - selectedBook = null; + helpee.unsetSelectedBook(); } }); } @@ -993,91 +717,13 @@ class GuiReaderFrame extends JFrame { delete.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + final GuiReaderBook selectedBook = helpee.getSelectedBook(); if (selectedBook != null) { - outOfUi(null, new Runnable() { + helpee.outOfUi(null, new Runnable() { @Override public void run() { - final MetaData meta = selectedBook.getMeta(); - new JFrame() { - private static final long serialVersionUID = 1L; - @SuppressWarnings("unused") - private Object init = init(); - - private Object init() { - // Borders - int top = 20; - int space = 10; - - // Image - ImageIcon img = GuiReaderCoverImager - .generateCoverIcon( - reader.getLibrary(), meta); - - // frame - setTitle(meta.getLuid() + ": " - + meta.getTitle()); - - setSize(800, img.getIconHeight() + 2 * top); - setLayout(new BorderLayout()); - - // Main panel - JPanel mainPanel = new JPanel( - new BorderLayout()); - JPanel mainPanelKeys = new JPanel(); - mainPanelKeys.setLayout(new BoxLayout( - mainPanelKeys, BoxLayout.Y_AXIS)); - JPanel mainPanelValues = new JPanel(); - mainPanelValues.setLayout(new BoxLayout( - mainPanelValues, BoxLayout.Y_AXIS)); - - mainPanel.add(mainPanelKeys, - BorderLayout.WEST); - mainPanel.add(mainPanelValues, - BorderLayout.CENTER); - - List> infos = BasicReader - .getMetaDesc(meta); - - Color trans = new Color(0, 0, 0, 1); - for (Entry info : infos) { - JTextArea key = new JTextArea(info - .getKey()); - key.setFont(new Font(key.getFont() - .getFontName(), Font.BOLD, key - .getFont().getSize())); - key.setEditable(false); - key.setLineWrap(false); - key.setBackground(trans); - mainPanelKeys.add(key); - - JTextArea value = new JTextArea(info - .getValue()); - value.setEditable(false); - value.setLineWrap(false); - value.setBackground(trans); - mainPanelValues.add(value); - } - - // Image - JLabel imgLabel = new JLabel(img); - imgLabel.setVerticalAlignment(JLabel.TOP); - - // Borders - mainPanelKeys.setBorder(BorderFactory - .createEmptyBorder(top, space, 0, 0)); - mainPanelValues.setBorder(BorderFactory - .createEmptyBorder(top, space, 0, 0)); - imgLabel.setBorder(BorderFactory - .createEmptyBorder(0, space, 0, 0)); - - // Add all - add(imgLabel, BorderLayout.WEST); - add(mainPanel, BorderLayout.CENTER); - - return null; - } - - }.setVisible(true); + new GuiReaderPropertiesFrame(reader, selectedBook + .getMeta()).setVisible(true); } }); } @@ -1088,22 +734,24 @@ class GuiReaderFrame extends JFrame { } /** - * Create the open menu item for a book or a source (no LUID). + * Create the open menu item for a book or a source/type (no LUID). * * @return the item */ - private JMenuItem createMenuItemOpenBook() { + public JMenuItem createMenuItemOpenBook() { JMenuItem open = new JMenuItem("Open", KeyEvent.VK_O); open.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + final GuiReaderBook selectedBook = helpee.getSelectedBook(); if (selectedBook != null) { if (selectedBook.getMeta().getLuid() == null) { - removeBookPanes(); - addBookPane(selectedBook.getMeta().getSource(), true); - refreshBooks(); + helpee.removeBookPanes(); + helpee.addBookPane(selectedBook.getMeta().getSource(), + true); + helpee.refreshBooks(); } else { - openBook(selectedBook); + helpee.openBook(selectedBook); } } } @@ -1123,6 +771,7 @@ class GuiReaderFrame extends JFrame { open.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { + final GuiReaderBook selectedBook = helpee.getSelectedBook(); if (selectedBook != null) { reader.getLibrary().setSourceCover( selectedBook.getMeta().getSource(), @@ -1137,197 +786,6 @@ class GuiReaderFrame extends JFrame { return open; } - /** - * Open a {@link GuiReaderBook} item. - * - * @param book - * the {@link GuiReaderBook} to open - */ - private void openBook(final GuiReaderBook book) { - final Progress pg = new Progress(); - outOfUi(pg, new Runnable() { - @Override - public void run() { - try { - reader.read(book.getMeta().getLuid(), false, pg); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - book.setCached(true); - } - }); - } catch (IOException e) { - // TODO: error message? - Instance.getTraceHandler().error(e); - } - } - }); - } - - /** - * Process the given action out of the Swing UI thread and link the given - * {@link ProgressBar} to the action. - *

- * The code will make sure that the {@link ProgressBar} (if not NULL) is set - * to done when the action is done. - * - * @param progress - * the {@link ProgressBar} or NULL - * @param run - * the action to run - */ - private void outOfUi(Progress progress, final Runnable run) { - final Progress pg = new Progress(); - final Progress reload = new Progress("Reload books"); - if (progress == null) { - progress = new Progress(); - } - - pg.addProgress(progress, 90); - pg.addProgress(reload, 10); - - invalidate(); - pgBar.setProgress(pg); - validate(); - setEnabled(false); - - new Thread(new Runnable() { - @Override - public void run() { - try { - run.run(); - refreshBooks(); - } finally { - reload.done(); - if (!pg.isDone()) { - // will trigger pgBar ActionListener: - pg.done(); - } - } - } - }, "outOfUi thread").start(); - } - - /** - * Import a {@link Story} into the main {@link LocalLibrary}. - *

- * Should be called inside the UI thread. - * - * @param askUrl - * TRUE for an {@link URL}, false for a {@link File} - */ - private void imprt(boolean askUrl) { - JFileChooser fc = new JFileChooser(); - - Object url; - if (askUrl) { - String clipboard = ""; - try { - clipboard = ("" + Toolkit.getDefaultToolkit() - .getSystemClipboard().getData(DataFlavor.stringFlavor)) - .trim(); - } catch (Exception e) { - // No data will be handled - } - - if (clipboard == null || !clipboard.startsWith("http")) { - clipboard = ""; - } - - url = JOptionPane.showInputDialog(GuiReaderFrame.this, - "url of the story to import?", "Importing from URL", - JOptionPane.QUESTION_MESSAGE, null, null, clipboard); - } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) { - url = fc.getSelectedFile().getAbsolutePath(); - } else { - url = null; - } - - if (url != null && !url.toString().isEmpty()) { - imprt(url.toString(), null, null); - } - } - - /** - * Actually import the {@link Story} into the main {@link LocalLibrary}. - *

- * Should be called inside the UI thread. - * - * @param url - * the {@link Story} to import by {@link URL} - * @param onSuccess - * Action to execute on success - */ - private void imprt(final String url, final StoryRunnable onSuccess, - String onSuccessPgName) { - final Progress pg = new Progress(); - final Progress pgImprt = new Progress(); - final Progress pgOnSuccess = new Progress(onSuccessPgName); - pg.addProgress(pgImprt, 95); - pg.addProgress(pgOnSuccess, 5); - - outOfUi(pg, new Runnable() { - @Override - public void run() { - Exception ex = null; - Story story = null; - try { - story = reader.getLibrary().imprt(BasicReader.getUrl(url), - pgImprt); - } catch (IOException e) { - ex = e; - } - - final Exception e = ex; - - final boolean ok = (e == null); - - pgOnSuccess.setProgress(0); - if (!ok) { - if (e instanceof UnknownHostException) { - error("URL not supported: " + url, "Cannot import URL", - null); - } else { - error("Failed to import " + url + ": \n" - + e.getMessage(), "Cannot import URL", e); - } - } else { - if (onSuccess != null) { - onSuccess.run(story); - } - } - pgOnSuccess.done(); - } - }); - } - - /** - * 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 (bar != null) { - bar.setEnabled(b); - } - - for (GuiReaderGroup group : booksByType.keySet()) { - group.setEnabled(b); - } - for (GuiReaderGroup group : booksByAuthor.keySet()) { - group.setEnabled(b); - } - super.setEnabled(b); - repaint(); - } - /** * Display an error message and log the linked {@link Exception}. * @@ -1338,7 +796,7 @@ class GuiReaderFrame extends JFrame { * @param e * the exception to log if any */ - private void error(final String message, final String title, Exception e) { + public void error(final String message, final String title, Exception e) { Instance.getTraceHandler().error(title + ": " + message); if (e != null) { Instance.getTraceHandler().error(e); @@ -1352,4 +810,9 @@ class GuiReaderFrame extends JFrame { } }); } + + @Override + public GuiReader getReader() { + return reader; + } } diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java new file mode 100644 index 00000000..a53d5725 --- /dev/null +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java @@ -0,0 +1,625 @@ +package be.nikiroo.fanfix.reader.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Frame; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.swing.BoxLayout; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JMenuBar; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; + +import be.nikiroo.fanfix.Instance; +import be.nikiroo.fanfix.bundles.UiConfig; +import be.nikiroo.fanfix.data.MetaData; +import be.nikiroo.fanfix.data.Story; +import be.nikiroo.fanfix.library.BasicLibrary; +import be.nikiroo.fanfix.library.BasicLibrary.Status; +import be.nikiroo.fanfix.library.LocalLibrary; +import be.nikiroo.fanfix.reader.BasicReader; +import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener; +import be.nikiroo.utils.Progress; +import be.nikiroo.utils.ui.ProgressBar; + +/** + * A {@link Frame} that will show a {@link GuiReaderBook} item for each + * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a + * way to copy them to the {@link GuiReader} cache ( + * {@link BasicReader#getLibrary()}), read them, delete them... + * + * @author niki + */ +class GuiReaderMainPanel extends JPanel { + private static final long serialVersionUID = 1L; + private FrameHelper helper; + private Map booksByType; + private Map booksByAuthor; + private JPanel pane; + private Color color; + private ProgressBar pgBar; + private JMenuBar bar; + private GuiReaderBook selectedBook; + private boolean words; // words or authors (secondary info on books) + + /** + * An object that offers some helper methods to access the frame that host + * it and the Fanfix-related functions. + * + * @author niki + */ + public interface FrameHelper { + /** + * Return the reader associated to this {@link FrameHelper}. + * + * @return the reader + */ + public GuiReader getReader(); + + /** + * Create the main menu bar. + * + * @param libOk + * the library can be queried + * + * @return the bar + */ + public void createMenu(boolean b); + + /** + * Create a popup menu for a {@link GuiReaderBook} that represents a + * story. + * + * @return the popup menu to display + */ + public JPopupMenu createBookPopup(); + + /** + * Create a popup menu for a {@link GuiReaderBook} that represents a + * source/type (no LUID). + * + * @return the popup menu to display + */ + public JPopupMenu createSourcePopup(); + } + + /** + * A {@link Runnable} with a {@link Story} parameter. + * + * @author niki + */ + public interface StoryRunnable { + /** + * Run the action. + * + * @param story + * the story + */ + public void run(Story story); + } + + /** + * Create a new {@link GuiReaderMainPanel}. + * + * @param reader + * the associated {@link GuiReader} to forward some commands and + * access its {@link LocalLibrary} + * @param type + * the type of {@link Story} to load, or NULL for all types + */ + public GuiReaderMainPanel(FrameHelper parent, String type) { + super(new BorderLayout(), true); + + this.helper = parent; + + pane = new JPanel(); + pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS)); + + Integer icolor = Instance.getUiConfig().getColor( + UiConfig.BACKGROUND_COLOR); + if (icolor != null) { + color = new Color(icolor); + setBackground(color); + pane.setBackground(color); + } + + JScrollPane scroll = new JScrollPane(pane); + scroll.getVerticalScrollBar().setUnitIncrement(16); + add(scroll, BorderLayout.CENTER); + + String message = parent.getReader().getLibrary().getLibraryName(); + if (!message.isEmpty()) { + JLabel name = new JLabel(message, SwingConstants.CENTER); + add(name, BorderLayout.NORTH); + } + + pgBar = new ProgressBar(); + add(pgBar, BorderLayout.SOUTH); + + pgBar.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + invalidate(); + pgBar.setProgress(null); + validate(); + setEnabled(true); + } + }); + + pgBar.addUpdateListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + invalidate(); + validate(); + repaint(); + } + }); + + booksByType = new HashMap(); + booksByAuthor = new HashMap(); + + pane.setVisible(false); + final Progress pg = new Progress(); + final String typeF = type; + outOfUi(pg, new Runnable() { + @Override + public void run() { + BasicLibrary lib = helper.getReader().getLibrary(); + Status status = lib.getStatus(); + + if (status == Status.READY) { + lib.refresh(pg); + invalidate(); + helper.createMenu(true); + if (typeF == null) { + addBookPane(true, false); + } else { + addBookPane(typeF, true); + } + refreshBooks(); + validate(); + pane.setVisible(true); + } else { + invalidate(); + helper.createMenu(false); + validate(); + + String err = lib.getLibraryName() + "\n"; + switch (status) { + case INVALID: + err += "Library not valid"; + break; + + case UNAUTORIZED: + err += "You are not allowed to access this library"; + break; + + case UNAVAILABLE: + err += "Library currently unavailable"; + break; + + default: + err += "An error occured when contacting the library"; + break; + } + + error(err, "Library error", null); + } + } + }); + } + + /** + * Add a new {@link GuiReaderGroup} on the frame to display all the + * sources/types or all the authors, or a listing of all the books sorted + * either by source or author. + *

+ * A display of all the sources/types or all the authors will show one icon + * per source/type or author. + *

+ * A listing of all the books sorted by source/type or author will display + * all the books. + * + * @param type + * TRUE for type/source, FALSE for author + * @param listMode + * TRUE to get a listing of all the sources or authors, FALSE to + * get one icon per source or author + */ + public void addBookPane(boolean type, boolean listMode) { + BasicLibrary lib = helper.getReader().getLibrary(); + if (type) { + if (!listMode) { + addListPane("Sources", lib.getSources(), type); + } else { + for (String tt : lib.getSources()) { + if (tt != null) { + addBookPane(tt, type); + } + } + } + } else { + if (!listMode) { + addListPane("Authors", lib.getAuthors(), type); + } else { + for (String tt : lib.getAuthors()) { + if (tt != null) { + addBookPane(tt, type); + } + } + } + } + } + + /** + * Add a new {@link GuiReaderGroup} on the frame to display the books of the + * selected type or author. + * + * @param value + * the author or the type, or NULL to get all the + * authors-or-types + * @param type + * TRUE for type/source, FALSE for author + * + */ + public void addBookPane(String value, boolean type) { + GuiReaderGroup bookPane = new GuiReaderGroup(helper.getReader(), value, + color); + if (type) { + booksByType.put(bookPane, value); + } else { + booksByAuthor.put(bookPane, value); + } + + 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 = helper.createBookPopup(); + popup.show(e.getComponent(), e.getX(), e.getY()); + } + + @Override + public void action(final GuiReaderBook book) { + openBook(book); + } + }); + } + + /** + * Clear the pane from any book that may be present, usually prior to adding + * new ones. + */ + public void removeBookPanes() { + booksByType.clear(); + booksByAuthor.clear(); + pane.invalidate(); + this.invalidate(); + pane.removeAll(); + pane.validate(); + this.validate(); + } + + /** + * Refresh the list of {@link GuiReaderBook}s from disk. + */ + public void refreshBooks() { + BasicLibrary lib = helper.getReader().getLibrary(); + for (GuiReaderGroup group : booksByType.keySet()) { + List stories = lib + .getListBySource(booksByType.get(group)); + group.refreshBooks(stories, words); + } + + for (GuiReaderGroup group : booksByAuthor.keySet()) { + List stories = lib.getListByAuthor(booksByAuthor + .get(group)); + group.refreshBooks(stories, words); + } + + pane.repaint(); + this.repaint(); + } + + /** + * Open a {@link GuiReaderBook} item. + * + * @param book + * the {@link GuiReaderBook} to open + */ + public void openBook(final GuiReaderBook book) { + final Progress pg = new Progress(); + outOfUi(pg, new Runnable() { + @Override + public void run() { + try { + helper.getReader() + .read(book.getMeta().getLuid(), false, pg); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + book.setCached(true); + } + }); + } catch (IOException e) { + Instance.getTraceHandler().error(e); + error("Cannot open the selected book", "Error", e); + } + } + }); + } + + /** + * Process the given action out of the Swing UI thread and link the given + * {@link ProgressBar} to the action. + *

+ * The code will make sure that the {@link ProgressBar} (if not NULL) is set + * to done when the action is done. + * + * @param progress + * the {@link ProgressBar} or NULL + * @param run + * the action to run + */ + public void outOfUi(Progress progress, final Runnable run) { + final Progress pg = new Progress(); + final Progress reload = new Progress("Reload books"); + if (progress == null) { + progress = new Progress(); + } + + pg.addProgress(progress, 90); + pg.addProgress(reload, 10); + + invalidate(); + pgBar.setProgress(pg); + validate(); + setEnabled(false); + + new Thread(new Runnable() { + @Override + public void run() { + try { + run.run(); + refreshBooks(); + } finally { + reload.done(); + if (!pg.isDone()) { + // will trigger pgBar ActionListener: + pg.done(); + } + } + } + }, "outOfUi thread").start(); + } + + /** + * Import a {@link Story} into the main {@link LocalLibrary}. + *

+ * Should be called inside the UI thread. + * + * @param askUrl + * TRUE for an {@link URL}, false for a {@link File} + */ + public void imprt(boolean askUrl) { + JFileChooser fc = new JFileChooser(); + + Object url; + if (askUrl) { + String clipboard = ""; + try { + clipboard = ("" + Toolkit.getDefaultToolkit() + .getSystemClipboard().getData(DataFlavor.stringFlavor)) + .trim(); + } catch (Exception e) { + // No data will be handled + } + + if (clipboard == null || !clipboard.startsWith("http")) { + clipboard = ""; + } + + url = JOptionPane.showInputDialog(GuiReaderMainPanel.this, + "url of the story to import?", "Importing from URL", + JOptionPane.QUESTION_MESSAGE, null, null, clipboard); + } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) { + url = fc.getSelectedFile().getAbsolutePath(); + } else { + url = null; + } + + if (url != null && !url.toString().isEmpty()) { + imprt(url.toString(), null, null); + } + } + + /** + * Actually import the {@link Story} into the main {@link LocalLibrary}. + *

+ * Should be called inside the UI thread. + * + * @param url + * the {@link Story} to import by {@link URL} + * @param onSuccess + * Action to execute on success + */ + public void imprt(final String url, final StoryRunnable onSuccess, + String onSuccessPgName) { + final Progress pg = new Progress(); + final Progress pgImprt = new Progress(); + final Progress pgOnSuccess = new Progress(onSuccessPgName); + pg.addProgress(pgImprt, 95); + pg.addProgress(pgOnSuccess, 5); + + outOfUi(pg, new Runnable() { + @Override + public void run() { + Exception ex = null; + Story story = null; + try { + story = helper.getReader().getLibrary() + .imprt(BasicReader.getUrl(url), pgImprt); + } catch (IOException e) { + ex = e; + } + + final Exception e = ex; + + final boolean ok = (e == null); + + pgOnSuccess.setProgress(0); + if (!ok) { + if (e instanceof UnknownHostException) { + error("URL not supported: " + url, "Cannot import URL", + null); + } else { + error("Failed to import " + url + ": \n" + + e.getMessage(), "Cannot import URL", e); + } + } else { + if (onSuccess != null) { + onSuccess.run(story); + } + } + pgOnSuccess.done(); + } + }); + } + + /** + * 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. + *

+ * Enabling or 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 (bar != null) { + bar.setEnabled(b); + } + + for (GuiReaderGroup group : booksByType.keySet()) { + group.setEnabled(b); + } + for (GuiReaderGroup group : booksByAuthor.keySet()) { + group.setEnabled(b); + } + super.setEnabled(b); + repaint(); + } + + public void setWords(boolean words) { + this.words = words; + } + + public GuiReaderBook getSelectedBook() { + return selectedBook; + } + + public void unsetSelectedBook() { + selectedBook = null; + } + + private void addListPane(String name, List values, + final boolean type) { + // Sources -> i18n + GuiReaderGroup bookPane = new GuiReaderGroup(helper.getReader(), name, + color); + + List metas = new ArrayList(); + for (String source : values) { + MetaData mSource = new MetaData(); + mSource.setLuid(null); + mSource.setTitle(source); + mSource.setSource(source); + metas.add(mSource); + } + + bookPane.refreshBooks(metas, 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 = helper.createSourcePopup(); + popup.show(e.getComponent(), e.getX(), e.getY()); + } + + @Override + public void action(final GuiReaderBook book) { + removeBookPanes(); + addBookPane(book.getMeta().getSource(), type); + refreshBooks(); + } + }); + } + + /** + * Display an error message and log the linked {@link Exception}. + * + * @param message + * the message + * @param title + * the title of the error message + * @param e + * the exception to log if any + */ + private void error(final String message, final String title, Exception e) { + Instance.getTraceHandler().error(title + ": " + message); + if (e != null) { + Instance.getTraceHandler().error(e); + } + + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + JOptionPane.showMessageDialog(GuiReaderMainPanel.this, message, + title, JOptionPane.ERROR_MESSAGE); + } + }); + } +} diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java new file mode 100644 index 00000000..5a647de5 --- /dev/null +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReaderPropertiesFrame.java @@ -0,0 +1,84 @@ +package be.nikiroo.fanfix.reader.ui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Font; +import java.util.List; +import java.util.Map.Entry; + +import javax.swing.BorderFactory; +import javax.swing.BoxLayout; +import javax.swing.ImageIcon; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextArea; + +import be.nikiroo.fanfix.data.MetaData; +import be.nikiroo.fanfix.reader.BasicReader; +import be.nikiroo.fanfix.reader.Reader; + +public class GuiReaderPropertiesFrame extends JFrame { + private static final long serialVersionUID = 1L; + + public GuiReaderPropertiesFrame(Reader reader, MetaData meta) { + // Borders + int top = 20; + int space = 10; + + // Image + ImageIcon img = GuiReaderCoverImager.generateCoverIcon( + reader.getLibrary(), meta); + + // frame + setTitle(meta.getLuid() + ": " + meta.getTitle()); + + setSize(800, img.getIconHeight() + 2 * top); + setLayout(new BorderLayout()); + + // Main panel + JPanel mainPanel = new JPanel(new BorderLayout()); + JPanel mainPanelKeys = new JPanel(); + mainPanelKeys.setLayout(new BoxLayout(mainPanelKeys, BoxLayout.Y_AXIS)); + JPanel mainPanelValues = new JPanel(); + mainPanelValues.setLayout(new BoxLayout(mainPanelValues, + BoxLayout.Y_AXIS)); + + mainPanel.add(mainPanelKeys, BorderLayout.WEST); + mainPanel.add(mainPanelValues, BorderLayout.CENTER); + + List> infos = BasicReader.getMetaDesc(meta); + + Color trans = new Color(0, 0, 0, 1); + for (Entry info : infos) { + JTextArea key = new JTextArea(info.getKey()); + key.setFont(new Font(key.getFont().getFontName(), Font.BOLD, key + .getFont().getSize())); + key.setEditable(false); + key.setLineWrap(false); + key.setBackground(trans); + mainPanelKeys.add(key); + + JTextArea value = new JTextArea(info.getValue()); + value.setEditable(false); + value.setLineWrap(false); + value.setBackground(trans); + mainPanelValues.add(value); + } + + // Image + JLabel imgLabel = new JLabel(img); + imgLabel.setVerticalAlignment(JLabel.TOP); + + // Borders + mainPanelKeys.setBorder(BorderFactory.createEmptyBorder(top, space, 0, + 0)); + mainPanelValues.setBorder(BorderFactory.createEmptyBorder(top, space, + 0, 0)); + imgLabel.setBorder(BorderFactory.createEmptyBorder(0, space, 0, 0)); + + // Add all + add(imgLabel, BorderLayout.WEST); + add(mainPanel, BorderLayout.CENTER); + } +} -- 2.27.0