1 package be
.nikiroo
.fanfix
.reader
.ui
;
3 import java
.awt
.BorderLayout
;
6 import java
.awt
.Toolkit
;
7 import java
.awt
.datatransfer
.DataFlavor
;
8 import java
.awt
.event
.ActionEvent
;
9 import java
.awt
.event
.ActionListener
;
10 import java
.awt
.event
.MouseEvent
;
12 import java
.io
.IOException
;
14 import java
.net
.UnknownHostException
;
15 import java
.util
.ArrayList
;
16 import java
.util
.HashMap
;
17 import java
.util
.List
;
20 import javax
.swing
.BoxLayout
;
21 import javax
.swing
.JFileChooser
;
22 import javax
.swing
.JLabel
;
23 import javax
.swing
.JMenuBar
;
24 import javax
.swing
.JOptionPane
;
25 import javax
.swing
.JPanel
;
26 import javax
.swing
.JPopupMenu
;
27 import javax
.swing
.JScrollPane
;
28 import javax
.swing
.SwingConstants
;
29 import javax
.swing
.SwingUtilities
;
31 import be
.nikiroo
.fanfix
.Instance
;
32 import be
.nikiroo
.fanfix
.bundles
.UiConfig
;
33 import be
.nikiroo
.fanfix
.data
.MetaData
;
34 import be
.nikiroo
.fanfix
.data
.Story
;
35 import be
.nikiroo
.fanfix
.library
.BasicLibrary
;
36 import be
.nikiroo
.fanfix
.library
.BasicLibrary
.Status
;
37 import be
.nikiroo
.fanfix
.library
.LocalLibrary
;
38 import be
.nikiroo
.fanfix
.reader
.BasicReader
;
39 import be
.nikiroo
.fanfix
.reader
.ui
.GuiReaderBook
.BookActionListener
;
40 import be
.nikiroo
.utils
.Progress
;
41 import be
.nikiroo
.utils
.ui
.ProgressBar
;
44 * A {@link Frame} that will show a {@link GuiReaderBook} item for each
45 * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
46 * way to copy them to the {@link GuiReader} cache (
47 * {@link BasicReader#getLibrary()}), read them, delete them...
51 class GuiReaderMainPanel
extends JPanel
{
52 private static final long serialVersionUID
= 1L;
53 private FrameHelper helper
;
54 private Map
<GuiReaderGroup
, String
> booksByType
;
55 private Map
<GuiReaderGroup
, String
> booksByAuthor
;
58 private ProgressBar pgBar
;
60 private GuiReaderBook selectedBook
;
61 private boolean words
; // words or authors (secondary info on books)
64 * An object that offers some helper methods to access the frame that host
65 * it and the Fanfix-related functions.
69 public interface FrameHelper
{
71 * Return the reader associated to this {@link FrameHelper}.
75 public GuiReader
getReader();
78 * Create the main menu bar.
81 * the library can be queried
85 public void createMenu(boolean b
);
88 * Create a popup menu for a {@link GuiReaderBook} that represents a
91 * @return the popup menu to display
93 public JPopupMenu
createBookPopup();
96 * Create a popup menu for a {@link GuiReaderBook} that represents a
97 * source/type (no LUID).
99 * @return the popup menu to display
101 public JPopupMenu
createSourcePopup();
105 * A {@link Runnable} with a {@link Story} parameter.
109 public interface StoryRunnable
{
116 public void run(Story story
);
120 * Create a new {@link GuiReaderMainPanel}.
123 * the associated {@link GuiReader} to forward some commands and
124 * access its {@link LocalLibrary}
126 * the type of {@link Story} to load, or NULL for all types
128 public GuiReaderMainPanel(FrameHelper parent
, String type
) {
129 super(new BorderLayout(), true);
131 this.helper
= parent
;
134 pane
.setLayout(new BoxLayout(pane
, BoxLayout
.PAGE_AXIS
));
136 Integer icolor
= Instance
.getUiConfig().getColor(
137 UiConfig
.BACKGROUND_COLOR
);
138 if (icolor
!= null) {
139 color
= new Color(icolor
);
140 setBackground(color
);
141 pane
.setBackground(color
);
144 JScrollPane scroll
= new JScrollPane(pane
);
145 scroll
.getVerticalScrollBar().setUnitIncrement(16);
146 add(scroll
, BorderLayout
.CENTER
);
148 String message
= parent
.getReader().getLibrary().getLibraryName();
149 if (!message
.isEmpty()) {
150 JLabel name
= new JLabel(message
, SwingConstants
.CENTER
);
151 add(name
, BorderLayout
.NORTH
);
154 pgBar
= new ProgressBar();
155 add(pgBar
, BorderLayout
.SOUTH
);
157 pgBar
.addActionListener(new ActionListener() {
159 public void actionPerformed(ActionEvent e
) {
161 pgBar
.setProgress(null);
167 pgBar
.addUpdateListener(new ActionListener() {
169 public void actionPerformed(ActionEvent e
) {
176 booksByType
= new HashMap
<GuiReaderGroup
, String
>();
177 booksByAuthor
= new HashMap
<GuiReaderGroup
, String
>();
179 pane
.setVisible(false);
180 final Progress pg
= new Progress();
181 final String typeF
= type
;
182 outOfUi(pg
, new Runnable() {
185 BasicLibrary lib
= helper
.getReader().getLibrary();
186 Status status
= lib
.getStatus();
188 if (status
== Status
.READY
) {
191 helper
.createMenu(true);
193 addBookPane(true, false);
195 addBookPane(typeF
, true);
199 pane
.setVisible(true);
202 helper
.createMenu(false);
205 String err
= lib
.getLibraryName() + "\n";
208 err
+= "Library not valid";
212 err
+= "You are not allowed to access this library";
216 err
+= "Library currently unavailable";
220 err
+= "An error occured when contacting the library";
224 error(err
, "Library error", null);
231 * Add a new {@link GuiReaderGroup} on the frame to display all the
232 * sources/types or all the authors, or a listing of all the books sorted
233 * either by source or author.
235 * A display of all the sources/types or all the authors will show one icon
236 * per source/type or author.
238 * A listing of all the books sorted by source/type or author will display
242 * TRUE for type/source, FALSE for author
244 * TRUE to get a listing of all the sources or authors, FALSE to
245 * get one icon per source or author
247 public void addBookPane(boolean type
, boolean listMode
) {
248 BasicLibrary lib
= helper
.getReader().getLibrary();
251 addListPane("Sources", lib
.getSources(), type
);
253 for (String tt
: lib
.getSources()) {
255 addBookPane(tt
, type
);
261 addListPane("Authors", lib
.getAuthors(), type
);
263 for (String tt
: lib
.getAuthors()) {
265 addBookPane(tt
, type
);
273 * Add a new {@link GuiReaderGroup} on the frame to display the books of the
274 * selected type or author.
277 * the author or the type, or NULL to get all the
280 * TRUE for type/source, FALSE for author
283 public void addBookPane(String value
, boolean type
) {
284 GuiReaderGroup bookPane
= new GuiReaderGroup(helper
.getReader(), value
,
287 booksByType
.put(bookPane
, value
);
289 booksByAuthor
.put(bookPane
, value
);
298 bookPane
.setActionListener(new BookActionListener() {
300 public void select(GuiReaderBook book
) {
305 public void popupRequested(GuiReaderBook book
, MouseEvent e
) {
306 JPopupMenu popup
= helper
.createBookPopup();
307 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
311 public void action(final GuiReaderBook book
) {
318 * Clear the pane from any book that may be present, usually prior to adding
321 public void removeBookPanes() {
323 booksByAuthor
.clear();
332 * Refresh the list of {@link GuiReaderBook}s from disk.
334 public void refreshBooks() {
335 BasicLibrary lib
= helper
.getReader().getLibrary();
336 for (GuiReaderGroup group
: booksByType
.keySet()) {
337 List
<GuiReaderBookInfo
> infos
= new ArrayList
<GuiReaderBookInfo
>();
338 for (MetaData meta
: lib
.getListBySource(booksByType
.get(group
))) {
339 infos
.add(GuiReaderBookInfo
.fromMeta(meta
));
341 group
.refreshBooks(infos
, words
);
344 for (GuiReaderGroup group
: booksByAuthor
.keySet()) {
345 List
<GuiReaderBookInfo
> infos
= new ArrayList
<GuiReaderBookInfo
>();
346 for (MetaData meta
: lib
.getListByAuthor(booksByAuthor
.get(group
))) {
347 infos
.add(GuiReaderBookInfo
.fromMeta(meta
));
349 group
.refreshBooks(infos
, words
);
357 * Open a {@link GuiReaderBook} item.
360 * the {@link GuiReaderBook} to open
362 public void openBook(final GuiReaderBook book
) {
363 final Progress pg
= new Progress();
364 outOfUi(pg
, new Runnable() {
368 helper
.getReader().read(book
.getInfo().getMeta().getLuid(),
370 SwingUtilities
.invokeLater(new Runnable() {
373 book
.setCached(true);
376 } catch (IOException e
) {
377 Instance
.getTraceHandler().error(e
);
378 error("Cannot open the selected book", "Error", e
);
385 * Process the given action out of the Swing UI thread and link the given
386 * {@link ProgressBar} to the action.
388 * The code will make sure that the {@link ProgressBar} (if not NULL) is set
389 * to done when the action is done.
392 * the {@link ProgressBar} or NULL
396 public void outOfUi(Progress progress
, final Runnable run
) {
397 final Progress pg
= new Progress();
398 final Progress reload
= new Progress("Reload books");
399 if (progress
== null) {
400 progress
= new Progress();
403 pg
.addProgress(progress
, 90);
404 pg
.addProgress(reload
, 10);
407 pgBar
.setProgress(pg
);
411 new Thread(new Runnable() {
420 // will trigger pgBar ActionListener:
425 }, "outOfUi thread").start();
429 * Import a {@link Story} into the main {@link LocalLibrary}.
431 * Should be called inside the UI thread.
434 * TRUE for an {@link URL}, false for a {@link File}
436 public void imprt(boolean askUrl
) {
437 JFileChooser fc
= new JFileChooser();
441 String clipboard
= "";
443 clipboard
= ("" + Toolkit
.getDefaultToolkit()
444 .getSystemClipboard().getData(DataFlavor
.stringFlavor
))
446 } catch (Exception e
) {
447 // No data will be handled
450 if (clipboard
== null || !clipboard
.startsWith("http")) {
454 url
= JOptionPane
.showInputDialog(GuiReaderMainPanel
.this,
455 "url of the story to import?", "Importing from URL",
456 JOptionPane
.QUESTION_MESSAGE
, null, null, clipboard
);
457 } else if (fc
.showOpenDialog(this) != JFileChooser
.CANCEL_OPTION
) {
458 url
= fc
.getSelectedFile().getAbsolutePath();
463 if (url
!= null && !url
.toString().isEmpty()) {
464 imprt(url
.toString(), null, null);
469 * Actually import the {@link Story} into the main {@link LocalLibrary}.
471 * Should be called inside the UI thread.
474 * the {@link Story} to import by {@link URL}
476 * Action to execute on success
478 public void imprt(final String url
, final StoryRunnable onSuccess
,
479 String onSuccessPgName
) {
480 final Progress pg
= new Progress();
481 final Progress pgImprt
= new Progress();
482 final Progress pgOnSuccess
= new Progress(onSuccessPgName
);
483 pg
.addProgress(pgImprt
, 95);
484 pg
.addProgress(pgOnSuccess
, 5);
486 outOfUi(pg
, new Runnable() {
492 story
= helper
.getReader().getLibrary()
493 .imprt(BasicReader
.getUrl(url
), pgImprt
);
494 } catch (IOException e
) {
498 final Exception e
= ex
;
500 final boolean ok
= (e
== null);
502 pgOnSuccess
.setProgress(0);
504 if (e
instanceof UnknownHostException
) {
505 error("URL not supported: " + url
, "Cannot import URL",
508 error("Failed to import " + url
+ ": \n"
509 + e
.getMessage(), "Cannot import URL", e
);
512 if (onSuccess
!= null) {
513 onSuccess
.run(story
);
522 * Enables or disables this component, depending on the value of the
523 * parameter <code>b</code>. An enabled component can respond to user input
524 * and generate events. Components are enabled initially by default.
526 * Enabling or disabling <b>this</b> component will also affect its
530 * If <code>true</code>, this component is enabled; otherwise
531 * this component is disabled
534 public void setEnabled(boolean b
) {
539 for (GuiReaderGroup group
: booksByType
.keySet()) {
542 for (GuiReaderGroup group
: booksByAuthor
.keySet()) {
549 public void setWords(boolean words
) {
553 public GuiReaderBook
getSelectedBook() {
557 public void unsetSelectedBook() {
561 private void addListPane(String name
, List
<String
> values
,
562 final boolean type
) {
563 GuiReader reader
= helper
.getReader();
564 BasicLibrary lib
= reader
.getLibrary();
566 GuiReaderGroup bookPane
= new GuiReaderGroup(reader
, name
, color
);
568 List
<GuiReaderBookInfo
> infos
= new ArrayList
<GuiReaderBookInfo
>();
569 for (String value
: values
) {
571 infos
.add(GuiReaderBookInfo
.fromSource(lib
, value
));
573 infos
.add(GuiReaderBookInfo
.fromAuthor(lib
, value
));
577 bookPane
.refreshBooks(infos
, false);
585 bookPane
.setActionListener(new BookActionListener() {
587 public void select(GuiReaderBook book
) {
592 public void popupRequested(GuiReaderBook book
, MouseEvent e
) {
593 JPopupMenu popup
= helper
.createSourcePopup();
594 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
598 public void action(final GuiReaderBook book
) {
600 addBookPane(book
.getInfo().getMainInfo(), type
);
607 * Display an error message and log the linked {@link Exception}.
612 * the title of the error message
614 * the exception to log if any
616 private void error(final String message
, final String title
, Exception e
) {
617 Instance
.getTraceHandler().error(title
+ ": " + message
);
619 Instance
.getTraceHandler().error(e
);
622 SwingUtilities
.invokeLater(new Runnable() {
625 JOptionPane
.showMessageDialog(GuiReaderMainPanel
.this, message
,
626 title
, JOptionPane
.ERROR_MESSAGE
);