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