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