gui: fix ChangeSTA, fix [unknown] author
[fanfix.git] / src / be / nikiroo / fanfix / reader / ui / GuiReaderFrame.java
1 package be.nikiroo.fanfix.reader.ui;
2
3 import java.awt.BorderLayout;
4 import java.awt.Frame;
5 import java.awt.event.ActionEvent;
6 import java.awt.event.ActionListener;
7 import java.awt.event.KeyEvent;
8 import java.awt.event.WindowEvent;
9 import java.io.File;
10 import java.io.IOException;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Map.Entry;
15
16 import javax.swing.JFileChooser;
17 import javax.swing.JFrame;
18 import javax.swing.JMenu;
19 import javax.swing.JMenuBar;
20 import javax.swing.JMenuItem;
21 import javax.swing.JOptionPane;
22 import javax.swing.JPopupMenu;
23 import javax.swing.SwingUtilities;
24 import javax.swing.filechooser.FileFilter;
25 import javax.swing.filechooser.FileNameExtensionFilter;
26
27 import be.nikiroo.fanfix.Instance;
28 import be.nikiroo.fanfix.bundles.Config;
29 import be.nikiroo.fanfix.bundles.UiConfig;
30 import be.nikiroo.fanfix.data.MetaData;
31 import be.nikiroo.fanfix.data.Story;
32 import be.nikiroo.fanfix.library.LocalLibrary;
33 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
34 import be.nikiroo.fanfix.reader.BasicReader;
35 import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.FrameHelper;
36 import be.nikiroo.fanfix.reader.ui.GuiReaderMainPanel.StoryRunnable;
37 import be.nikiroo.utils.Progress;
38 import be.nikiroo.utils.Version;
39 import be.nikiroo.utils.ui.ConfigEditor;
40
41 /**
42 * A {@link Frame} that will show a {@link GuiReaderBook} item for each
43 * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
44 * way to copy them to the {@link GuiReader} cache (
45 * {@link BasicReader#getLibrary()}), read them, delete them...
46 *
47 * @author niki
48 */
49 class GuiReaderFrame extends JFrame implements FrameHelper {
50 private static final long serialVersionUID = 1L;
51 private GuiReader reader;
52 private GuiReaderMainPanel mainPanel;
53
54 private enum MoveAction {
55 SOURCE, TITLE, AUTHOR
56 }
57
58 /**
59 * Create a new {@link GuiReaderFrame}.
60 *
61 * @param reader
62 * the associated {@link GuiReader} to forward some commands and
63 * access its {@link LocalLibrary}
64 * @param type
65 * the type of {@link Story} to load, or NULL for all types
66 */
67 public GuiReaderFrame(GuiReader reader, String type) {
68 super(String.format("Fanfix %s Library", Version.getCurrentVersion()));
69
70 this.reader = reader;
71
72 mainPanel = new GuiReaderMainPanel(this, type);
73
74 setSize(800, 600);
75 setLayout(new BorderLayout());
76 add(mainPanel);
77 }
78
79 @Override
80 public JPopupMenu createBookPopup() {
81 JPopupMenu popup = new JPopupMenu();
82 popup.add(createMenuItemOpenBook());
83 popup.addSeparator();
84 popup.add(createMenuItemExport());
85 popup.add(createMenuItemMoveTo(true));
86 popup.add(createMenuItemSetCover());
87 popup.add(createMenuItemClearCache());
88 popup.add(createMenuItemRedownload());
89 popup.addSeparator();
90 popup.add(createMenuItemRename(true));
91 popup.add(createMenuItemSetAuthor(true));
92 popup.addSeparator();
93 popup.add(createMenuItemDelete());
94 popup.addSeparator();
95 popup.add(createMenuItemProperties());
96 return popup;
97 }
98
99 @Override
100 public JPopupMenu createSourcePopup() {
101 JPopupMenu popup = new JPopupMenu();
102 popup.add(createMenuItemOpenBook());
103 return popup;
104 }
105
106 @Override
107 public void createMenu(boolean libOk) {
108 JMenuBar bar = new JMenuBar();
109
110 JMenu file = new JMenu("File");
111 file.setMnemonic(KeyEvent.VK_F);
112
113 JMenuItem imprt = new JMenuItem("Import URL...", KeyEvent.VK_U);
114 imprt.addActionListener(new ActionListener() {
115 @Override
116 public void actionPerformed(ActionEvent e) {
117 mainPanel.imprt(true);
118 }
119 });
120 JMenuItem imprtF = new JMenuItem("Import File...", KeyEvent.VK_F);
121 imprtF.addActionListener(new ActionListener() {
122 @Override
123 public void actionPerformed(ActionEvent e) {
124 mainPanel.imprt(false);
125 }
126 });
127 JMenuItem exit = new JMenuItem("Exit", KeyEvent.VK_X);
128 exit.addActionListener(new ActionListener() {
129 @Override
130 public void actionPerformed(ActionEvent e) {
131 GuiReaderFrame.this.dispatchEvent(new WindowEvent(
132 GuiReaderFrame.this, WindowEvent.WINDOW_CLOSING));
133 }
134 });
135
136 file.add(createMenuItemOpenBook());
137 file.add(createMenuItemExport());
138 file.add(createMenuItemMoveTo(libOk));
139 file.addSeparator();
140 file.add(imprt);
141 file.add(imprtF);
142 file.addSeparator();
143 file.add(createMenuItemRename(libOk));
144 file.add(createMenuItemSetAuthor(libOk));
145 file.addSeparator();
146 file.add(exit);
147
148 bar.add(file);
149
150 JMenu edit = new JMenu("Edit");
151 edit.setMnemonic(KeyEvent.VK_E);
152
153 edit.add(createMenuItemClearCache());
154 edit.add(createMenuItemRedownload());
155 edit.addSeparator();
156 edit.add(createMenuItemDelete());
157
158 bar.add(edit);
159
160 JMenu view = new JMenu("View");
161 view.setMnemonic(KeyEvent.VK_V);
162 JMenuItem vauthors = new JMenuItem("Author");
163 vauthors.setMnemonic(KeyEvent.VK_A);
164 vauthors.addActionListener(new ActionListener() {
165 @Override
166 public void actionPerformed(ActionEvent e) {
167 mainPanel.setWords(false);
168 mainPanel.refreshBooks();
169 }
170 });
171 view.add(vauthors);
172 JMenuItem vwords = new JMenuItem("Word count");
173 vwords.setMnemonic(KeyEvent.VK_W);
174 vwords.addActionListener(new ActionListener() {
175 @Override
176 public void actionPerformed(ActionEvent e) {
177 mainPanel.setWords(true);
178 mainPanel.refreshBooks();
179 }
180 });
181 view.add(vwords);
182 bar.add(view);
183
184 Map<String, List<String>> groupedSources = new HashMap<String, List<String>>();
185 if (libOk) {
186 groupedSources = reader.getLibrary().getSourcesGrouped();
187 }
188 JMenu sources = new JMenu("Sources");
189 sources.setMnemonic(KeyEvent.VK_S);
190 populateMenuSA(sources, groupedSources, true);
191 bar.add(sources);
192
193 Map<String, List<String>> goupedAuthors = new HashMap<String, List<String>>();
194 if (libOk) {
195 goupedAuthors = reader.getLibrary().getAuthorsGrouped();
196 }
197 JMenu authors = new JMenu("Authors");
198 authors.setMnemonic(KeyEvent.VK_A);
199 populateMenuSA(authors, goupedAuthors, false);
200 bar.add(authors);
201
202 JMenu options = new JMenu("Options");
203 options.setMnemonic(KeyEvent.VK_O);
204 options.add(createMenuItemConfig());
205 options.add(createMenuItemUiConfig());
206 bar.add(options);
207
208 setJMenuBar(bar);
209 }
210
211 // "" = [unknown]
212 private void populateMenuSA(JMenu menu,
213 Map<String, List<String>> groupedValues, boolean type) {
214
215 // "All" and "Listing" special items
216 JMenuItem item = new JMenuItem("All");
217 item.addActionListener(getActionOpenList(type, false));
218 menu.add(item);
219 item = new JMenuItem("Listing");
220 item.addActionListener(getActionOpenList(type, true));
221 menu.add(item);
222 menu.addSeparator();
223
224 for (final String value : groupedValues.keySet()) {
225 List<String> list = groupedValues.get(value);
226 if (type && list.size() == 1 && list.get(0).isEmpty()) {
227 // leaf item source/type
228 item = new JMenuItem(value.isEmpty() ? "[unknown]" : value);
229 item.addActionListener(getActionOpen(value, type));
230 menu.add(item);
231 } else {
232 JMenu dir;
233 if (!type && groupedValues.size() == 1) {
234 // only one group of authors
235 dir = menu;
236 } else {
237 dir = new JMenu(value.isEmpty() ? "[unknown]" : value);
238 }
239
240 for (String sub : list) {
241 // " " instead of "" for the visual height
242 String itemName = sub;
243 if (itemName.isEmpty()) {
244 itemName = type ? " " : "[unknown]";
245 }
246
247 String actualValue = value;
248 if (type) {
249 if (!sub.isEmpty()) {
250 actualValue += "/" + sub;
251 }
252 } else {
253 actualValue = sub;
254 }
255
256 item = new JMenuItem(itemName);
257 item.addActionListener(getActionOpen(actualValue, type));
258 dir.add(item);
259 }
260
261 if (menu != dir) {
262 menu.add(dir);
263 }
264 }
265 }
266 }
267
268 /**
269 * Return an {@link ActionListener} that will set the given source (type) as
270 * the selected/displayed one.
271 *
272 * @param type
273 * the type (source) to select, cannot be NULL
274 *
275 * @return the {@link ActionListener}
276 */
277 private ActionListener getActionOpen(final String source, final boolean type) {
278 return new ActionListener() {
279 @Override
280 public void actionPerformed(ActionEvent e) {
281 mainPanel.removeBookPanes();
282 mainPanel.addBookPane(source, type);
283 mainPanel.refreshBooks();
284 }
285 };
286 }
287
288 private ActionListener getActionOpenList(final boolean type,
289 final boolean listMode) {
290 return new ActionListener() {
291 @Override
292 public void actionPerformed(ActionEvent e) {
293 mainPanel.removeBookPanes();
294 mainPanel.addBookPane(type, listMode);
295 mainPanel.refreshBooks();
296 }
297 };
298 }
299
300 /**
301 * Create the Fanfix Configuration menu item.
302 *
303 * @return the item
304 */
305 private JMenuItem createMenuItemConfig() {
306 final String title = "Fanfix Configuration";
307 JMenuItem item = new JMenuItem(title);
308 item.setMnemonic(KeyEvent.VK_F);
309
310 item.addActionListener(new ActionListener() {
311 @Override
312 public void actionPerformed(ActionEvent e) {
313 ConfigEditor<Config> ed = new ConfigEditor<Config>(
314 Config.class, Instance.getConfig(),
315 "This is where you configure the options of the program.");
316 JFrame frame = new JFrame(title);
317 frame.add(ed);
318 frame.setSize(800, 600);
319 frame.setVisible(true);
320 }
321 });
322
323 return item;
324 }
325
326 /**
327 * Create the UI Configuration menu item.
328 *
329 * @return the item
330 */
331 private JMenuItem createMenuItemUiConfig() {
332 final String title = "UI Configuration";
333 JMenuItem item = new JMenuItem(title);
334 item.setMnemonic(KeyEvent.VK_U);
335
336 item.addActionListener(new ActionListener() {
337 @Override
338 public void actionPerformed(ActionEvent e) {
339 ConfigEditor<UiConfig> ed = new ConfigEditor<UiConfig>(
340 UiConfig.class, Instance.getUiConfig(),
341 "This is where you configure the graphical appearence of the program.");
342 JFrame frame = new JFrame(title);
343 frame.add(ed);
344 frame.setSize(800, 600);
345 frame.setVisible(true);
346 }
347 });
348
349 return item;
350 }
351
352 /**
353 * Create the export menu item.
354 *
355 * @return the item
356 */
357 private JMenuItem createMenuItemExport() {
358 final JFileChooser fc = new JFileChooser();
359 fc.setAcceptAllFileFilterUsed(false);
360
361 final Map<FileFilter, OutputType> filters = new HashMap<FileFilter, OutputType>();
362 for (OutputType type : OutputType.values()) {
363 String ext = type.getDefaultExtension(false);
364 String desc = type.getDesc(false);
365
366 if (ext == null || ext.isEmpty()) {
367 filters.put(createAllFilter(desc), type);
368 } else {
369 filters.put(new FileNameExtensionFilter(desc, ext), type);
370 }
371 }
372
373 // First the "ALL" filters, then, the extension filters
374 for (Entry<FileFilter, OutputType> entry : filters.entrySet()) {
375 if (!(entry.getKey() instanceof FileNameExtensionFilter)) {
376 fc.addChoosableFileFilter(entry.getKey());
377 }
378 }
379 for (Entry<FileFilter, OutputType> entry : filters.entrySet()) {
380 if (entry.getKey() instanceof FileNameExtensionFilter) {
381 fc.addChoosableFileFilter(entry.getKey());
382 }
383 }
384 //
385
386 JMenuItem export = new JMenuItem("Save as...", KeyEvent.VK_S);
387 export.addActionListener(new ActionListener() {
388 @Override
389 public void actionPerformed(ActionEvent e) {
390 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
391 if (selectedBook != null) {
392 fc.showDialog(GuiReaderFrame.this, "Save");
393 if (fc.getSelectedFile() != null) {
394 final OutputType type = filters.get(fc.getFileFilter());
395 final String path = fc.getSelectedFile()
396 .getAbsolutePath()
397 + type.getDefaultExtension(false);
398 final Progress pg = new Progress();
399 mainPanel.outOfUi(pg, new Runnable() {
400 @Override
401 public void run() {
402 try {
403 reader.getLibrary().export(
404 selectedBook.getMeta().getLuid(),
405 type, path, pg);
406 } catch (IOException e) {
407 Instance.getTraceHandler().error(e);
408 }
409 }
410 });
411 }
412 }
413 }
414 });
415
416 return export;
417 }
418
419 /**
420 * Create a {@link FileFilter} that accepts all files and return the given
421 * description.
422 *
423 * @param desc
424 * the description
425 *
426 * @return the filter
427 */
428 private FileFilter createAllFilter(final String desc) {
429 return new FileFilter() {
430 @Override
431 public String getDescription() {
432 return desc;
433 }
434
435 @Override
436 public boolean accept(File f) {
437 return true;
438 }
439 };
440 }
441
442 /**
443 * Create the refresh (delete cache) menu item.
444 *
445 * @return the item
446 */
447 private JMenuItem createMenuItemClearCache() {
448 JMenuItem refresh = new JMenuItem("Clear cache", KeyEvent.VK_C);
449 refresh.addActionListener(new ActionListener() {
450 @Override
451 public void actionPerformed(ActionEvent e) {
452 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
453 if (selectedBook != null) {
454 mainPanel.outOfUi(null, new Runnable() {
455 @Override
456 public void run() {
457 reader.clearLocalReaderCache(selectedBook.getMeta()
458 .getLuid());
459 selectedBook.setCached(false);
460 GuiReaderCoverImager.clearIcon(selectedBook
461 .getMeta());
462 SwingUtilities.invokeLater(new Runnable() {
463 @Override
464 public void run() {
465 selectedBook.repaint();
466 }
467 });
468 }
469 });
470 }
471 }
472 });
473
474 return refresh;
475 }
476
477 /**
478 * Create the "move to" menu item.
479 *
480 * @param libOk
481 * the library can be queried
482 *
483 * @return the item
484 */
485 private JMenuItem createMenuItemMoveTo(boolean libOk) {
486 JMenu changeTo = new JMenu("Move to");
487 changeTo.setMnemonic(KeyEvent.VK_M);
488
489 Map<String, List<String>> groupedSources = new HashMap<String, List<String>>();
490 if (libOk) {
491 groupedSources = reader.getLibrary().getSourcesGrouped();
492 }
493
494 JMenuItem item = new JMenuItem("New type...");
495 item.addActionListener(createMoveAction(MoveAction.SOURCE, null));
496 changeTo.add(item);
497 changeTo.addSeparator();
498
499 for (final String type : groupedSources.keySet()) {
500 List<String> list = groupedSources.get(type);
501 if (list.size() == 1 && list.get(0).isEmpty()) {
502 item = new JMenuItem(type);
503 item.addActionListener(createMoveAction(MoveAction.SOURCE, type));
504 changeTo.add(item);
505 } else {
506 JMenu dir = new JMenu(type);
507 for (String sub : list) {
508 // " " instead of "" for the visual height
509 String itemName = sub.isEmpty() ? " " : sub;
510 String actualType = type;
511 if (!sub.isEmpty()) {
512 actualType += "/" + sub;
513 }
514
515 item = new JMenuItem(itemName);
516 item.addActionListener(createMoveAction(MoveAction.SOURCE,
517 actualType));
518 dir.add(item);
519 }
520 changeTo.add(dir);
521 }
522 }
523
524 return changeTo;
525 }
526
527 /**
528 * Create the "set author" menu item.
529 *
530 * @param libOk
531 * the library can be queried
532 *
533 * @return the item
534 */
535 private JMenuItem createMenuItemSetAuthor(boolean libOk) {
536 JMenu changeTo = new JMenu("Set author");
537 changeTo.setMnemonic(KeyEvent.VK_A);
538
539 // New author
540 JMenuItem newItem = new JMenuItem("New author...");
541 changeTo.add(newItem);
542 changeTo.addSeparator();
543 newItem.addActionListener(createMoveAction(MoveAction.AUTHOR, null));
544
545 // Existing authors
546 if (libOk) {
547 Map<String, List<String>> groupedAuthors = reader.getLibrary()
548 .getAuthorsGrouped();
549
550 if (groupedAuthors.size() > 1) {
551 for (String key : groupedAuthors.keySet()) {
552 JMenu group = new JMenu(key);
553 for (String value : groupedAuthors.get(key)) {
554 JMenuItem item = new JMenuItem(
555 value.isEmpty() ? "[unknown]" : value);
556 item.addActionListener(createMoveAction(
557 MoveAction.AUTHOR, value));
558 group.add(item);
559 }
560 changeTo.add(group);
561 }
562 } else if (groupedAuthors.size() == 1) {
563 for (String value : groupedAuthors.values().iterator().next()) {
564 JMenuItem item = new JMenuItem(
565 value.isEmpty() ? "[unknown]" : value);
566 item.addActionListener(createMoveAction(MoveAction.AUTHOR,
567 value));
568 changeTo.add(item);
569 }
570 }
571 }
572
573 return changeTo;
574 }
575
576 /**
577 * Create the "rename" menu item.
578 *
579 * @param libOk
580 * the library can be queried
581 *
582 * @return the item
583 */
584 private JMenuItem createMenuItemRename(
585 @SuppressWarnings("unused") boolean libOk) {
586 JMenuItem changeTo = new JMenuItem("Rename...");
587 changeTo.setMnemonic(KeyEvent.VK_R);
588 changeTo.addActionListener(createMoveAction(MoveAction.TITLE, null));
589 return changeTo;
590 }
591
592 private ActionListener createMoveAction(final MoveAction what,
593 final String type) {
594 return new ActionListener() {
595 @Override
596 public void actionPerformed(ActionEvent e) {
597 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
598 if (selectedBook != null) {
599 String changeTo = type;
600 if (type == null) {
601 String init = "";
602 if (what == MoveAction.SOURCE) {
603 init = selectedBook.getMeta().getSource();
604 } else if (what == MoveAction.TITLE) {
605 init = selectedBook.getMeta().getTitle();
606 } else if (what == MoveAction.AUTHOR) {
607 init = selectedBook.getMeta().getAuthor();
608 }
609
610 Object rep = JOptionPane.showInputDialog(
611 GuiReaderFrame.this, "Move to:",
612 "Moving story", JOptionPane.QUESTION_MESSAGE,
613 null, null, init);
614
615 if (rep == null) {
616 return;
617 }
618
619 changeTo = rep.toString();
620 }
621
622 final String fChangeTo = changeTo;
623 mainPanel.outOfUi(null, new Runnable() {
624 @Override
625 public void run() {
626 if (what == MoveAction.SOURCE) {
627 reader.changeSource(selectedBook.getMeta()
628 .getLuid(), fChangeTo);
629 } else if (what == MoveAction.TITLE) {
630 reader.changeTitle(selectedBook.getMeta()
631 .getLuid(), fChangeTo);
632 } else if (what == MoveAction.AUTHOR) {
633 reader.changeAuthor(selectedBook.getMeta()
634 .getLuid(), fChangeTo);
635 }
636
637 mainPanel.unsetSelectedBook();
638
639 SwingUtilities.invokeLater(new Runnable() {
640 @Override
641 public void run() {
642 createMenu(true);
643 }
644 });
645 }
646 });
647 }
648 }
649 };
650 }
651
652 /**
653 * Create the redownload (then delete original) menu item.
654 *
655 * @return the item
656 */
657 private JMenuItem createMenuItemRedownload() {
658 JMenuItem refresh = new JMenuItem("Redownload", KeyEvent.VK_R);
659 refresh.addActionListener(new ActionListener() {
660 @Override
661 public void actionPerformed(ActionEvent e) {
662 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
663 if (selectedBook != null) {
664 final MetaData meta = selectedBook.getMeta();
665 mainPanel.imprt(meta.getUrl(), new StoryRunnable() {
666 @Override
667 public void run(Story story) {
668 reader.delete(meta.getLuid());
669 mainPanel.unsetSelectedBook();
670 MetaData newMeta = story.getMeta();
671 if (!newMeta.getSource().equals(meta.getSource())) {
672 reader.changeSource(newMeta.getLuid(),
673 meta.getSource());
674 }
675 }
676 }, "Removing old copy");
677 }
678 }
679 });
680
681 return refresh;
682 }
683
684 /**
685 * Create the delete menu item.
686 *
687 * @return the item
688 */
689 private JMenuItem createMenuItemDelete() {
690 JMenuItem delete = new JMenuItem("Delete", KeyEvent.VK_D);
691 delete.addActionListener(new ActionListener() {
692 @Override
693 public void actionPerformed(ActionEvent e) {
694 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
695 if (selectedBook != null) {
696 mainPanel.outOfUi(null, new Runnable() {
697 @Override
698 public void run() {
699 reader.delete(selectedBook.getMeta().getLuid());
700 mainPanel.unsetSelectedBook();
701 }
702 });
703 }
704 }
705 });
706
707 return delete;
708 }
709
710 /**
711 * Create the properties menu item.
712 *
713 * @return the item
714 */
715 private JMenuItem createMenuItemProperties() {
716 JMenuItem delete = new JMenuItem("Properties", KeyEvent.VK_P);
717 delete.addActionListener(new ActionListener() {
718 @Override
719 public void actionPerformed(ActionEvent e) {
720 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
721 if (selectedBook != null) {
722 mainPanel.outOfUi(null, new Runnable() {
723 @Override
724 public void run() {
725 new GuiReaderPropertiesFrame(reader, selectedBook
726 .getMeta()).setVisible(true);
727 }
728 });
729 }
730 }
731 });
732
733 return delete;
734 }
735
736 /**
737 * Create the open menu item for a book or a source/type (no LUID).
738 *
739 * @return the item
740 */
741 public JMenuItem createMenuItemOpenBook() {
742 JMenuItem open = new JMenuItem("Open", KeyEvent.VK_O);
743 open.addActionListener(new ActionListener() {
744 @Override
745 public void actionPerformed(ActionEvent e) {
746 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
747 if (selectedBook != null) {
748 if (selectedBook.getMeta().getLuid() == null) {
749 mainPanel.removeBookPanes();
750 mainPanel.addBookPane(selectedBook.getMeta()
751 .getSource(), true);
752 mainPanel.refreshBooks();
753 } else {
754 mainPanel.openBook(selectedBook);
755 }
756 }
757 }
758 });
759
760 return open;
761 }
762
763 /**
764 * Create the SetCover menu item for a book to change the linked source
765 * cover.
766 *
767 * @return the item
768 */
769 private JMenuItem createMenuItemSetCover() {
770 JMenuItem open = new JMenuItem("Set as cover for source", KeyEvent.VK_C);
771 open.addActionListener(new ActionListener() {
772 @Override
773 public void actionPerformed(ActionEvent e) {
774 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
775 if (selectedBook != null) {
776 reader.getLibrary().setSourceCover(
777 selectedBook.getMeta().getSource(),
778 selectedBook.getMeta().getLuid());
779 MetaData source = selectedBook.getMeta().clone();
780 source.setLuid(null);
781 GuiReaderCoverImager.clearIcon(source);
782 }
783 }
784 });
785
786 return open;
787 }
788
789 /**
790 * Display an error message and log the linked {@link Exception}.
791 *
792 * @param message
793 * the message
794 * @param title
795 * the title of the error message
796 * @param e
797 * the exception to log if any
798 */
799 public void error(final String message, final String title, Exception e) {
800 Instance.getTraceHandler().error(title + ": " + message);
801 if (e != null) {
802 Instance.getTraceHandler().error(e);
803 }
804
805 SwingUtilities.invokeLater(new Runnable() {
806 @Override
807 public void run() {
808 JOptionPane.showMessageDialog(GuiReaderFrame.this, message,
809 title, JOptionPane.ERROR_MESSAGE);
810 }
811 });
812 }
813
814 @Override
815 public GuiReader getReader() {
816 return reader;
817 }
818 }