use filter() instead of list()
[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, Instance.getInstance().getConfig(),
117 trans(StringIdGui.SUBTITLE_CONFIG));
118 JFrame frame = new JFrame(title);
119 frame.add(ed);
120 frame.setSize(850, 600);
121 frame.setVisible(true);
122 }
123 });
124
125 return item;
126 }
127
128 /**
129 * Create the UI Configuration menu item.
130 *
131 * @return the item
132 */
133 private JMenuItem createMenuItemUiConfig() {
134 final String title = trans(StringIdGui.TITLE_CONFIG_UI);
135 JMenuItem item = new JMenuItem(title);
136 item.setMnemonic(KeyEvent.VK_U);
137
138 item.addActionListener(new ActionListener() {
139 @Override
140 public void actionPerformed(ActionEvent e) {
141 ConfigEditor<UiConfig> ed = new ConfigEditor<UiConfig>(UiConfig.class,
142 Instance.getInstance().getUiConfig(), trans(StringIdGui.SUBTITLE_CONFIG_UI));
143 JFrame frame = new JFrame(title);
144 frame.add(ed);
145 frame.setSize(800, 600);
146 frame.setVisible(true);
147 }
148 });
149
150 return item;
151 }
152
153 /**
154 * Create the export menu item.
155 *
156 * @return the item
157 */
158 private JMenuItem createMenuItemExport() {
159
160 // TODO: allow dir for multiple selection?
161
162 final JFileChooser fc = new JFileChooser();
163 fc.setAcceptAllFileFilterUsed(false);
164
165 // Add the "ALL" filters first, then the others
166 final Map<FileFilter, OutputType> otherFilters = new HashMap<FileFilter, OutputType>();
167 for (OutputType type : OutputType.values()) {
168 String ext = type.getDefaultExtension(false);
169 String desc = type.getDesc(false);
170
171 if (ext == null || ext.isEmpty()) {
172 fc.addChoosableFileFilter(createAllFilter(desc));
173 } else {
174 otherFilters.put(new FileNameExtensionFilter(desc, ext), type);
175 }
176 }
177
178 for (Entry<FileFilter, OutputType> entry : otherFilters.entrySet()) {
179 fc.addChoosableFileFilter(entry.getKey());
180 }
181 //
182
183 JMenuItem export = new JMenuItem(trans(StringIdGui.MENU_FILE_EXPORT), KeyEvent.VK_S);
184 export.addActionListener(new ActionListener() {
185 @Override
186 public void actionPerformed(ActionEvent e) {
187 final BookInfo book = informer.getUniqueSelected();
188 if (book != null) {
189 fc.showDialog(BookPopup.this.getParent(), trans(StringIdGui.TITLE_SAVE));
190 if (fc.getSelectedFile() != null) {
191 final OutputType type = otherFilters.get(fc.getFileFilter());
192 final String path = fc.getSelectedFile().getAbsolutePath() + type.getDefaultExtension(false);
193 final Progress pg = new Progress();
194
195 new SwingWorker<Void, Void>() {
196 @Override
197 protected Void doInBackground() throws Exception {
198 lib.export(book.getMeta().getLuid(), type, path, pg);
199 return null;
200 }
201
202 @Override
203 protected void done() {
204 try {
205 get();
206 } catch (Exception e) {
207 UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(), "IOException",
208 e);
209 }
210 }
211 }.execute();
212 }
213 }
214 }
215 });
216
217 return export;
218 }
219
220 /**
221 * Create a {@link FileFilter} that accepts all files and return the given
222 * description.
223 *
224 * @param desc the description
225 *
226 * @return the filter
227 */
228 private FileFilter createAllFilter(final String desc) {
229 return new FileFilter() {
230 @Override
231 public String getDescription() {
232 return desc;
233 }
234
235 @Override
236 public boolean accept(File f) {
237 return true;
238 }
239 };
240 }
241
242 /**
243 * Create the refresh (delete cache) menu item.
244 *
245 * @return the item
246 */
247 private JMenuItem createMenuItemClearCache() {
248 JMenuItem refresh = new JMenuItem(trans(StringIdGui.MENU_EDIT_CLEAR_CACHE), KeyEvent.VK_C);
249 refresh.addActionListener(new ActionListener() {
250 @Override
251 public void actionPerformed(ActionEvent e) {
252 final List<BookInfo> selected = informer.getSelected();
253 if (!selected.isEmpty()) {
254 new SwingWorker<Void, Void>() {
255 @Override
256 protected Void doInBackground() throws Exception {
257 for (BookInfo book : selected) {
258 lib.clearFromCache(book.getMeta().getLuid());
259 BookCoverImager.clearIcon(book);
260 }
261 return null;
262 }
263
264 @Override
265 protected void done() {
266 try {
267 get();
268 for (BookInfo book : selected) {
269 informer.setCached(book, false);
270 }
271 } catch (Exception e) {
272 UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(), "IOException", e);
273 }
274 }
275 }.execute();
276 }
277 }
278 });
279
280 return refresh;
281 }
282
283 /**
284 * Create the "move to" menu item.
285 *
286 * @return the item
287 */
288 private JMenuItem createMenuItemMoveTo() {
289 JMenu changeTo = new JMenu(trans(StringIdGui.MENU_FILE_MOVE_TO));
290 changeTo.setMnemonic(KeyEvent.VK_M);
291
292 Map<String, List<String>> groupedSources = new HashMap<String, List<String>>();
293 try {
294 groupedSources = lib.getSourcesGrouped();
295 } catch (IOException e) {
296 UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(), "IOException", e);
297 }
298
299 JMenuItem item = new JMenuItem(trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_TYPE));
300 item.addActionListener(createMoveAction(ChangeAction.SOURCE, null));
301 changeTo.add(item);
302 changeTo.addSeparator();
303
304 for (final String type : groupedSources.keySet()) {
305 List<String> list = groupedSources.get(type);
306 if (list.size() == 1 && list.get(0).isEmpty()) {
307 item = new JMenuItem(type);
308 item.addActionListener(createMoveAction(ChangeAction.SOURCE, type));
309 changeTo.add(item);
310 } else {
311 JMenu dir = new JMenu(type);
312 for (String sub : list) {
313 // " " instead of "" for the visual height
314 String itemName = sub.isEmpty() ? " " : sub;
315 String actualType = type;
316 if (!sub.isEmpty()) {
317 actualType += "/" + sub;
318 }
319
320 item = new JMenuItem(itemName);
321 item.addActionListener(createMoveAction(ChangeAction.SOURCE, actualType));
322 dir.add(item);
323 }
324 changeTo.add(dir);
325 }
326 }
327
328 return changeTo;
329 }
330
331 /**
332 * Create the "set author" menu item.
333 *
334 * @return the item
335 */
336 private JMenuItem createMenuItemSetAuthor() {
337 JMenu changeTo = new JMenu(trans(StringIdGui.MENU_FILE_SET_AUTHOR));
338 changeTo.setMnemonic(KeyEvent.VK_A);
339
340 // New author
341 JMenuItem newItem = new JMenuItem(trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_AUTHOR));
342 changeTo.add(newItem);
343 changeTo.addSeparator();
344 newItem.addActionListener(createMoveAction(ChangeAction.AUTHOR, null));
345
346 // Existing authors
347 Map<String, List<String>> groupedAuthors;
348
349 try {
350 groupedAuthors = lib.getAuthorsGrouped();
351 } catch (IOException e) {
352 UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(), "IOException", e);
353 groupedAuthors = new HashMap<String, List<String>>();
354
355 }
356
357 if (groupedAuthors.size() > 1) {
358 for (String key : groupedAuthors.keySet()) {
359 JMenu group = new JMenu(key);
360 for (String value : groupedAuthors.get(key)) {
361 JMenuItem item = new JMenuItem(value.isEmpty() ? trans(StringIdGui.MENU_AUTHORS_UNKNOWN) : value);
362 item.addActionListener(createMoveAction(ChangeAction.AUTHOR, value));
363 group.add(item);
364 }
365 changeTo.add(group);
366 }
367 } else if (groupedAuthors.size() == 1) {
368 for (String value : groupedAuthors.values().iterator().next()) {
369 JMenuItem item = new JMenuItem(value.isEmpty() ? trans(StringIdGui.MENU_AUTHORS_UNKNOWN) : value);
370 item.addActionListener(createMoveAction(ChangeAction.AUTHOR, value));
371 changeTo.add(item);
372 }
373 }
374
375 return changeTo;
376 }
377
378 /**
379 * Create the "rename" menu item.
380 *
381 * @return the item
382 */
383 private JMenuItem createMenuItemRename() {
384 JMenuItem changeTo = new JMenuItem(trans(StringIdGui.MENU_FILE_RENAME));
385 changeTo.setMnemonic(KeyEvent.VK_R);
386 changeTo.addActionListener(createMoveAction(ChangeAction.TITLE, null));
387 return changeTo;
388 }
389
390 private ActionListener createMoveAction(final ChangeAction what, final String type) {
391 return new ActionListener() {
392 @Override
393 public void actionPerformed(ActionEvent e) {
394 final List<BookInfo> selected = informer.getSelected();
395 if (!selected.isEmpty()) {
396 String changeTo = type;
397 if (type == null) {
398 String init = "";
399
400 if (selected.size() == 1) {
401 MetaData meta = selected.get(0).getMeta();
402 if (what == ChangeAction.SOURCE) {
403 init = meta.getSource();
404 } else if (what == ChangeAction.TITLE) {
405 init = meta.getTitle();
406 } else if (what == ChangeAction.AUTHOR) {
407 init = meta.getAuthor();
408 }
409 }
410
411 Object rep = JOptionPane.showInputDialog(BookPopup.this.getParent(),
412 trans(StringIdGui.SUBTITLE_MOVE_TO), trans(StringIdGui.TITLE_MOVE_TO),
413 JOptionPane.QUESTION_MESSAGE, null, null, init);
414
415 if (rep == null) {
416 return;
417 }
418
419 changeTo = rep.toString();
420 }
421
422 final String fChangeTo = changeTo;
423 new SwingWorker<Void, Void>() {
424 @Override
425 protected Void doInBackground() throws Exception {
426 for (BookInfo book : selected) {
427 String luid = book.getMeta().getLuid();
428 if (what == ChangeAction.SOURCE) {
429 lib.changeSource(luid, fChangeTo, null);
430 } else if (what == ChangeAction.TITLE) {
431 lib.changeTitle(luid, fChangeTo, null);
432 } else if (what == ChangeAction.AUTHOR) {
433 lib.changeAuthor(luid, fChangeTo, null);
434 }
435 }
436
437 return null;
438 }
439
440 @Override
441 protected void done() {
442 try {
443 // this can create new sources/authors, so a simple fireElementChanged is not
444 // enough, we need to clear the whole cache (for BrowserPanel for instance)
445 informer.invalidateCache();
446
447 // TODO: also refresh the Sources/Authors(/Tags?) list
448
449 // Even if problems occurred, still invalidate the cache
450 get();
451 } catch (Exception e) {
452 UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(), "IOException", e);
453 }
454 }
455 }.execute();
456 }
457 }
458 };
459 }
460
461 /**
462 * Create the re-download (then delete original) menu item.
463 *
464 * @return the item
465 */
466 private JMenuItem createMenuItemRedownload() {
467 JMenuItem refresh = new JMenuItem(trans(StringIdGui.MENU_EDIT_REDOWNLOAD), KeyEvent.VK_R);
468 refresh.addActionListener(new ActionListener() {
469 @Override
470 public void actionPerformed(ActionEvent e) {
471 // final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
472 // if (selectedBook != null) {
473 // final MetaData meta = selectedBook.getInfo().getMeta();
474 // mainPanel.imprt(meta.getUrl(), new MetaDataRunnable() {
475 // @Override
476 // public void run(MetaData newMeta) {
477 // if (!newMeta.getSource().equals(meta.getSource())) {
478 // reader.changeSource(newMeta.getLuid(), meta.getSource());
479 // }
480 // }
481 // }, trans(StringIdGui.PROGRESS_CHANGE_SOURCE));
482 // }
483 }
484 });
485
486 return refresh;
487 }
488
489 /**
490 * Create the download to cache menu item.
491 *
492 * @return the item
493 */
494 private JMenuItem createMenuItemDownloadToCache() {
495 JMenuItem refresh = new JMenuItem(trans(StringIdGui.MENU_EDIT_DOWNLOAD_TO_CACHE), KeyEvent.VK_T);
496 refresh.addActionListener(new ActionListener() {
497 @Override
498 public void actionPerformed(ActionEvent e) {
499 final List<BookInfo> selected = informer.getSelected();
500
501 new SwingWorker<Void, Void>() {
502 @Override
503 protected Void doInBackground() throws Exception {
504
505 final List<String> luids = new LinkedList<String>();
506 for (BookInfo book : selected) {
507 switch (book.getType()) {
508 case STORY:
509 luids.add(book.getMeta().getLuid());
510 break;
511 case SOURCE:
512 for (MetaData meta : lib.getList().filter(book.getMainInfo(), null, null)) {
513 luids.add(meta.getLuid());
514 }
515 break;
516 case AUTHOR:
517 for (MetaData meta : lib.getList().filter(null, book.getMainInfo(), null)) {
518 luids.add(meta.getLuid());
519 }
520 break;
521 case TAG:
522 for (MetaData meta : lib.getList().filter(null, null, book.getMainInfo())) {
523 luids.add(meta.getLuid());
524 }
525 break;
526 }
527 }
528
529 // TODO: do something with pg?
530 final Progress pg = new Progress();
531 pg.setMax(luids.size());
532 for (String luid : luids) {
533 Progress pgStep = new Progress();
534 pg.addProgress(pgStep, 1);
535
536 lib.getFile(luid, pgStep);
537 }
538
539 return null;
540 }
541
542 @Override
543 protected void done() {
544 try {
545 get();
546 for (BookInfo book : selected) {
547 informer.setCached(book, true);
548 }
549 } catch (Exception e) {
550 UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(), "IOException", e);
551 }
552 }
553 }.execute();
554 }
555 });
556
557 return refresh;
558 }
559
560 /**
561 * Create the delete menu item.
562 *
563 * @return the item
564 */
565 private JMenuItem createMenuItemDelete() {
566 JMenuItem delete = new JMenuItem(trans(StringIdGui.MENU_EDIT_DELETE), KeyEvent.VK_D);
567 delete.addActionListener(new ActionListener() {
568 @Override
569 public void actionPerformed(ActionEvent e) {
570 // final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
571 // if (selectedBook != null && selectedBook.getInfo().getMeta() != null) {
572 //
573 // final MetaData meta = selectedBook.getInfo().getMeta();
574 // int rep = JOptionPane.showConfirmDialog(GuiReaderFrame.this,
575 // trans(StringIdGui.SUBTITLE_DELETE, meta.getLuid(), meta.getTitle()),
576 // trans(StringIdGui.TITLE_DELETE), JOptionPane.OK_CANCEL_OPTION);
577 //
578 // if (rep == JOptionPane.OK_OPTION) {
579 // mainPanel.outOfUi(null, true, new Runnable() {
580 // @Override
581 // public void run() {
582 // reader.delete(meta.getLuid());
583 // mainPanel.unsetSelectedBook();
584 // }
585 // });
586 // }
587 // }
588 }
589 });
590
591 return delete;
592 }
593
594 /**
595 * Create the properties menu item.
596 *
597 * @return the item
598 */
599 private JMenuItem createMenuItemProperties() {
600 JMenuItem delete = new JMenuItem(trans(StringIdGui.MENU_FILE_PROPERTIES), KeyEvent.VK_P);
601 delete.addActionListener(new ActionListener() {
602 @Override
603 public void actionPerformed(ActionEvent e) {
604 // final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
605 // if (selectedBook != null) {
606 // mainPanel.outOfUi(null, false, new Runnable() {
607 // @Override
608 // public void run() {
609 // new GuiReaderPropertiesFrame(lib, selectedBook.getInfo().getMeta())
610 // .setVisible(true);
611 // }
612 // });
613 // }
614 }
615 });
616
617 return delete;
618 }
619
620 /**
621 * Create the open menu item for a book, a source/type or an author.
622 *
623 * @return the item
624 */
625 public JMenuItem createMenuItemOpenBook() {
626 JMenuItem open = new JMenuItem(trans(StringIdGui.MENU_FILE_OPEN), KeyEvent.VK_O);
627 open.addActionListener(new ActionListener() {
628 @Override
629 public void actionPerformed(ActionEvent e) {
630 final BookInfo book = informer.getUniqueSelected();
631 if (book != null) {
632 Actions.openExternal(lib, book.getMeta(), BookPopup.this.getParent(), new Runnable() {
633 @Override
634 public void run() {
635 informer.setCached(book, true);
636 }
637 });
638 }
639 }
640 });
641
642 return open;
643 }
644
645 /**
646 * Create the SetCover menu item for a book to change the linked source cover.
647 *
648 * @return the item
649 */
650 private JMenuItem createMenuItemSetCoverForSource() {
651 JMenuItem open = new JMenuItem(trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_SOURCE), KeyEvent.VK_C);
652 open.addActionListener(new ActionListener() {
653 @Override
654 public void actionPerformed(ActionEvent ae) {
655 // final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
656 // if (selectedBook != null) {
657 // BasicLibrary lib = lib;
658 // String luid = selectedBook.getInfo().getMeta().getLuid();
659 // String source = selectedBook.getInfo().getMeta().getSource();
660 //
661 // try {
662 // lib.setSourceCover(source, luid);
663 // } catch (IOException e) {
664 // error(e.getLocalizedMessage(), "IOException", e);
665 // }
666 //
667 // GuiReaderBookInfo sourceInfo = GuiReaderBookInfo.fromSource(lib, source);
668 // GuiReaderCoverImager.clearIcon(sourceInfo);
669 // }
670 }
671 });
672
673 return open;
674 }
675
676 /**
677 * Create the SetCover menu item for a book to change the linked source cover.
678 *
679 * @return the item
680 */
681 private JMenuItem createMenuItemSetCoverForAuthor() {
682 JMenuItem open = new JMenuItem(trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_AUTHOR), KeyEvent.VK_A);
683 open.addActionListener(new ActionListener() {
684 @Override
685 public void actionPerformed(ActionEvent ae) {
686 // final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
687 // if (selectedBook != null) {
688 // String luid = selectedBook.getInfo().getMeta().getLuid();
689 // String author = selectedBook.getInfo().getMeta().getAuthor();
690 //
691 // try {
692 // lib.setAuthorCover(author, luid);
693 // } catch (IOException e) {
694 // error(e.getLocalizedMessage(), "IOException", e);
695 // }
696 //
697 // GuiReaderBookInfo authorInfo = GuiReaderBookInfo.fromAuthor(lib, author);
698 // GuiReaderCoverImager.clearIcon(authorInfo);
699 // }
700 }
701 });
702
703 return open;
704 }
705 }