make it subtree
[fanfix.git] / reader / ui / GuiReaderMainPanel.java
CommitLineData
14bb95fa
NR
1package be.nikiroo.fanfix.reader.ui;
2
3import java.awt.BorderLayout;
4import java.awt.Color;
484a31aa 5import java.awt.Component;
32224dda 6import java.awt.EventQueue;
14bb95fa
NR
7import java.awt.Frame;
8import java.awt.Toolkit;
9import java.awt.datatransfer.DataFlavor;
10import java.awt.event.ActionEvent;
11import java.awt.event.ActionListener;
574d709a
NR
12import java.awt.event.FocusAdapter;
13import java.awt.event.FocusEvent;
14bb95fa
NR
14import java.io.File;
15import java.io.IOException;
32224dda 16import java.lang.reflect.InvocationTargetException;
14bb95fa
NR
17import java.net.URL;
18import java.net.UnknownHostException;
19import java.util.ArrayList;
ca7d4e2f 20import java.util.LinkedList;
14bb95fa
NR
21import java.util.List;
22import java.util.Map;
8590da19 23import java.util.TreeMap;
14bb95fa
NR
24
25import javax.swing.BoxLayout;
26import javax.swing.JFileChooser;
27import javax.swing.JLabel;
28import javax.swing.JMenuBar;
29import javax.swing.JOptionPane;
30import javax.swing.JPanel;
31import javax.swing.JPopupMenu;
32import javax.swing.JScrollPane;
33import javax.swing.SwingConstants;
34import javax.swing.SwingUtilities;
35
36import be.nikiroo.fanfix.Instance;
5bc9573b 37import be.nikiroo.fanfix.bundles.StringIdGui;
14bb95fa
NR
38import be.nikiroo.fanfix.bundles.UiConfig;
39import be.nikiroo.fanfix.data.MetaData;
40import be.nikiroo.fanfix.data.Story;
41import be.nikiroo.fanfix.library.BasicLibrary;
42import be.nikiroo.fanfix.library.BasicLibrary.Status;
43import be.nikiroo.fanfix.library.LocalLibrary;
44import be.nikiroo.fanfix.reader.BasicReader;
45import be.nikiroo.fanfix.reader.ui.GuiReaderBook.BookActionListener;
ca7d4e2f 46import be.nikiroo.fanfix.reader.ui.GuiReaderBookInfo.Type;
14bb95fa
NR
47import be.nikiroo.utils.Progress;
48import be.nikiroo.utils.ui.ProgressBar;
49
50/**
51 * A {@link Frame} that will show a {@link GuiReaderBook} item for each
52 * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
53 * way to copy them to the {@link GuiReader} cache (
54 * {@link BasicReader#getLibrary()}), read them, delete them...
55 *
56 * @author niki
57 */
58class GuiReaderMainPanel extends JPanel {
59 private static final long serialVersionUID = 1L;
60 private FrameHelper helper;
8590da19
NR
61 private Map<String, GuiReaderGroup> books;
62 private GuiReaderGroup bookPane; // for more "All"
14bb95fa
NR
63 private JPanel pane;
64 private Color color;
65 private ProgressBar pgBar;
66 private JMenuBar bar;
67 private GuiReaderBook selectedBook;
68 private boolean words; // words or authors (secondary info on books)
8590da19 69 private boolean currentType; // type/source or author mode (All and Listing)
14bb95fa
NR
70
71 /**
72 * An object that offers some helper methods to access the frame that host
73 * it and the Fanfix-related functions.
74 *
75 * @author niki
76 */
77 public interface FrameHelper {
78 /**
79 * Return the reader associated to this {@link FrameHelper}.
80 *
81 * @return the reader
82 */
83 public GuiReader getReader();
84
85 /**
86 * Create the main menu bar.
32224dda 87 * <p>
2d56db73 88 * Will invalidate the layout.
14bb95fa 89 *
0bb51c9c
NR
90 * @param status
91 * the library status, <b>must not</b> be NULL
14bb95fa 92 */
0bb51c9c 93 public void createMenu(Status status);
14bb95fa
NR
94
95 /**
96 * Create a popup menu for a {@link GuiReaderBook} that represents a
97 * story.
98 *
99 * @return the popup menu to display
100 */
101 public JPopupMenu createBookPopup();
102
103 /**
104 * Create a popup menu for a {@link GuiReaderBook} that represents a
8590da19 105 * source/type or an author.
14bb95fa
NR
106 *
107 * @return the popup menu to display
108 */
8590da19 109 public JPopupMenu createSourceAuthorPopup();
14bb95fa
NR
110 }
111
112 /**
b6b65795 113 * A {@link Runnable} with a {@link MetaData} parameter.
14bb95fa
NR
114 *
115 * @author niki
116 */
b6b65795 117 public interface MetaDataRunnable {
14bb95fa
NR
118 /**
119 * Run the action.
120 *
b6b65795
NR
121 * @param meta
122 * the meta of the story
14bb95fa 123 */
b6b65795 124 public void run(MetaData meta);
14bb95fa
NR
125 }
126
127 /**
128 * Create a new {@link GuiReaderMainPanel}.
129 *
2d56db73
NR
130 * @param parent
131 * the associated {@link FrameHelper} to forward some commands
132 * and access its {@link LocalLibrary}
14bb95fa
NR
133 * @param type
134 * the type of {@link Story} to load, or NULL for all types
135 */
136 public GuiReaderMainPanel(FrameHelper parent, String type) {
137 super(new BorderLayout(), true);
138
139 this.helper = parent;
140
141 pane = new JPanel();
142 pane.setLayout(new BoxLayout(pane, BoxLayout.PAGE_AXIS));
a3550a0a 143 JScrollPane scroll = new JScrollPane(pane);
14bb95fa
NR
144
145 Integer icolor = Instance.getUiConfig().getColor(
146 UiConfig.BACKGROUND_COLOR);
147 if (icolor != null) {
148 color = new Color(icolor);
149 setBackground(color);
150 pane.setBackground(color);
a3550a0a 151 scroll.setBackground(color);
14bb95fa
NR
152 }
153
14bb95fa
NR
154 scroll.getVerticalScrollBar().setUnitIncrement(16);
155 add(scroll, BorderLayout.CENTER);
156
157 String message = parent.getReader().getLibrary().getLibraryName();
158 if (!message.isEmpty()) {
159 JLabel name = new JLabel(message, SwingConstants.CENTER);
160 add(name, BorderLayout.NORTH);
161 }
162
163 pgBar = new ProgressBar();
164 add(pgBar, BorderLayout.SOUTH);
165
166 pgBar.addActionListener(new ActionListener() {
167 @Override
168 public void actionPerformed(ActionEvent e) {
32224dda 169 pgBar.invalidate();
14bb95fa 170 pgBar.setProgress(null);
14bb95fa 171 setEnabled(true);
32224dda 172 validate();
14bb95fa
NR
173 }
174 });
175
176 pgBar.addUpdateListener(new ActionListener() {
177 @Override
178 public void actionPerformed(ActionEvent e) {
32224dda 179 pgBar.invalidate();
14bb95fa
NR
180 validate();
181 repaint();
182 }
183 });
184
8590da19 185 books = new TreeMap<String, GuiReaderGroup>();
14bb95fa 186
574d709a
NR
187 addFocusListener(new FocusAdapter() {
188 @Override
189 public void focusGained(FocusEvent e) {
190 focus();
191 }
192 });
193
14bb95fa
NR
194 pane.setVisible(false);
195 final Progress pg = new Progress();
196 final String typeF = type;
2d56db73 197 outOfUi(pg, true, new Runnable() {
14bb95fa
NR
198 @Override
199 public void run() {
32224dda
N
200 final BasicLibrary lib = helper.getReader().getLibrary();
201 final Status status = lib.getStatus();
14bb95fa 202
0bb51c9c 203 if (status == Status.READ_WRITE) {
14bb95fa 204 lib.refresh(pg);
14bb95fa 205 }
32224dda
N
206
207 inUi(new Runnable() {
208 @Override
209 public void run() {
0bb51c9c
NR
210 if (status.isReady()) {
211 helper.createMenu(status);
574d709a 212 pane.setVisible(true);
32224dda 213 if (typeF == null) {
0bb51c9c
NR
214 try {
215 addBookPane(true, false);
216 } catch (IOException e) {
217 error(e.getLocalizedMessage(),
218 "IOException", e);
219 }
32224dda
N
220 } else {
221 addBookPane(typeF, true);
222 }
32224dda 223 } else {
0bb51c9c 224 helper.createMenu(status);
32224dda
N
225 validate();
226
5bc9573b
NR
227 String desc = Instance.getTransGui().getStringX(
228 StringIdGui.ERROR_LIB_STATUS,
229 status.toString());
230 if (desc == null) {
231 desc = GuiReader
232 .trans(StringIdGui.ERROR_LIB_STATUS);
32224dda
N
233 }
234
5bc9573b
NR
235 String err = lib.getLibraryName() + "\n" + desc;
236 error(err, GuiReader
237 .trans(StringIdGui.TITLE_ERROR_LIBRARY),
238 null);
32224dda
N
239 }
240 }
241 });
14bb95fa
NR
242 }
243 });
244 }
245
8590da19
NR
246 public boolean getCurrentType() {
247 return currentType;
248 }
249
14bb95fa
NR
250 /**
251 * Add a new {@link GuiReaderGroup} on the frame to display all the
252 * sources/types or all the authors, or a listing of all the books sorted
253 * either by source or author.
254 * <p>
255 * A display of all the sources/types or all the authors will show one icon
256 * per source/type or author.
257 * <p>
258 * A listing of all the books sorted by source/type or author will display
259 * all the books.
260 *
261 * @param type
262 * TRUE for type/source, FALSE for author
263 * @param listMode
264 * TRUE to get a listing of all the sources or authors, FALSE to
265 * get one icon per source or author
0bb51c9c
NR
266 *
267 * @throws IOException
268 * in case of I/O error
14bb95fa 269 */
0bb51c9c 270 public void addBookPane(boolean type, boolean listMode) throws IOException {
8590da19 271 this.currentType = type;
14bb95fa
NR
272 BasicLibrary lib = helper.getReader().getLibrary();
273 if (type) {
274 if (!listMode) {
5bc9573b
NR
275 addListPane(GuiReader.trans(StringIdGui.MENU_SOURCES),
276 lib.getSources(), type);
14bb95fa
NR
277 } else {
278 for (String tt : lib.getSources()) {
279 if (tt != null) {
280 addBookPane(tt, type);
281 }
282 }
283 }
284 } else {
285 if (!listMode) {
5bc9573b
NR
286 addListPane(GuiReader.trans(StringIdGui.MENU_AUTHORS),
287 lib.getAuthors(), type);
14bb95fa
NR
288 } else {
289 for (String tt : lib.getAuthors()) {
290 if (tt != null) {
291 addBookPane(tt, type);
292 }
293 }
294 }
295 }
296 }
297
298 /**
299 * Add a new {@link GuiReaderGroup} on the frame to display the books of the
300 * selected type or author.
32224dda
N
301 * <p>
302 * Will invalidate the layout.
8590da19 303 *
14bb95fa
NR
304 * @param value
305 * the author or the type, or NULL to get all the
306 * authors-or-types
307 * @param type
308 * TRUE for type/source, FALSE for author
14bb95fa
NR
309 */
310 public void addBookPane(String value, boolean type) {
8590da19
NR
311 this.currentType = type;
312
14bb95fa
NR
313 GuiReaderGroup bookPane = new GuiReaderGroup(helper.getReader(), value,
314 color);
8590da19
NR
315
316 books.put(value, bookPane);
14bb95fa 317
14bb95fa
NR
318 pane.invalidate();
319 pane.add(bookPane);
14bb95fa
NR
320
321 bookPane.setActionListener(new BookActionListener() {
322 @Override
323 public void select(GuiReaderBook book) {
324 selectedBook = book;
325 }
326
327 @Override
484a31aa
NR
328 public void popupRequested(GuiReaderBook book, Component target,
329 int x, int y) {
14bb95fa 330 JPopupMenu popup = helper.createBookPopup();
484a31aa 331 popup.show(target, x, y);
14bb95fa
NR
332 }
333
334 @Override
335 public void action(final GuiReaderBook book) {
336 openBook(book);
337 }
338 });
574d709a
NR
339
340 focus();
14bb95fa
NR
341 }
342
343 /**
344 * Clear the pane from any book that may be present, usually prior to adding
345 * new ones.
32224dda
N
346 * <p>
347 * Will invalidate the layout.
14bb95fa
NR
348 */
349 public void removeBookPanes() {
8590da19 350 books.clear();
14bb95fa 351 pane.invalidate();
14bb95fa 352 pane.removeAll();
14bb95fa
NR
353 }
354
355 /**
356 * Refresh the list of {@link GuiReaderBook}s from disk.
32224dda
N
357 * <p>
358 * Will validate the layout, as it is a "refresh" operation.
14bb95fa
NR
359 */
360 public void refreshBooks() {
361 BasicLibrary lib = helper.getReader().getLibrary();
8590da19 362 for (String value : books.keySet()) {
79a99506 363 List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
8590da19
NR
364
365 List<MetaData> metas;
0bb51c9c
NR
366 try {
367 if (currentType) {
368 metas = lib.getListBySource(value);
369 } else {
370 metas = lib.getListByAuthor(value);
371 }
372 } catch (IOException e) {
373 error(e.getLocalizedMessage(), "IOException", e);
374 metas = new ArrayList<MetaData>();
8590da19 375 }
0bb51c9c 376
8590da19 377 for (MetaData meta : metas) {
79a99506
NR
378 infos.add(GuiReaderBookInfo.fromMeta(meta));
379 }
8590da19
NR
380
381 books.get(value).refreshBooks(infos, words);
14bb95fa
NR
382 }
383
8590da19
NR
384 if (bookPane != null) {
385 bookPane.refreshBooks(words);
14bb95fa
NR
386 }
387
32224dda 388 this.validate();
14bb95fa
NR
389 }
390
391 /**
392 * Open a {@link GuiReaderBook} item.
393 *
394 * @param book
395 * the {@link GuiReaderBook} to open
396 */
397 public void openBook(final GuiReaderBook book) {
398 final Progress pg = new Progress();
2d56db73 399 outOfUi(pg, false, new Runnable() {
14bb95fa
NR
400 @Override
401 public void run() {
402 try {
79a99506
NR
403 helper.getReader().read(book.getInfo().getMeta().getLuid(),
404 false, pg);
14bb95fa
NR
405 SwingUtilities.invokeLater(new Runnable() {
406 @Override
407 public void run() {
408 book.setCached(true);
409 }
410 });
411 } catch (IOException e) {
412 Instance.getTraceHandler().error(e);
5bc9573b
NR
413 error(GuiReader.trans(StringIdGui.ERROR_CANNOT_OPEN),
414 GuiReader.trans(StringIdGui.TITLE_ERROR), e);
14bb95fa
NR
415 }
416 }
417 });
418 }
ca7d4e2f
NR
419
420 /**
421 * Prefetch a {@link GuiReaderBook} item (which can be a group, in which
422 * case we prefetch all its members).
423 *
424 * @param book
425 * the {@link GuiReaderBook} to open
426 */
427 public void prefetchBook(final GuiReaderBook book) {
428 final List<String> luids = new LinkedList<String>();
429 try {
430 switch (book.getInfo().getType()) {
431 case STORY:
432 luids.add(book.getInfo().getMeta().getLuid());
433 break;
434 case SOURCE:
435 for (MetaData meta : helper.getReader().getLibrary()
436 .getListBySource(book.getInfo().getMainInfo())) {
437 luids.add(meta.getLuid());
438 }
439 break;
440 case AUTHOR:
441 for (MetaData meta : helper.getReader().getLibrary()
442 .getListByAuthor(book.getInfo().getMainInfo())) {
443 luids.add(meta.getLuid());
444 }
445 break;
446 }
447 } catch (IOException e) {
448 Instance.getTraceHandler().error(e);
449 }
450
451 final Progress pg = new Progress();
452 pg.setMax(luids.size());
453
454 outOfUi(pg, false, new Runnable() {
455 @Override
456 public void run() {
457 try {
458 for (String luid : luids) {
459 Progress pgStep = new Progress();
460 pg.addProgress(pgStep, 1);
461
462 helper.getReader().prefetch(luid, pgStep);
463 }
464
465 // TODO: also set the green button on sources/authors?
466 // requires to do the same when all stories inside are green
467 if (book.getInfo().getType() == Type.STORY) {
468 SwingUtilities.invokeLater(new Runnable() {
469 @Override
470 public void run() {
471 book.setCached(true);
472 }
473 });
474 }
475 } catch (IOException e) {
476 Instance.getTraceHandler().error(e);
477 error(GuiReader.trans(StringIdGui.ERROR_CANNOT_OPEN),
478 GuiReader.trans(StringIdGui.TITLE_ERROR), e);
479 }
480 }
481 });
482 }
14bb95fa
NR
483
484 /**
485 * Process the given action out of the Swing UI thread and link the given
486 * {@link ProgressBar} to the action.
487 * <p>
488 * The code will make sure that the {@link ProgressBar} (if not NULL) is set
489 * to done when the action is done.
490 *
491 * @param progress
492 * the {@link ProgressBar} or NULL
2d56db73
NR
493 * @param refreshBooks
494 * TRUE to refresh the books after
14bb95fa
NR
495 * @param run
496 * the action to run
497 */
2d56db73
NR
498 public void outOfUi(Progress progress, final boolean refreshBooks,
499 final Runnable run) {
14bb95fa 500 final Progress pg = new Progress();
5bc9573b
NR
501 final Progress reload = new Progress(
502 GuiReader.trans(StringIdGui.PROGRESS_OUT_OF_UI_RELOAD_BOOKS));
2d56db73 503
14bb95fa
NR
504 if (progress == null) {
505 progress = new Progress();
506 }
507
2d56db73
NR
508 if (refreshBooks) {
509 pg.addProgress(progress, 100);
510 } else {
511 pg.addProgress(progress, 90);
512 pg.addProgress(reload, 10);
513 }
14bb95fa
NR
514
515 invalidate();
516 pgBar.setProgress(pg);
517 validate();
518 setEnabled(false);
519
520 new Thread(new Runnable() {
521 @Override
522 public void run() {
523 try {
524 run.run();
2d56db73
NR
525 if (refreshBooks) {
526 refreshBooks();
527 }
14bb95fa
NR
528 } finally {
529 reload.done();
530 if (!pg.isDone()) {
531 // will trigger pgBar ActionListener:
532 pg.done();
533 }
534 }
535 }
536 }, "outOfUi thread").start();
537 }
538
32224dda
N
539 /**
540 * Process the given action in the main Swing UI thread.
541 * <p>
542 * The code will make sure the current thread is the main UI thread and, if
543 * not, will switch to it before executing the runnable.
544 * <p>
545 * Synchronous operation.
546 *
547 * @param run
548 * the action to run
549 */
550 public void inUi(final Runnable run) {
551 if (EventQueue.isDispatchThread()) {
552 run.run();
553 } else {
554 try {
555 EventQueue.invokeAndWait(run);
556 } catch (InterruptedException e) {
557 Instance.getTraceHandler().error(e);
558 } catch (InvocationTargetException e) {
559 Instance.getTraceHandler().error(e);
560 }
561 }
562 }
563
14bb95fa
NR
564 /**
565 * Import a {@link Story} into the main {@link LocalLibrary}.
566 * <p>
567 * Should be called inside the UI thread.
568 *
569 * @param askUrl
570 * TRUE for an {@link URL}, false for a {@link File}
571 */
572 public void imprt(boolean askUrl) {
573 JFileChooser fc = new JFileChooser();
574
575 Object url;
576 if (askUrl) {
577 String clipboard = "";
578 try {
579 clipboard = ("" + Toolkit.getDefaultToolkit()
580 .getSystemClipboard().getData(DataFlavor.stringFlavor))
581 .trim();
582 } catch (Exception e) {
583 // No data will be handled
584 }
585
4606c850
NR
586 if (clipboard == null || !(clipboard.startsWith("http://") || //
587 clipboard.startsWith("https://"))) {
14bb95fa
NR
588 clipboard = "";
589 }
590
591 url = JOptionPane.showInputDialog(GuiReaderMainPanel.this,
5bc9573b
NR
592 GuiReader.trans(StringIdGui.SUBTITLE_IMPORT_URL),
593 GuiReader.trans(StringIdGui.TITLE_IMPORT_URL),
14bb95fa
NR
594 JOptionPane.QUESTION_MESSAGE, null, null, clipboard);
595 } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) {
596 url = fc.getSelectedFile().getAbsolutePath();
597 } else {
598 url = null;
599 }
600
601 if (url != null && !url.toString().isEmpty()) {
602 imprt(url.toString(), null, null);
603 }
604 }
605
606 /**
607 * Actually import the {@link Story} into the main {@link LocalLibrary}.
608 * <p>
609 * Should be called inside the UI thread.
610 *
611 * @param url
612 * the {@link Story} to import by {@link URL}
613 * @param onSuccess
614 * Action to execute on success
2d56db73
NR
615 * @param onSuccessPgName
616 * the name to use for the onSuccess progress bar
14bb95fa 617 */
b6b65795 618 public void imprt(final String url, final MetaDataRunnable onSuccess,
14bb95fa
NR
619 String onSuccessPgName) {
620 final Progress pg = new Progress();
621 final Progress pgImprt = new Progress();
622 final Progress pgOnSuccess = new Progress(onSuccessPgName);
623 pg.addProgress(pgImprt, 95);
624 pg.addProgress(pgOnSuccess, 5);
625
2d56db73 626 outOfUi(pg, true, new Runnable() {
14bb95fa
NR
627 @Override
628 public void run() {
629 Exception ex = null;
b6b65795 630 MetaData meta = null;
14bb95fa 631 try {
b6b65795 632 meta = helper.getReader().getLibrary()
14bb95fa
NR
633 .imprt(BasicReader.getUrl(url), pgImprt);
634 } catch (IOException e) {
635 ex = e;
636 }
637
638 final Exception e = ex;
639
640 final boolean ok = (e == null);
641
642 pgOnSuccess.setProgress(0);
643 if (!ok) {
644 if (e instanceof UnknownHostException) {
5bc9573b
NR
645 error(GuiReader.trans(
646 StringIdGui.ERROR_URL_NOT_SUPPORTED, url),
647 GuiReader.trans(StringIdGui.TITLE_ERROR), null);
14bb95fa 648 } else {
5bc9573b
NR
649 error(GuiReader.trans(
650 StringIdGui.ERROR_URL_IMPORT_FAILED, url,
651 e.getMessage()), GuiReader
652 .trans(StringIdGui.TITLE_ERROR), e);
14bb95fa
NR
653 }
654 } else {
655 if (onSuccess != null) {
b6b65795 656 onSuccess.run(meta);
14bb95fa
NR
657 }
658 }
659 pgOnSuccess.done();
660 }
661 });
662 }
663
664 /**
665 * Enables or disables this component, depending on the value of the
666 * parameter <code>b</code>. An enabled component can respond to user input
667 * and generate events. Components are enabled initially by default.
668 * <p>
669 * Enabling or disabling <b>this</b> component will also affect its
670 * children.
671 *
672 * @param b
673 * If <code>true</code>, this component is enabled; otherwise
674 * this component is disabled
675 */
676 @Override
677 public void setEnabled(boolean b) {
678 if (bar != null) {
679 bar.setEnabled(b);
680 }
681
8590da19 682 for (GuiReaderGroup group : books.values()) {
14bb95fa
NR
683 group.setEnabled(b);
684 }
685 super.setEnabled(b);
686 repaint();
687 }
688
689 public void setWords(boolean words) {
690 this.words = words;
691 }
692
693 public GuiReaderBook getSelectedBook() {
694 return selectedBook;
695 }
696
697 public void unsetSelectedBook() {
698 selectedBook = null;
699 }
700
701 private void addListPane(String name, List<String> values,
702 final boolean type) {
79a99506
NR
703 GuiReader reader = helper.getReader();
704 BasicLibrary lib = reader.getLibrary();
14bb95fa 705
8590da19 706 bookPane = new GuiReaderGroup(reader, name, color);
79a99506
NR
707
708 List<GuiReaderBookInfo> infos = new ArrayList<GuiReaderBookInfo>();
709 for (String value : values) {
710 if (type) {
711 infos.add(GuiReaderBookInfo.fromSource(lib, value));
712 } else {
713 infos.add(GuiReaderBookInfo.fromAuthor(lib, value));
714 }
14bb95fa
NR
715 }
716
fb1ffdd0 717 bookPane.refreshBooks(infos, words);
14bb95fa
NR
718
719 this.invalidate();
720 pane.invalidate();
721 pane.add(bookPane);
722 pane.validate();
723 this.validate();
724
725 bookPane.setActionListener(new BookActionListener() {
726 @Override
727 public void select(GuiReaderBook book) {
728 selectedBook = book;
729 }
730
731 @Override
484a31aa
NR
732 public void popupRequested(GuiReaderBook book, Component target,
733 int x, int y) {
8590da19 734 JPopupMenu popup = helper.createSourceAuthorPopup();
484a31aa 735 popup.show(target, x, y);
14bb95fa
NR
736 }
737
738 @Override
739 public void action(final GuiReaderBook book) {
740 removeBookPanes();
79a99506 741 addBookPane(book.getInfo().getMainInfo(), type);
14bb95fa
NR
742 refreshBooks();
743 }
744 });
574d709a
NR
745
746 focus();
747 }
748
749 /**
750 * Focus the first {@link GuiReaderGroup} we find.
751 */
752 private void focus() {
753 GuiReaderGroup group = null;
754 Map<String, GuiReaderGroup> books = this.books;
755 if (books.size() > 0) {
756 group = books.values().iterator().next();
757 }
758
759 if (group == null) {
760 group = bookPane;
761 }
762
763 if (group != null) {
764 group.requestFocusInWindow();
765 }
14bb95fa
NR
766 }
767
768 /**
769 * Display an error message and log the linked {@link Exception}.
770 *
771 * @param message
772 * the message
773 * @param title
774 * the title of the error message
775 * @param e
776 * the exception to log if any
777 */
778 private void error(final String message, final String title, Exception e) {
779 Instance.getTraceHandler().error(title + ": " + message);
780 if (e != null) {
781 Instance.getTraceHandler().error(e);
782 }
783
784 SwingUtilities.invokeLater(new Runnable() {
785 @Override
786 public void run() {
787 JOptionPane.showMessageDialog(GuiReaderMainPanel.this, message,
788 title, JOptionPane.ERROR_MESSAGE);
789 }
790 });
791 }
792}