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