1 package be
.nikiroo
.fanfix
.reader
.ui
;
3 import java
.awt
.BorderLayout
;
5 import java
.awt
.EventQueue
;
7 import java
.awt
.Toolkit
;
8 import java
.awt
.datatransfer
.DataFlavor
;
9 import java
.awt
.event
.ActionEvent
;
10 import java
.awt
.event
.ActionListener
;
11 import java
.awt
.event
.MouseEvent
;
13 import java
.io
.IOException
;
14 import java
.lang
.reflect
.InvocationTargetException
;
16 import java
.net
.UnknownHostException
;
17 import java
.util
.ArrayList
;
18 import java
.util
.List
;
20 import java
.util
.TreeMap
;
22 import javax
.swing
.BoxLayout
;
23 import javax
.swing
.JFileChooser
;
24 import javax
.swing
.JLabel
;
25 import javax
.swing
.JMenuBar
;
26 import javax
.swing
.JOptionPane
;
27 import javax
.swing
.JPanel
;
28 import javax
.swing
.JPopupMenu
;
29 import javax
.swing
.JScrollPane
;
30 import javax
.swing
.SwingConstants
;
31 import javax
.swing
.SwingUtilities
;
33 import be
.nikiroo
.fanfix
.Instance
;
34 import be
.nikiroo
.fanfix
.bundles
.UiConfig
;
35 import be
.nikiroo
.fanfix
.data
.MetaData
;
36 import be
.nikiroo
.fanfix
.data
.Story
;
37 import be
.nikiroo
.fanfix
.library
.BasicLibrary
;
38 import be
.nikiroo
.fanfix
.library
.BasicLibrary
.Status
;
39 import be
.nikiroo
.fanfix
.library
.LocalLibrary
;
40 import be
.nikiroo
.fanfix
.reader
.BasicReader
;
41 import be
.nikiroo
.fanfix
.reader
.ui
.GuiReaderBook
.BookActionListener
;
42 import be
.nikiroo
.utils
.Progress
;
43 import be
.nikiroo
.utils
.ui
.ProgressBar
;
46 * A {@link Frame} that will show a {@link GuiReaderBook} item for each
47 * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
48 * way to copy them to the {@link GuiReader} cache (
49 * {@link BasicReader#getLibrary()}), read them, delete them...
53 class GuiReaderMainPanel
extends JPanel
{
54 private static final long serialVersionUID
= 1L;
55 private FrameHelper helper
;
56 private Map
<String
, GuiReaderGroup
> books
;
57 private GuiReaderGroup bookPane
; // for more "All"
60 private ProgressBar pgBar
;
62 private GuiReaderBook selectedBook
;
63 private boolean words
; // words or authors (secondary info on books)
64 private boolean currentType
; // type/source or author mode (All and Listing)
67 * An object that offers some helper methods to access the frame that host
68 * it and the Fanfix-related functions.
72 public interface FrameHelper
{
74 * Return the reader associated to this {@link FrameHelper}.
78 public GuiReader
getReader();
81 * Create the main menu bar.
83 * Wil invalidate the layout.
86 * the library can be queried
90 public void createMenu(boolean b
);
93 * Create a popup menu for a {@link GuiReaderBook} that represents a
96 * @return the popup menu to display
98 public JPopupMenu
createBookPopup();
101 * Create a popup menu for a {@link GuiReaderBook} that represents a
102 * source/type or an author.
104 * @return the popup menu to display
106 public JPopupMenu
createSourceAuthorPopup();
110 * A {@link Runnable} with a {@link Story} parameter.
114 public interface StoryRunnable
{
121 public void run(Story story
);
125 * Create a new {@link GuiReaderMainPanel}.
128 * the associated {@link GuiReader} to forward some commands and
129 * access its {@link LocalLibrary}
131 * the type of {@link Story} to load, or NULL for all types
133 public GuiReaderMainPanel(FrameHelper parent
, String type
) {
134 super(new BorderLayout(), true);
136 this.helper
= parent
;
139 pane
.setLayout(new BoxLayout(pane
, BoxLayout
.PAGE_AXIS
));
141 Integer icolor
= Instance
.getUiConfig().getColor(
142 UiConfig
.BACKGROUND_COLOR
);
143 if (icolor
!= null) {
144 color
= new Color(icolor
);
145 setBackground(color
);
146 pane
.setBackground(color
);
149 JScrollPane scroll
= new JScrollPane(pane
);
150 scroll
.getVerticalScrollBar().setUnitIncrement(16);
151 add(scroll
, BorderLayout
.CENTER
);
153 String message
= parent
.getReader().getLibrary().getLibraryName();
154 if (!message
.isEmpty()) {
155 JLabel name
= new JLabel(message
, SwingConstants
.CENTER
);
156 add(name
, BorderLayout
.NORTH
);
159 pgBar
= new ProgressBar();
160 add(pgBar
, BorderLayout
.SOUTH
);
162 pgBar
.addActionListener(new ActionListener() {
164 public void actionPerformed(ActionEvent e
) {
166 pgBar
.setProgress(null);
172 pgBar
.addUpdateListener(new ActionListener() {
174 public void actionPerformed(ActionEvent e
) {
181 books
= new TreeMap
<String
, GuiReaderGroup
>();
183 pane
.setVisible(false);
184 final Progress pg
= new Progress();
185 final String typeF
= type
;
186 outOfUi(pg
, new Runnable() {
189 final BasicLibrary lib
= helper
.getReader().getLibrary();
190 final Status status
= lib
.getStatus();
192 if (status
== Status
.READY
) {
196 inUi(new Runnable() {
199 if (status
== Status
.READY
) {
200 helper
.createMenu(true);
202 addBookPane(true, false);
204 addBookPane(typeF
, true);
206 pane
.setVisible(true);
209 helper
.createMenu(false);
212 String err
= lib
.getLibraryName() + "\n";
215 err
+= "Library not valid";
219 err
+= "You are not allowed to access this library";
223 err
+= "Library currently unavailable";
227 err
+= "An error occured when contacting the library";
231 error(err
, "Library error", null);
239 public boolean getCurrentType() {
244 * Add a new {@link GuiReaderGroup} on the frame to display all the
245 * sources/types or all the authors, or a listing of all the books sorted
246 * either by source or author.
248 * A display of all the sources/types or all the authors will show one icon
249 * per source/type or author.
251 * A listing of all the books sorted by source/type or author will display
255 * TRUE for type/source, FALSE for author
257 * TRUE to get a listing of all the sources or authors, FALSE to
258 * get one icon per source or author
260 public void addBookPane(boolean type
, boolean listMode
) {
261 this.currentType
= type
;
262 BasicLibrary lib
= helper
.getReader().getLibrary();
265 addListPane("Sources", lib
.getSources(), type
);
267 for (String tt
: lib
.getSources()) {
269 addBookPane(tt
, type
);
275 addListPane("Authors", lib
.getAuthors(), type
);
277 for (String tt
: lib
.getAuthors()) {
279 addBookPane(tt
, type
);
287 * Add a new {@link GuiReaderGroup} on the frame to display the books of the
288 * selected type or author.
290 * Will invalidate the layout.
293 * the author or the type, or NULL to get all the
296 * TRUE for type/source, FALSE for author
298 public void addBookPane(String value
, boolean type
) {
299 this.currentType
= type
;
301 GuiReaderGroup bookPane
= new GuiReaderGroup(helper
.getReader(), value
,
304 books
.put(value
, bookPane
);
309 bookPane
.setActionListener(new BookActionListener() {
311 public void select(GuiReaderBook book
) {
316 public void popupRequested(GuiReaderBook book
, MouseEvent e
) {
317 JPopupMenu popup
= helper
.createBookPopup();
318 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
322 public void action(final GuiReaderBook book
) {
329 * Clear the pane from any book that may be present, usually prior to adding
332 * Will invalidate the layout.
334 public void removeBookPanes() {
341 * Refresh the list of {@link GuiReaderBook}s from disk.
343 * Will validate the layout, as it is a "refresh" operation.
345 public void refreshBooks() {
346 BasicLibrary lib
= helper
.getReader().getLibrary();
347 for (String value
: books
.keySet()) {
348 List
<GuiReaderBookInfo
> infos
= new ArrayList
<GuiReaderBookInfo
>();
350 List
<MetaData
> metas
;
352 metas
= lib
.getListBySource(value
);
354 metas
= lib
.getListByAuthor(value
);
356 for (MetaData meta
: metas
) {
357 infos
.add(GuiReaderBookInfo
.fromMeta(meta
));
360 books
.get(value
).refreshBooks(infos
, words
);
363 if (bookPane
!= null) {
364 bookPane
.refreshBooks(words
);
371 * Open a {@link GuiReaderBook} item.
374 * the {@link GuiReaderBook} to open
376 public void openBook(final GuiReaderBook book
) {
377 final Progress pg
= new Progress();
378 outOfUi(pg
, new Runnable() {
382 helper
.getReader().read(book
.getInfo().getMeta().getLuid(),
384 SwingUtilities
.invokeLater(new Runnable() {
387 book
.setCached(true);
390 } catch (IOException e
) {
391 Instance
.getTraceHandler().error(e
);
392 error("Cannot open the selected book", "Error", e
);
399 * Process the given action out of the Swing UI thread and link the given
400 * {@link ProgressBar} to the action.
402 * The code will make sure that the {@link ProgressBar} (if not NULL) is set
403 * to done when the action is done.
406 * the {@link ProgressBar} or NULL
410 public void outOfUi(Progress progress
, final Runnable run
) {
411 final Progress pg
= new Progress();
412 final Progress reload
= new Progress("Reload books");
413 if (progress
== null) {
414 progress
= new Progress();
417 pg
.addProgress(progress
, 90);
418 pg
.addProgress(reload
, 10);
421 pgBar
.setProgress(pg
);
425 new Thread(new Runnable() {
434 // will trigger pgBar ActionListener:
439 }, "outOfUi thread").start();
443 * Process the given action in the main Swing UI thread.
445 * The code will make sure the current thread is the main UI thread and, if
446 * not, will switch to it before executing the runnable.
448 * Synchronous operation.
453 public void inUi(final Runnable run
) {
454 if (EventQueue
.isDispatchThread()) {
458 EventQueue
.invokeAndWait(run
);
459 } catch (InterruptedException e
) {
460 Instance
.getTraceHandler().error(e
);
461 } catch (InvocationTargetException e
) {
462 Instance
.getTraceHandler().error(e
);
468 * Import a {@link Story} into the main {@link LocalLibrary}.
470 * Should be called inside the UI thread.
473 * TRUE for an {@link URL}, false for a {@link File}
475 public void imprt(boolean askUrl
) {
476 JFileChooser fc
= new JFileChooser();
480 String clipboard
= "";
482 clipboard
= ("" + Toolkit
.getDefaultToolkit()
483 .getSystemClipboard().getData(DataFlavor
.stringFlavor
))
485 } catch (Exception e
) {
486 // No data will be handled
489 if (clipboard
== null || !clipboard
.startsWith("http")) {
493 url
= JOptionPane
.showInputDialog(GuiReaderMainPanel
.this,
494 "url of the story to import?", "Importing from URL",
495 JOptionPane
.QUESTION_MESSAGE
, null, null, clipboard
);
496 } else if (fc
.showOpenDialog(this) != JFileChooser
.CANCEL_OPTION
) {
497 url
= fc
.getSelectedFile().getAbsolutePath();
502 if (url
!= null && !url
.toString().isEmpty()) {
503 imprt(url
.toString(), null, null);
508 * Actually import the {@link Story} into the main {@link LocalLibrary}.
510 * Should be called inside the UI thread.
513 * the {@link Story} to import by {@link URL}
515 * Action to execute on success
517 public void imprt(final String url
, final StoryRunnable onSuccess
,
518 String onSuccessPgName
) {
519 final Progress pg
= new Progress();
520 final Progress pgImprt
= new Progress();
521 final Progress pgOnSuccess
= new Progress(onSuccessPgName
);
522 pg
.addProgress(pgImprt
, 95);
523 pg
.addProgress(pgOnSuccess
, 5);
525 outOfUi(pg
, new Runnable() {
531 story
= helper
.getReader().getLibrary()
532 .imprt(BasicReader
.getUrl(url
), pgImprt
);
533 } catch (IOException e
) {
537 final Exception e
= ex
;
539 final boolean ok
= (e
== null);
541 pgOnSuccess
.setProgress(0);
543 if (e
instanceof UnknownHostException
) {
544 error("URL not supported: " + url
, "Cannot import URL",
547 error("Failed to import " + url
+ ": \n"
548 + e
.getMessage(), "Cannot import URL", e
);
551 if (onSuccess
!= null) {
552 onSuccess
.run(story
);
561 * Enables or disables this component, depending on the value of the
562 * parameter <code>b</code>. An enabled component can respond to user input
563 * and generate events. Components are enabled initially by default.
565 * Enabling or disabling <b>this</b> component will also affect its
569 * If <code>true</code>, this component is enabled; otherwise
570 * this component is disabled
573 public void setEnabled(boolean b
) {
578 for (GuiReaderGroup group
: books
.values()) {
585 public void setWords(boolean words
) {
589 public GuiReaderBook
getSelectedBook() {
593 public void unsetSelectedBook() {
597 private void addListPane(String name
, List
<String
> values
,
598 final boolean type
) {
599 GuiReader reader
= helper
.getReader();
600 BasicLibrary lib
= reader
.getLibrary();
602 bookPane
= new GuiReaderGroup(reader
, name
, color
);
604 List
<GuiReaderBookInfo
> infos
= new ArrayList
<GuiReaderBookInfo
>();
605 for (String value
: values
) {
607 infos
.add(GuiReaderBookInfo
.fromSource(lib
, value
));
609 infos
.add(GuiReaderBookInfo
.fromAuthor(lib
, value
));
613 bookPane
.refreshBooks(infos
, words
);
621 bookPane
.setActionListener(new BookActionListener() {
623 public void select(GuiReaderBook book
) {
628 public void popupRequested(GuiReaderBook book
, MouseEvent e
) {
629 JPopupMenu popup
= helper
.createSourceAuthorPopup();
630 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
634 public void action(final GuiReaderBook book
) {
636 addBookPane(book
.getInfo().getMainInfo(), type
);
643 * Display an error message and log the linked {@link Exception}.
648 * the title of the error message
650 * the exception to log if any
652 private void error(final String message
, final String title
, Exception e
) {
653 Instance
.getTraceHandler().error(title
+ ": " + message
);
655 Instance
.getTraceHandler().error(e
);
658 SwingUtilities
.invokeLater(new Runnable() {
661 JOptionPane
.showMessageDialog(GuiReaderMainPanel
.this, message
,
662 title
, JOptionPane
.ERROR_MESSAGE
);