reformat
[fanfix.git] / src / be / nikiroo / fanfix_swing / gui / book / BookPopup.java
1 package be.nikiroo.fanfix_swing.gui.book;
2
3 import java.awt.event.ActionEvent;
4 import java.awt.event.ActionListener;
5 import java.awt.event.KeyEvent;
6 import java.io.File;
7 import java.io.IOException;
8 import java.util.HashMap;
9 import java.util.LinkedList;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.Map.Entry;
13
14 import javax.swing.JFileChooser;
15 import javax.swing.JFrame;
16 import javax.swing.JMenu;
17 import javax.swing.JMenuItem;
18 import javax.swing.JOptionPane;
19 import javax.swing.JPopupMenu;
20 import javax.swing.SwingWorker;
21 import javax.swing.filechooser.FileFilter;
22 import javax.swing.filechooser.FileNameExtensionFilter;
23
24 import be.nikiroo.fanfix.Instance;
25 import be.nikiroo.fanfix.bundles.Config;
26 import be.nikiroo.fanfix.bundles.StringIdGui;
27 import be.nikiroo.fanfix.bundles.UiConfig;
28 import be.nikiroo.fanfix.data.MetaData;
29 import be.nikiroo.fanfix.data.Story;
30 import be.nikiroo.fanfix.library.BasicLibrary;
31 import be.nikiroo.fanfix.library.BasicLibrary.Status;
32 import be.nikiroo.fanfix.output.BasicOutput.OutputType;
33 import be.nikiroo.fanfix_swing.Actions;
34 import be.nikiroo.fanfix_swing.gui.utils.UiHelper;
35 import be.nikiroo.utils.Progress;
36 import be.nikiroo.utils.ui.ConfigEditor;
37
38 public class BookPopup extends JPopupMenu {
39 public abstract interface Informer {
40
41 // not null
42 public List<BookInfo> getSelected();
43
44 public void setCached(BookInfo book, boolean cached);
45
46 public BookInfo getUniqueSelected();
47
48 public void fireElementChanged(BookInfo book);
49
50 public void invalidateCache();
51 }
52
53 /**
54 * The different modification actions you can use on {@link Story} items.
55 *
56 * @author niki
57 */
58 private enum ChangeAction {
59 /** Change the source/type, that is, move it to another source. */
60 SOURCE,
61 /** Change its name. */
62 TITLE,
63 /** Change its author. */
64 AUTHOR
65 }
66
67 // be careful with that
68 private BasicLibrary lib;
69
70 private Informer informer;
71
72 public BookPopup(BasicLibrary lib, Informer informer) {
73 this.lib = lib;
74 this.informer = informer;
75
76 Status status = lib.getStatus();
77 add(createMenuItemOpenBook());
78 addSeparator();
79 add(createMenuItemExport());
80 if (status.isWritable()) {
81 add(createMenuItemMoveTo());
82 add(createMenuItemSetCoverForSource());
83 add(createMenuItemSetCoverForAuthor());
84 }
85 add(createMenuItemDownloadToCache());
86 add(createMenuItemClearCache());
87 if (status.isWritable()) {
88 add(createMenuItemRedownload());
89 addSeparator();
90 add(createMenuItemRename());
91 add(createMenuItemSetAuthor());
92 addSeparator();
93 add(createMenuItemDelete());
94 }
95 addSeparator();
96 add(createMenuItemProperties());
97 }
98
99 private String trans(StringIdGui id) {
100 return Instance.getInstance().getTransGui().getString(id);
101 }
102
103 /**
104 * Create the Fanfix Configuration menu item.
105 *
106 * @return the item
107 */
108 private JMenuItem createMenuItemConfig() {
109 final String title = trans(StringIdGui.TITLE_CONFIG);
110 JMenuItem item = new JMenuItem(title);
111 item.setMnemonic(KeyEvent.VK_F);
112
113 item.addActionListener(new ActionListener() {
114 @Override
115 public void actionPerformed(ActionEvent e) {
116 ConfigEditor<Config> ed = new ConfigEditor<Config>(Config.class,
117 Instance.getInstance().getConfig(),
118 trans(StringIdGui.SUBTITLE_CONFIG));
119 JFrame frame = new JFrame(title);
120 frame.add(ed);
121 frame.setSize(850, 600);
122 frame.setVisible(true);
123 }
124 });
125
126 return item;
127 }
128
129 /**
130 * Create the UI Configuration menu item.
131 *
132 * @return the item
133 */
134 private JMenuItem createMenuItemUiConfig() {
135 final String title = trans(StringIdGui.TITLE_CONFIG_UI);
136 JMenuItem item = new JMenuItem(title);
137 item.setMnemonic(KeyEvent.VK_U);
138
139 item.addActionListener(new ActionListener() {
140 @Override
141 public void actionPerformed(ActionEvent e) {
142 ConfigEditor<UiConfig> ed = new ConfigEditor<UiConfig>(
143 UiConfig.class, Instance.getInstance().getUiConfig(),
144 trans(StringIdGui.SUBTITLE_CONFIG_UI));
145 JFrame frame = new JFrame(title);
146 frame.add(ed);
147 frame.setSize(800, 600);
148 frame.setVisible(true);
149 }
150 });
151
152 return item;
153 }
154
155 /**
156 * Create the export menu item.
157 *
158 * @return the item
159 */
160 private JMenuItem createMenuItemExport() {
161
162 // TODO: allow dir for multiple selection?
163
164 final JFileChooser fc = new JFileChooser();
165 fc.setAcceptAllFileFilterUsed(false);
166
167 // Add the "ALL" filters first, then the others
168 final Map<FileFilter, OutputType> otherFilters = new HashMap<FileFilter, OutputType>();
169 for (OutputType type : OutputType.values()) {
170 String ext = type.getDefaultExtension(false);
171 String desc = type.getDesc(false);
172
173 if (ext == null || ext.isEmpty()) {
174 fc.addChoosableFileFilter(createAllFilter(desc));
175 } else {
176 otherFilters.put(new FileNameExtensionFilter(desc, ext), type);
177 }
178 }
179
180 for (Entry<FileFilter, OutputType> entry : otherFilters.entrySet()) {
181 fc.addChoosableFileFilter(entry.getKey());
182 }
183 //
184
185 JMenuItem export = new JMenuItem(trans(StringIdGui.MENU_FILE_EXPORT),
186 KeyEvent.VK_S);
187 export.addActionListener(new ActionListener() {
188 @Override
189 public void actionPerformed(ActionEvent e) {
190 final BookInfo book = informer.getUniqueSelected();
191 if (book != null) {
192 fc.showDialog(BookPopup.this.getParent(),
193 trans(StringIdGui.TITLE_SAVE));
194 if (fc.getSelectedFile() != null) {
195 final OutputType type = otherFilters
196 .get(fc.getFileFilter());
197 final String path = fc.getSelectedFile()
198 .getAbsolutePath()
199 + type.getDefaultExtension(false);
200 final Progress pg = new Progress();
201
202 new SwingWorker<Void, Void>() {
203 @Override
204 protected Void doInBackground() throws Exception {
205 lib.export(book.getMeta().getLuid(), type, path,
206 pg);
207 return null;
208 }
209
210 @Override
211 protected void done() {
212 try {
213 get();
214 } catch (Exception e) {
215 UiHelper.error(BookPopup.this.getParent(),
216 e.getLocalizedMessage(),
217 "IOException", e);
218 }
219 }
220 }.execute();
221 }
222 }
223 }
224 });
225
226 return export;
227 }
228
229 /**
230 * Create a {@link FileFilter} that accepts all files and return the given
231 * description.
232 *
233 * @param desc
234 * the description
235 *
236 * @return the filter
237 */
238 private FileFilter createAllFilter(final String desc) {
239 return new FileFilter() {
240 @Override
241 public String getDescription() {
242 return desc;
243 }
244
245 @Override
246 public boolean accept(File f) {
247 return true;
248 }
249 };
250 }
251
252 /**
253 * Create the refresh (delete cache) menu item.
254 *
255 * @return the item
256 */
257 private JMenuItem createMenuItemClearCache() {
258 JMenuItem refresh = new JMenuItem(
259 trans(StringIdGui.MENU_EDIT_CLEAR_CACHE), KeyEvent.VK_C);
260 refresh.addActionListener(new ActionListener() {
261 @Override
262 public void actionPerformed(ActionEvent e) {
263 final List<BookInfo> selected = informer.getSelected();
264 if (!selected.isEmpty()) {
265 new SwingWorker<Void, Void>() {
266 @Override
267 protected Void doInBackground() throws Exception {
268 for (BookInfo book : selected) {
269 lib.clearFromCache(book.getMeta().getLuid());
270 BookCoverImager.clearIcon(book);
271 }
272 return null;
273 }
274
275 @Override
276 protected void done() {
277 try {
278 get();
279 for (BookInfo book : selected) {
280 informer.setCached(book, false);
281 }
282 } catch (Exception e) {
283 UiHelper.error(BookPopup.this.getParent(),
284 e.getLocalizedMessage(), "IOException",
285 e);
286 }
287 }
288 }.execute();
289 }
290 }
291 });
292
293 return refresh;
294 }
295
296 /**
297 * Create the "move to" menu item.
298 *
299 * @return the item
300 */
301 private JMenuItem createMenuItemMoveTo() {
302 JMenu changeTo = new JMenu(trans(StringIdGui.MENU_FILE_MOVE_TO));
303 changeTo.setMnemonic(KeyEvent.VK_M);
304
305 Map<String, List<String>> groupedSources = new HashMap<String, List<String>>();
306 try {
307 groupedSources = lib.getSourcesGrouped();
308 } catch (IOException e) {
309 UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(),
310 "IOException", e);
311 }
312
313 JMenuItem item = new JMenuItem(
314 trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_TYPE));
315 item.addActionListener(createMoveAction(ChangeAction.SOURCE, null));
316 changeTo.add(item);
317 changeTo.addSeparator();
318
319 for (final String type : groupedSources.keySet()) {
320 List<String> list = groupedSources.get(type);
321 if (list.size() == 1 && list.get(0).isEmpty()) {
322 item = new JMenuItem(type);
323 item.addActionListener(
324 createMoveAction(ChangeAction.SOURCE, type));
325 changeTo.add(item);
326 } else {
327 JMenu dir = new JMenu(type);
328 for (String sub : list) {
329 // " " instead of "" for the visual height
330 String itemName = sub.isEmpty() ? " " : sub;
331 String actualType = type;
332 if (!sub.isEmpty()) {
333 actualType += "/" + sub;
334 }
335
336 item = new JMenuItem(itemName);
337 item.addActionListener(
338 createMoveAction(ChangeAction.SOURCE, actualType));
339 dir.add(item);
340 }
341 changeTo.add(dir);
342 }
343 }
344
345 return changeTo;
346 }
347
348 /**
349 * Create the "set author" menu item.
350 *
351 * @return the item
352 */
353 private JMenuItem createMenuItemSetAuthor() {
354 JMenu changeTo = new JMenu(trans(StringIdGui.MENU_FILE_SET_AUTHOR));
355 changeTo.setMnemonic(KeyEvent.VK_A);
356
357 // New author
358 JMenuItem newItem = new JMenuItem(
359 trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_AUTHOR));
360 changeTo.add(newItem);
361 changeTo.addSeparator();
362 newItem.addActionListener(createMoveAction(ChangeAction.AUTHOR, null));
363
364 // Existing authors
365 Map<String, List<String>> groupedAuthors;
366
367 try {
368 groupedAuthors = lib.getAuthorsGrouped();
369 } catch (IOException e) {
370 UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(),
371 "IOException", e);
372 groupedAuthors = new HashMap<String, List<String>>();
373
374 }
375
376 if (groupedAuthors.size() > 1) {
377 for (String key : groupedAuthors.keySet()) {
378 JMenu group = new JMenu(key);
379 for (String value : groupedAuthors.get(key)) {
380 JMenuItem item = new JMenuItem(value.isEmpty()
381 ? trans(StringIdGui.MENU_AUTHORS_UNKNOWN)
382 : value);
383 item.addActionListener(
384 createMoveAction(ChangeAction.AUTHOR, value));
385 group.add(item);
386 }
387 changeTo.add(group);
388 }
389 } else if (groupedAuthors.size() == 1) {
390 for (String value : groupedAuthors.values().iterator().next()) {
391 JMenuItem item = new JMenuItem(value.isEmpty()
392 ? trans(StringIdGui.MENU_AUTHORS_UNKNOWN)
393 : value);
394 item.addActionListener(
395 createMoveAction(ChangeAction.AUTHOR, value));
396 changeTo.add(item);
397 }
398 }
399
400 return changeTo;
401 }
402
403 /**
404 * Create the "rename" menu item.
405 *
406 * @return the item
407 */
408 private JMenuItem createMenuItemRename() {
409 JMenuItem changeTo = new JMenuItem(trans(StringIdGui.MENU_FILE_RENAME));
410 changeTo.setMnemonic(KeyEvent.VK_R);
411 changeTo.addActionListener(createMoveAction(ChangeAction.TITLE, null));
412 return changeTo;
413 }
414
415 private ActionListener createMoveAction(final ChangeAction what,
416 final String type) {
417 return new ActionListener() {
418 @Override
419 public void actionPerformed(ActionEvent e) {
420 final List<BookInfo> selected = informer.getSelected();
421 if (!selected.isEmpty()) {
422 String changeTo = type;
423 if (type == null) {
424 String init = "";
425
426 if (selected.size() == 1) {
427 MetaData meta = selected.get(0).getMeta();
428 if (what == ChangeAction.SOURCE) {
429 init = meta.getSource();
430 } else if (what == ChangeAction.TITLE) {
431 init = meta.getTitle();
432 } else if (what == ChangeAction.AUTHOR) {
433 init = meta.getAuthor();
434 }
435 }
436
437 Object rep = JOptionPane.showInputDialog(
438 BookPopup.this.getParent(),
439 trans(StringIdGui.SUBTITLE_MOVE_TO),
440 trans(StringIdGui.TITLE_MOVE_TO),
441 JOptionPane.QUESTION_MESSAGE, null, null, init);
442
443 if (rep == null) {
444 return;
445 }
446
447 changeTo = rep.toString();
448 }
449
450 final String fChangeTo = changeTo;
451 new SwingWorker<Void, Void>() {
452 @Override
453 protected Void doInBackground() throws Exception {
454 for (BookInfo book : selected) {
455 String luid = book.getMeta().getLuid();
456 if (what == ChangeAction.SOURCE) {
457 lib.changeSource(luid, fChangeTo, null);
458 } else if (what == ChangeAction.TITLE) {
459 lib.changeTitle(luid, fChangeTo, null);
460 } else if (what == ChangeAction.AUTHOR) {
461 lib.changeAuthor(luid, fChangeTo, null);
462 }
463 }
464
465 return null;
466 }
467
468 @Override
469 protected void done() {
470 try {
471 // this can create new sources/authors, so a
472 // simple fireElementChanged is not
473 // enough, we need to clear the whole cache (for
474 // BrowserPanel for instance)
475 informer.invalidateCache();
476
477 // TODO: also refresh the
478 // Sources/Authors(/Tags?) list
479
480 // Even if problems occurred, still invalidate
481 // the cache
482 get();
483 } catch (Exception e) {
484 UiHelper.error(BookPopup.this.getParent(),
485 e.getLocalizedMessage(), "IOException",
486 e);
487 }
488 }
489 }.execute();
490 }
491 }
492 };
493 }
494
495 /**
496 * Create the re-download (then delete original) menu item.
497 *
498 * @return the item
499 */
500 private JMenuItem createMenuItemRedownload() {
501 JMenuItem refresh = new JMenuItem(
502 trans(StringIdGui.MENU_EDIT_REDOWNLOAD), KeyEvent.VK_R);
503 refresh.addActionListener(new ActionListener() {
504 @Override
505 public void actionPerformed(ActionEvent e) {
506 // final GuiReaderBook selectedBook =
507 // mainPanel.getSelectedBook();
508 // if (selectedBook != null) {
509 // final MetaData meta = selectedBook.getInfo().getMeta();
510 // mainPanel.imprt(meta.getUrl(), new MetaDataRunnable() {
511 // @Override
512 // public void run(MetaData newMeta) {
513 // if (!newMeta.getSource().equals(meta.getSource())) {
514 // reader.changeSource(newMeta.getLuid(), meta.getSource());
515 // }
516 // }
517 // }, trans(StringIdGui.PROGRESS_CHANGE_SOURCE));
518 // }
519 }
520 });
521
522 return refresh;
523 }
524
525 /**
526 * Create the download to cache menu item.
527 *
528 * @return the item
529 */
530 private JMenuItem createMenuItemDownloadToCache() {
531 JMenuItem refresh = new JMenuItem(
532 trans(StringIdGui.MENU_EDIT_DOWNLOAD_TO_CACHE), KeyEvent.VK_T);
533 refresh.addActionListener(new ActionListener() {
534 @Override
535 public void actionPerformed(ActionEvent e) {
536 final List<BookInfo> selected = informer.getSelected();
537
538 new SwingWorker<Void, Void>() {
539 @Override
540 protected Void doInBackground() throws Exception {
541
542 final List<String> luids = new LinkedList<String>();
543 for (BookInfo book : selected) {
544 switch (book.getType()) {
545 case STORY:
546 luids.add(book.getMeta().getLuid());
547 break;
548 case SOURCE:
549 for (MetaData meta : lib.getList().filter(
550 book.getMainInfo(), null, null)) {
551 luids.add(meta.getLuid());
552 }
553 break;
554 case AUTHOR:
555 for (MetaData meta : lib.getList().filter(null,
556 book.getMainInfo(), null)) {
557 luids.add(meta.getLuid());
558 }
559 break;
560 case TAG:
561 for (MetaData meta : lib.getList().filter(null,
562 null, book.getMainInfo())) {
563 luids.add(meta.getLuid());
564 }
565 break;
566 }
567 }
568
569 // TODO: do something with pg?
570 final Progress pg = new Progress();
571 pg.setMax(luids.size());
572 for (String luid : luids) {
573 Progress pgStep = new Progress();
574 pg.addProgress(pgStep, 1);
575
576 lib.getFile(luid, pgStep);
577 }
578
579 return null;
580 }
581
582 @Override
583 protected void done() {
584 try {
585 get();
586 for (BookInfo book : selected) {
587 informer.setCached(book, true);
588 }
589 } catch (Exception e) {
590 UiHelper.error(BookPopup.this.getParent(),
591 e.getLocalizedMessage(), "IOException", e);
592 }
593 }
594 }.execute();
595 }
596 });
597
598 return refresh;
599 }
600
601 /**
602 * Create the delete menu item.
603 *
604 * @return the item
605 */
606 private JMenuItem createMenuItemDelete() {
607 JMenuItem delete = new JMenuItem(trans(StringIdGui.MENU_EDIT_DELETE),
608 KeyEvent.VK_D);
609 delete.addActionListener(new ActionListener() {
610 @Override
611 public void actionPerformed(ActionEvent e) {
612 // final GuiReaderBook selectedBook =
613 // mainPanel.getSelectedBook();
614 // if (selectedBook != null && selectedBook.getInfo().getMeta()
615 // != null) {
616 //
617 // final MetaData meta = selectedBook.getInfo().getMeta();
618 // int rep = JOptionPane.showConfirmDialog(GuiReaderFrame.this,
619 // trans(StringIdGui.SUBTITLE_DELETE, meta.getLuid(),
620 // meta.getTitle()),
621 // trans(StringIdGui.TITLE_DELETE),
622 // JOptionPane.OK_CANCEL_OPTION);
623 //
624 // if (rep == JOptionPane.OK_OPTION) {
625 // mainPanel.outOfUi(null, true, new Runnable() {
626 // @Override
627 // public void run() {
628 // reader.delete(meta.getLuid());
629 // mainPanel.unsetSelectedBook();
630 // }
631 // });
632 // }
633 // }
634 }
635 });
636
637 return delete;
638 }
639
640 /**
641 * Create the properties menu item.
642 *
643 * @return the item
644 */
645 private JMenuItem createMenuItemProperties() {
646 JMenuItem delete = new JMenuItem(
647 trans(StringIdGui.MENU_FILE_PROPERTIES), KeyEvent.VK_P);
648 delete.addActionListener(new ActionListener() {
649 @Override
650 public void actionPerformed(ActionEvent e) {
651 // final GuiReaderBook selectedBook =
652 // mainPanel.getSelectedBook();
653 // if (selectedBook != null) {
654 // mainPanel.outOfUi(null, false, new Runnable() {
655 // @Override
656 // public void run() {
657 // new GuiReaderPropertiesFrame(lib,
658 // selectedBook.getInfo().getMeta())
659 // .setVisible(true);
660 // }
661 // });
662 // }
663 }
664 });
665
666 return delete;
667 }
668
669 /**
670 * Create the open menu item for a book, a source/type or an author.
671 *
672 * @return the item
673 */
674 public JMenuItem createMenuItemOpenBook() {
675 JMenuItem open = new JMenuItem(trans(StringIdGui.MENU_FILE_OPEN),
676 KeyEvent.VK_O);
677 open.addActionListener(new ActionListener() {
678 @Override
679 public void actionPerformed(ActionEvent e) {
680 final BookInfo book = informer.getUniqueSelected();
681 if (book != null) {
682 Actions.openExternal(lib, book.getMeta(),
683 BookPopup.this.getParent(), new Runnable() {
684 @Override
685 public void run() {
686 informer.setCached(book, true);
687 }
688 });
689 }
690 }
691 });
692
693 return open;
694 }
695
696 /**
697 * Create the SetCover menu item for a book to change the linked source
698 * cover.
699 *
700 * @return the item
701 */
702 private JMenuItem createMenuItemSetCoverForSource() {
703 JMenuItem open = new JMenuItem(
704 trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_SOURCE),
705 KeyEvent.VK_C);
706 open.addActionListener(new ActionListener() {
707 @Override
708 public void actionPerformed(ActionEvent ae) {
709 // final GuiReaderBook selectedBook =
710 // mainPanel.getSelectedBook();
711 // if (selectedBook != null) {
712 // BasicLibrary lib = lib;
713 // String luid = selectedBook.getInfo().getMeta().getLuid();
714 // String source = selectedBook.getInfo().getMeta().getSource();
715 //
716 // try {
717 // lib.setSourceCover(source, luid);
718 // } catch (IOException e) {
719 // error(e.getLocalizedMessage(), "IOException", e);
720 // }
721 //
722 // GuiReaderBookInfo sourceInfo =
723 // GuiReaderBookInfo.fromSource(lib, source);
724 // GuiReaderCoverImager.clearIcon(sourceInfo);
725 // }
726 }
727 });
728
729 return open;
730 }
731
732 /**
733 * Create the SetCover menu item for a book to change the linked source
734 * cover.
735 *
736 * @return the item
737 */
738 private JMenuItem createMenuItemSetCoverForAuthor() {
739 JMenuItem open = new JMenuItem(
740 trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_AUTHOR),
741 KeyEvent.VK_A);
742 open.addActionListener(new ActionListener() {
743 @Override
744 public void actionPerformed(ActionEvent ae) {
745 // final GuiReaderBook selectedBook =
746 // mainPanel.getSelectedBook();
747 // if (selectedBook != null) {
748 // String luid = selectedBook.getInfo().getMeta().getLuid();
749 // String author = selectedBook.getInfo().getMeta().getAuthor();
750 //
751 // try {
752 // lib.setAuthorCover(author, luid);
753 // } catch (IOException e) {
754 // error(e.getLocalizedMessage(), "IOException", e);
755 // }
756 //
757 // GuiReaderBookInfo authorInfo =
758 // GuiReaderBookInfo.fromAuthor(lib, author);
759 // GuiReaderCoverImager.clearIcon(authorInfo);
760 // }
761 }
762 });
763
764 return open;
765 }
766 }