gui: better refresh for changeSTA
[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, false, 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, false, 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 boolean refreshRequired = false;
615
616 if (what == ChangeAction.SOURCE) {
617 refreshRequired = mainPanel.getCurrentType();
618 } else if (what == ChangeAction.TITLE) {
619 refreshRequired = false;
620 } else if (what == ChangeAction.AUTHOR) {
621 refreshRequired = !mainPanel.getCurrentType();
622 }
623
624 String changeTo = type;
625 if (type == null) {
626 MetaData meta = selectedBook.getInfo().getMeta();
627 String init = "";
628 if (what == ChangeAction.SOURCE) {
629 init = meta.getSource();
630 } else if (what == ChangeAction.TITLE) {
631 init = meta.getTitle();
632 } else if (what == ChangeAction.AUTHOR) {
633 init = meta.getAuthor();
634 }
635
636 Object rep = JOptionPane.showInputDialog(
637 GuiReaderFrame.this, "Move to:",
638 "Moving story", JOptionPane.QUESTION_MESSAGE,
639 null, null, init);
640
641 if (rep == null) {
642 return;
643 }
644
645 changeTo = rep.toString();
646 }
647
648 final String fChangeTo = changeTo;
649 mainPanel.outOfUi(null, refreshRequired, new Runnable() {
650 @Override
651 public void run() {
652 String luid = selectedBook.getInfo().getMeta()
653 .getLuid();
654 if (what == ChangeAction.SOURCE) {
655 reader.changeSource(luid, fChangeTo);
656 } else if (what == ChangeAction.TITLE) {
657 reader.changeTitle(luid, fChangeTo);
658 } else if (what == ChangeAction.AUTHOR) {
659 reader.changeAuthor(luid, fChangeTo);
660 }
661
662 mainPanel.getSelectedBook().repaint();
663 mainPanel.unsetSelectedBook();
664
665 SwingUtilities.invokeLater(new Runnable() {
666 @Override
667 public void run() {
668 createMenu(true);
669 }
670 });
671 }
672 });
673 }
674 }
675 };
676 }
677
678 /**
679 * Create the re-download (then delete original) menu item.
680 *
681 * @return the item
682 */
683 private JMenuItem createMenuItemRedownload() {
684 JMenuItem refresh = new JMenuItem("Redownload", KeyEvent.VK_R);
685 refresh.addActionListener(new ActionListener() {
686 @Override
687 public void actionPerformed(ActionEvent e) {
688 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
689 if (selectedBook != null) {
690 final MetaData meta = selectedBook.getInfo().getMeta();
691 mainPanel.imprt(meta.getUrl(), new StoryRunnable() {
692 @Override
693 public void run(Story story) {
694 reader.delete(meta.getLuid());
695 mainPanel.unsetSelectedBook();
696 MetaData newMeta = story.getMeta();
697 if (!newMeta.getSource().equals(meta.getSource())) {
698 reader.changeSource(newMeta.getLuid(),
699 meta.getSource());
700 }
701 }
702 }, "Removing old copy");
703 }
704 }
705 });
706
707 return refresh;
708 }
709
710 /**
711 * Create the delete menu item.
712 *
713 * @return the item
714 */
715 private JMenuItem createMenuItemDelete() {
716 JMenuItem delete = new JMenuItem("Delete", KeyEvent.VK_D);
717 delete.addActionListener(new ActionListener() {
718 @Override
719 public void actionPerformed(ActionEvent e) {
720 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
721 if (selectedBook != null
722 && selectedBook.getInfo().getMeta() != null) {
723
724 final MetaData meta = selectedBook.getInfo().getMeta();
725 int rep = JOptionPane.showConfirmDialog(
726 GuiReaderFrame.this,
727 String.format("Delete %s: %s", meta.getLuid(),
728 meta.getTitle()), "Delete story",
729 JOptionPane.OK_CANCEL_OPTION);
730
731 if (rep == JOptionPane.OK_OPTION) {
732 mainPanel.outOfUi(null, true, new Runnable() {
733 @Override
734 public void run() {
735 reader.delete(meta.getLuid());
736 mainPanel.unsetSelectedBook();
737 }
738 });
739 }
740 }
741 }
742 });
743
744 return delete;
745 }
746
747 /**
748 * Create the properties menu item.
749 *
750 * @return the item
751 */
752 private JMenuItem createMenuItemProperties() {
753 JMenuItem delete = new JMenuItem("Properties", KeyEvent.VK_P);
754 delete.addActionListener(new ActionListener() {
755 @Override
756 public void actionPerformed(ActionEvent e) {
757 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
758 if (selectedBook != null) {
759 mainPanel.outOfUi(null, false, new Runnable() {
760 @Override
761 public void run() {
762 new GuiReaderPropertiesFrame(reader.getLibrary(),
763 selectedBook.getInfo().getMeta())
764 .setVisible(true);
765 }
766 });
767 }
768 }
769 });
770
771 return delete;
772 }
773
774 /**
775 * Create the open menu item for a book, a source/type or an author.
776 *
777 * @return the item
778 */
779 public JMenuItem createMenuItemOpenBook() {
780 JMenuItem open = new JMenuItem("Open", KeyEvent.VK_O);
781 open.addActionListener(new ActionListener() {
782 @Override
783 public void actionPerformed(ActionEvent e) {
784 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
785 if (selectedBook != null) {
786 if (selectedBook.getInfo().getMeta() == null) {
787 mainPanel.removeBookPanes();
788 mainPanel.addBookPane(selectedBook.getInfo()
789 .getMainInfo(), mainPanel.getCurrentType());
790 mainPanel.refreshBooks();
791 } else {
792 mainPanel.openBook(selectedBook);
793 }
794 }
795 }
796 });
797
798 return open;
799 }
800
801 /**
802 * Create the SetCover menu item for a book to change the linked source
803 * cover.
804 *
805 * @return the item
806 */
807 private JMenuItem createMenuItemSetCoverForSource() {
808 JMenuItem open = new JMenuItem("Set as cover for source", KeyEvent.VK_C);
809 open.addActionListener(new ActionListener() {
810 @Override
811 public void actionPerformed(ActionEvent e) {
812 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
813 if (selectedBook != null) {
814 BasicLibrary lib = reader.getLibrary();
815 String luid = selectedBook.getInfo().getMeta().getLuid();
816 String source = selectedBook.getInfo().getMeta()
817 .getSource();
818
819 lib.setSourceCover(source, luid);
820
821 GuiReaderBookInfo sourceInfo = GuiReaderBookInfo
822 .fromSource(lib, source);
823 GuiReaderCoverImager.clearIcon(sourceInfo);
824 }
825 }
826 });
827
828 return open;
829 }
830
831 /**
832 * Create the SetCover menu item for a book to change the linked source
833 * cover.
834 *
835 * @return the item
836 */
837 private JMenuItem createMenuItemSetCoverForAuthor() {
838 JMenuItem open = new JMenuItem("Set as cover for author", KeyEvent.VK_A);
839 open.addActionListener(new ActionListener() {
840 @Override
841 public void actionPerformed(ActionEvent e) {
842 final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
843 if (selectedBook != null) {
844 BasicLibrary lib = reader.getLibrary();
845 String luid = selectedBook.getInfo().getMeta().getLuid();
846 String author = selectedBook.getInfo().getMeta()
847 .getAuthor();
848
849 lib.setAuthorCover(author, luid);
850
851 GuiReaderBookInfo authorInfo = GuiReaderBookInfo
852 .fromAuthor(lib, author);
853 GuiReaderCoverImager.clearIcon(authorInfo);
854 }
855 }
856 });
857
858 return open;
859 }
860
861 /**
862 * Display an error message and log the linked {@link Exception}.
863 *
864 * @param message
865 * the message
866 * @param title
867 * the title of the error message
868 * @param e
869 * the exception to log if any
870 */
871 public void error(final String message, final String title, Exception e) {
872 Instance.getTraceHandler().error(title + ": " + message);
873 if (e != null) {
874 Instance.getTraceHandler().error(e);
875 }
876
877 SwingUtilities.invokeLater(new Runnable() {
878 @Override
879 public void run() {
880 JOptionPane.showMessageDialog(GuiReaderFrame.this, message,
881 title, JOptionPane.ERROR_MESSAGE);
882 }
883 });
884 }
885
886 @Override
887 public GuiReader getReader() {
888 return reader;
889 }
890 }