X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2Freader%2Fui%2FGuiReaderMainPanel.java;h=a5eb6916f5972b281d7b7237a3464e2f50ee7c55;hb=1387a30ab59dbf4071f2c5e5e0e08ca98c75b726;hp=a53d5725a53938feb5a76b6b51c66f52741f9e98;hpb=14bb95fae33d405c0a43682c144d081bfbcad545;p=fanfix.git diff --git a/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java b/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java index a53d572..a5eb691 100644 --- a/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java +++ b/src/be/nikiroo/fanfix/reader/ui/GuiReaderMainPanel.java @@ -2,20 +2,25 @@ package be.nikiroo.fanfix.reader.ui; import java.awt.BorderLayout; import java.awt.Color; +import java.awt.Component; +import java.awt.EventQueue; 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.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; import java.io.File; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.TreeMap; import javax.swing.BoxLayout; import javax.swing.JFileChooser; @@ -29,6 +34,7 @@ import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import be.nikiroo.fanfix.Instance; +import be.nikiroo.fanfix.bundles.StringIdGui; import be.nikiroo.fanfix.bundles.UiConfig; import be.nikiroo.fanfix.data.MetaData; import be.nikiroo.fanfix.data.Story; @@ -37,6 +43,7 @@ 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.fanfix.reader.ui.GuiReaderBookInfo.Type; import be.nikiroo.utils.Progress; import be.nikiroo.utils.ui.ProgressBar; @@ -51,14 +58,15 @@ import be.nikiroo.utils.ui.ProgressBar; class GuiReaderMainPanel extends JPanel { private static final long serialVersionUID = 1L; private FrameHelper helper; - private Map booksByType; - private Map booksByAuthor; + private Map books; + private GuiReaderGroup bookPane; // for more "All" 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 boolean currentType; // type/source or author mode (All and Listing) /** * An object that offers some helper methods to access the frame that host @@ -76,13 +84,13 @@ class GuiReaderMainPanel extends JPanel { /** * Create the main menu bar. + *

+ * Will invalidate the layout. * - * @param libOk - * the library can be queried - * - * @return the bar + * @param status + * the library status, must not be NULL */ - public void createMenu(boolean b); + public void createMenu(Status status); /** * Create a popup menu for a {@link GuiReaderBook} that represents a @@ -94,34 +102,34 @@ class GuiReaderMainPanel extends JPanel { /** * Create a popup menu for a {@link GuiReaderBook} that represents a - * source/type (no LUID). + * source/type or an author. * * @return the popup menu to display */ - public JPopupMenu createSourcePopup(); + public JPopupMenu createSourceAuthorPopup(); } /** - * A {@link Runnable} with a {@link Story} parameter. + * A {@link Runnable} with a {@link MetaData} parameter. * * @author niki */ - public interface StoryRunnable { + public interface MetaDataRunnable { /** * Run the action. * - * @param story - * the story + * @param meta + * the meta of the story */ - public void run(Story story); + public void run(MetaData meta); } /** * Create a new {@link GuiReaderMainPanel}. * - * @param reader - * the associated {@link GuiReader} to forward some commands and - * access its {@link LocalLibrary} + * @param parent + * the associated {@link FrameHelper} to forward some commands + * and access its {@link LocalLibrary} * @param type * the type of {@link Story} to load, or NULL for all types */ @@ -132,16 +140,16 @@ class GuiReaderMainPanel extends JPanel { pane = new JPanel(); pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS)); + JScrollPane scroll = new JScrollPane(pane); - Integer icolor = Instance.getUiConfig().getColor( - UiConfig.BACKGROUND_COLOR); + Integer icolor = Instance.getInstance().getUiConfig().getColor(UiConfig.BACKGROUND_COLOR); if (icolor != null) { color = new Color(icolor); setBackground(color); pane.setBackground(color); + scroll.setBackground(color); } - JScrollPane scroll = new JScrollPane(pane); scroll.getVerticalScrollBar().setUnitIncrement(16); add(scroll, BorderLayout.CENTER); @@ -157,76 +165,86 @@ class GuiReaderMainPanel extends JPanel { pgBar.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - invalidate(); + pgBar.invalidate(); pgBar.setProgress(null); - validate(); setEnabled(true); + validate(); } }); pgBar.addUpdateListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - invalidate(); + pgBar.invalidate(); validate(); repaint(); } }); - booksByType = new HashMap(); - booksByAuthor = new HashMap(); + books = new TreeMap(); + + addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + focus(); + } + }); pane.setVisible(false); final Progress pg = new Progress(); final String typeF = type; - outOfUi(pg, new Runnable() { + outOfUi(pg, true, new Runnable() { @Override public void run() { - BasicLibrary lib = helper.getReader().getLibrary(); - Status status = lib.getStatus(); + final BasicLibrary lib = helper.getReader().getLibrary(); + final Status status = lib.getStatus(); - if (status == Status.READY) { + if (status == Status.READ_WRITE) { 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); } + + inUi(new Runnable() { + @Override + public void run() { + if (status.isReady()) { + helper.createMenu(status); + pane.setVisible(true); + if (typeF == null) { + try { + addBookPane(true, false); + } catch (IOException e) { + error(e.getLocalizedMessage(), + "IOException", e); + } + } else { + addBookPane(typeF, true); + } + } else { + helper.createMenu(status); + validate(); + + String desc = Instance.getInstance().getTransGui().getStringX(StringIdGui.ERROR_LIB_STATUS, + status.toString()); + if (desc == null) { + desc = GuiReader + .trans(StringIdGui.ERROR_LIB_STATUS); + } + + String err = lib.getLibraryName() + "\n" + desc; + error(err, GuiReader + .trans(StringIdGui.TITLE_ERROR_LIBRARY), + null); + } + } + }); } }); } + public boolean getCurrentType() { + return currentType; + } + /** * 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 @@ -243,12 +261,17 @@ class GuiReaderMainPanel extends JPanel { * @param listMode * TRUE to get a listing of all the sources or authors, FALSE to * get one icon per source or author + * + * @throws IOException + * in case of I/O error */ - public void addBookPane(boolean type, boolean listMode) { + public void addBookPane(boolean type, boolean listMode) throws IOException { + this.currentType = type; BasicLibrary lib = helper.getReader().getLibrary(); if (type) { if (!listMode) { - addListPane("Sources", lib.getSources(), type); + addListPane(GuiReader.trans(StringIdGui.MENU_SOURCES), + lib.getSources(), type); } else { for (String tt : lib.getSources()) { if (tt != null) { @@ -258,7 +281,8 @@ class GuiReaderMainPanel extends JPanel { } } else { if (!listMode) { - addListPane("Authors", lib.getAuthors(), type); + addListPane(GuiReader.trans(StringIdGui.MENU_AUTHORS), + lib.getAuthors(), type); } else { for (String tt : lib.getAuthors()) { if (tt != null) { @@ -272,28 +296,25 @@ class GuiReaderMainPanel extends JPanel { /** * Add a new {@link GuiReaderGroup} on the frame to display the books of the * selected type or author. + *

+ * Will invalidate the layout. * * @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) { + this.currentType = type; + GuiReaderGroup bookPane = new GuiReaderGroup(helper.getReader(), value, color); - if (type) { - booksByType.put(bookPane, value); - } else { - booksByAuthor.put(bookPane, value); - } - this.invalidate(); + books.put(value, bookPane); + pane.invalidate(); pane.add(bookPane); - pane.validate(); - this.validate(); bookPane.setActionListener(new BookActionListener() { @Override @@ -302,9 +323,10 @@ class GuiReaderMainPanel extends JPanel { } @Override - public void popupRequested(GuiReaderBook book, MouseEvent e) { + public void popupRequested(GuiReaderBook book, Component target, + int x, int y) { JPopupMenu popup = helper.createBookPopup(); - popup.show(e.getComponent(), e.getX(), e.getY()); + popup.show(target, x, y); } @Override @@ -312,41 +334,56 @@ class GuiReaderMainPanel extends JPanel { openBook(book); } }); + + focus(); } /** * Clear the pane from any book that may be present, usually prior to adding * new ones. + *

+ * Will invalidate the layout. */ public void removeBookPanes() { - booksByType.clear(); - booksByAuthor.clear(); + books.clear(); pane.invalidate(); - this.invalidate(); pane.removeAll(); - pane.validate(); - this.validate(); } /** * Refresh the list of {@link GuiReaderBook}s from disk. + *

+ * Will validate the layout, as it is a "refresh" operation. */ 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 (String value : books.keySet()) { + List infos = new ArrayList(); + + List metas; + try { + if (currentType) { + metas = lib.getList().filter(value, null, null); + } else { + metas = lib.getList().filter(null, value, null); + } + } catch (IOException e) { + error(e.getLocalizedMessage(), "IOException", e); + metas = new ArrayList(); + } + + for (MetaData meta : metas) { + infos.add(GuiReaderBookInfo.fromMeta(meta)); + } + + books.get(value).refreshBooks(infos, words); } - for (GuiReaderGroup group : booksByAuthor.keySet()) { - List stories = lib.getListByAuthor(booksByAuthor - .get(group)); - group.refreshBooks(stories, words); + if (bookPane != null) { + bookPane.refreshBooks(words); } - pane.repaint(); - this.repaint(); + this.validate(); } /** @@ -357,12 +394,12 @@ class GuiReaderMainPanel extends JPanel { */ public void openBook(final GuiReaderBook book) { final Progress pg = new Progress(); - outOfUi(pg, new Runnable() { + outOfUi(pg, false, new Runnable() { @Override public void run() { try { - helper.getReader() - .read(book.getMeta().getLuid(), false, pg); + helper.getReader().read(book.getInfo().getMeta().getLuid(), + false, pg); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { @@ -370,8 +407,73 @@ class GuiReaderMainPanel extends JPanel { } }); } catch (IOException e) { - Instance.getTraceHandler().error(e); - error("Cannot open the selected book", "Error", e); + Instance.getInstance().getTraceHandler().error(e); + error(GuiReader.trans(StringIdGui.ERROR_CANNOT_OPEN), + GuiReader.trans(StringIdGui.TITLE_ERROR), e); + } + } + }); + } + + /** + * Prefetch a {@link GuiReaderBook} item (which can be a group, in which + * case we prefetch all its members). + * + * @param book + * the {@link GuiReaderBook} to open + */ + public void prefetchBook(final GuiReaderBook book) { + final List luids = new LinkedList(); + try { + switch (book.getInfo().getType()) { + case STORY: + luids.add(book.getInfo().getMeta().getLuid()); + break; + case SOURCE: + for (MetaData meta : helper.getReader().getLibrary() + .getList().filter(book.getInfo().getMainInfo(), null, null)) { + luids.add(meta.getLuid()); + } + break; + case AUTHOR: + for (MetaData meta : helper.getReader().getLibrary() + .getList().filter(null, book.getInfo().getMainInfo(), null)) { + luids.add(meta.getLuid()); + } + break; + } + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + } + + final Progress pg = new Progress(); + pg.setMax(luids.size()); + + outOfUi(pg, false, new Runnable() { + @Override + public void run() { + try { + for (String luid : luids) { + Progress pgStep = new Progress(); + pg.addProgress(pgStep, 1); + + helper.getReader().prefetch(luid, pgStep); + } + + // TODO: also set the green button on sources/authors? + // requires to do the same when all stories inside are green + if (book.getInfo().getType() == Type.STORY) { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + book.setCached(true); + } + }); + } + } catch (IOException e) { + Instance.getInstance().getTraceHandler().error(e); + error(GuiReader.trans(StringIdGui.ERROR_CANNOT_OPEN), + GuiReader.trans(StringIdGui.TITLE_ERROR), e); } } }); @@ -386,18 +488,27 @@ class GuiReaderMainPanel extends JPanel { * * @param progress * the {@link ProgressBar} or NULL + * @param refreshBooks + * TRUE to refresh the books after * @param run * the action to run */ - public void outOfUi(Progress progress, final Runnable run) { + public void outOfUi(Progress progress, final boolean refreshBooks, + final Runnable run) { final Progress pg = new Progress(); - final Progress reload = new Progress("Reload books"); + final Progress reload = new Progress( + GuiReader.trans(StringIdGui.PROGRESS_OUT_OF_UI_RELOAD_BOOKS)); + if (progress == null) { progress = new Progress(); } - pg.addProgress(progress, 90); - pg.addProgress(reload, 10); + if (refreshBooks) { + pg.addProgress(progress, 100); + } else { + pg.addProgress(progress, 90); + pg.addProgress(reload, 10); + } invalidate(); pgBar.setProgress(pg); @@ -409,7 +520,9 @@ class GuiReaderMainPanel extends JPanel { public void run() { try { run.run(); - refreshBooks(); + if (refreshBooks) { + refreshBooks(); + } } finally { reload.done(); if (!pg.isDone()) { @@ -421,6 +534,31 @@ class GuiReaderMainPanel extends JPanel { }, "outOfUi thread").start(); } + /** + * Process the given action in the main Swing UI thread. + *

+ * The code will make sure the current thread is the main UI thread and, if + * not, will switch to it before executing the runnable. + *

+ * Synchronous operation. + * + * @param run + * the action to run + */ + public void inUi(final Runnable run) { + if (EventQueue.isDispatchThread()) { + run.run(); + } else { + try { + EventQueue.invokeAndWait(run); + } catch (InterruptedException e) { + Instance.getInstance().getTraceHandler().error(e); + } catch (InvocationTargetException e) { + Instance.getInstance().getTraceHandler().error(e); + } + } + } + /** * Import a {@link Story} into the main {@link LocalLibrary}. *

@@ -443,12 +581,14 @@ class GuiReaderMainPanel extends JPanel { // No data will be handled } - if (clipboard == null || !clipboard.startsWith("http")) { + if (clipboard == null || !(clipboard.startsWith("http://") || // + clipboard.startsWith("https://"))) { clipboard = ""; } url = JOptionPane.showInputDialog(GuiReaderMainPanel.this, - "url of the story to import?", "Importing from URL", + GuiReader.trans(StringIdGui.SUBTITLE_IMPORT_URL), + GuiReader.trans(StringIdGui.TITLE_IMPORT_URL), JOptionPane.QUESTION_MESSAGE, null, null, clipboard); } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) { url = fc.getSelectedFile().getAbsolutePath(); @@ -470,8 +610,10 @@ class GuiReaderMainPanel extends JPanel { * the {@link Story} to import by {@link URL} * @param onSuccess * Action to execute on success + * @param onSuccessPgName + * the name to use for the onSuccess progress bar */ - public void imprt(final String url, final StoryRunnable onSuccess, + public void imprt(final String url, final MetaDataRunnable onSuccess, String onSuccessPgName) { final Progress pg = new Progress(); final Progress pgImprt = new Progress(); @@ -479,13 +621,13 @@ class GuiReaderMainPanel extends JPanel { pg.addProgress(pgImprt, 95); pg.addProgress(pgOnSuccess, 5); - outOfUi(pg, new Runnable() { + outOfUi(pg, true, new Runnable() { @Override public void run() { Exception ex = null; - Story story = null; + MetaData meta = null; try { - story = helper.getReader().getLibrary() + meta = helper.getReader().getLibrary() .imprt(BasicReader.getUrl(url), pgImprt); } catch (IOException e) { ex = e; @@ -498,15 +640,18 @@ class GuiReaderMainPanel extends JPanel { pgOnSuccess.setProgress(0); if (!ok) { if (e instanceof UnknownHostException) { - error("URL not supported: " + url, "Cannot import URL", - null); + error(GuiReader.trans( + StringIdGui.ERROR_URL_NOT_SUPPORTED, url), + GuiReader.trans(StringIdGui.TITLE_ERROR), null); } else { - error("Failed to import " + url + ": \n" - + e.getMessage(), "Cannot import URL", e); + error(GuiReader.trans( + StringIdGui.ERROR_URL_IMPORT_FAILED, url, + e.getMessage()), GuiReader + .trans(StringIdGui.TITLE_ERROR), e); } } else { if (onSuccess != null) { - onSuccess.run(story); + onSuccess.run(meta); } } pgOnSuccess.done(); @@ -532,10 +677,7 @@ class GuiReaderMainPanel extends JPanel { bar.setEnabled(b); } - for (GuiReaderGroup group : booksByType.keySet()) { - group.setEnabled(b); - } - for (GuiReaderGroup group : booksByAuthor.keySet()) { + for (GuiReaderGroup group : books.values()) { group.setEnabled(b); } super.setEnabled(b); @@ -556,20 +698,21 @@ class GuiReaderMainPanel extends JPanel { private void addListPane(String name, List values, final boolean type) { - // Sources -> i18n - GuiReaderGroup bookPane = new GuiReaderGroup(helper.getReader(), name, - color); + GuiReader reader = helper.getReader(); + BasicLibrary lib = reader.getLibrary(); + + 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); + List infos = new ArrayList(); + for (String value : values) { + if (type) { + infos.add(GuiReaderBookInfo.fromSource(lib, value)); + } else { + infos.add(GuiReaderBookInfo.fromAuthor(lib, value)); + } } - bookPane.refreshBooks(metas, false); + bookPane.refreshBooks(infos, words); this.invalidate(); pane.invalidate(); @@ -584,18 +727,40 @@ class GuiReaderMainPanel extends JPanel { } @Override - public void popupRequested(GuiReaderBook book, MouseEvent e) { - JPopupMenu popup = helper.createSourcePopup(); - popup.show(e.getComponent(), e.getX(), e.getY()); + public void popupRequested(GuiReaderBook book, Component target, + int x, int y) { + JPopupMenu popup = helper.createSourceAuthorPopup(); + popup.show(target, x, y); } @Override public void action(final GuiReaderBook book) { removeBookPanes(); - addBookPane(book.getMeta().getSource(), type); + addBookPane(book.getInfo().getMainInfo(), type); refreshBooks(); } }); + + focus(); + } + + /** + * Focus the first {@link GuiReaderGroup} we find. + */ + private void focus() { + GuiReaderGroup group = null; + Map books = this.books; + if (books.size() > 0) { + group = books.values().iterator().next(); + } + + if (group == null) { + group = bookPane; + } + + if (group != null) { + group.requestFocusInWindow(); + } } /** @@ -609,9 +774,9 @@ class GuiReaderMainPanel extends JPanel { * the exception to log if any */ private void error(final String message, final String title, Exception e) { - Instance.getTraceHandler().error(title + ": " + message); + Instance.getInstance().getTraceHandler().error(title + ": " + message); if (e != null) { - Instance.getTraceHandler().error(e); + Instance.getInstance().getTraceHandler().error(e); } SwingUtilities.invokeLater(new Runnable() {