1 package be
.nikiroo
.fanfix_swing
.gui
.book
;
3 import java
.awt
.event
.ActionEvent
;
4 import java
.awt
.event
.ActionListener
;
5 import java
.awt
.event
.KeyEvent
;
7 import java
.io
.IOException
;
8 import java
.util
.HashMap
;
9 import java
.util
.LinkedList
;
10 import java
.util
.List
;
12 import java
.util
.Map
.Entry
;
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
;
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
;
38 public class BookPopup
extends JPopupMenu
{
39 public abstract interface Informer
{
42 public List
<BookInfo
> getSelected();
44 public void setCached(BookInfo book
, boolean cached
);
46 public BookInfo
getUniqueSelected();
48 public void fireElementChanged(BookInfo book
);
50 public void invalidateCache();
54 * The different modification actions you can use on {@link Story} items.
58 private enum ChangeAction
{
59 /** Change the source/type, that is, move it to another source. */
61 /** Change its name. */
63 /** Change its author. */
67 // be careful with that
68 private BasicLibrary lib
;
70 private Informer informer
;
72 public BookPopup(BasicLibrary lib
, Informer informer
) {
74 this.informer
= informer
;
76 Status status
= lib
.getStatus();
77 add(createMenuItemOpenBook());
79 add(createMenuItemExport());
80 if (status
.isWritable()) {
81 add(createMenuItemMoveTo());
82 add(createMenuItemSetCoverForSource());
83 add(createMenuItemSetCoverForAuthor());
85 add(createMenuItemDownloadToCache());
86 add(createMenuItemClearCache());
87 if (status
.isWritable()) {
88 add(createMenuItemRedownload());
90 add(createMenuItemRename());
91 add(createMenuItemSetAuthor());
93 add(createMenuItemDelete());
96 add(createMenuItemProperties());
99 private String
trans(StringIdGui id
) {
100 return Instance
.getInstance().getTransGui().getString(id
);
104 * Create the Fanfix Configuration menu item.
108 private JMenuItem
createMenuItemConfig() {
109 final String title
= trans(StringIdGui
.TITLE_CONFIG
);
110 JMenuItem item
= new JMenuItem(title
);
111 item
.setMnemonic(KeyEvent
.VK_F
);
113 item
.addActionListener(new ActionListener() {
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
);
120 frame
.setSize(850, 600);
121 frame
.setVisible(true);
129 * Create the UI Configuration menu item.
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
);
138 item
.addActionListener(new ActionListener() {
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
);
145 frame
.setSize(800, 600);
146 frame
.setVisible(true);
154 * Create the export menu item.
158 private JMenuItem
createMenuItemExport() {
160 // TODO: allow dir for multiple selection?
162 final JFileChooser fc
= new JFileChooser();
163 fc
.setAcceptAllFileFilterUsed(false);
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);
171 if (ext
== null || ext
.isEmpty()) {
172 fc
.addChoosableFileFilter(createAllFilter(desc
));
174 otherFilters
.put(new FileNameExtensionFilter(desc
, ext
), type
);
178 for (Entry
<FileFilter
, OutputType
> entry
: otherFilters
.entrySet()) {
179 fc
.addChoosableFileFilter(entry
.getKey());
183 JMenuItem export
= new JMenuItem(trans(StringIdGui
.MENU_FILE_EXPORT
), KeyEvent
.VK_S
);
184 export
.addActionListener(new ActionListener() {
186 public void actionPerformed(ActionEvent e
) {
187 final BookInfo book
= informer
.getUniqueSelected();
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();
195 new SwingWorker
<Void
, Void
>() {
197 protected Void
doInBackground() throws Exception
{
198 lib
.export(book
.getMeta().getLuid(), type
, path
, pg
);
203 protected void done() {
206 } catch (Exception e
) {
207 UiHelper
.error(BookPopup
.this.getParent(), e
.getLocalizedMessage(), "IOException",
221 * Create a {@link FileFilter} that accepts all files and return the given
224 * @param desc the description
228 private FileFilter
createAllFilter(final String desc
) {
229 return new FileFilter() {
231 public String
getDescription() {
236 public boolean accept(File f
) {
243 * Create the refresh (delete cache) menu item.
247 private JMenuItem
createMenuItemClearCache() {
248 JMenuItem refresh
= new JMenuItem(trans(StringIdGui
.MENU_EDIT_CLEAR_CACHE
), KeyEvent
.VK_C
);
249 refresh
.addActionListener(new ActionListener() {
251 public void actionPerformed(ActionEvent e
) {
252 final List
<BookInfo
> selected
= informer
.getSelected();
253 if (!selected
.isEmpty()) {
254 new SwingWorker
<Void
, Void
>() {
256 protected Void
doInBackground() throws Exception
{
257 for (BookInfo book
: selected
) {
258 lib
.clearFromCache(book
.getMeta().getLuid());
259 BookCoverImager
.clearIcon(book
);
265 protected void done() {
268 for (BookInfo book
: selected
) {
269 informer
.setCached(book
, false);
271 } catch (Exception e
) {
272 UiHelper
.error(BookPopup
.this.getParent(), e
.getLocalizedMessage(), "IOException", e
);
284 * Create the "move to" menu item.
288 private JMenuItem
createMenuItemMoveTo() {
289 JMenu changeTo
= new JMenu(trans(StringIdGui
.MENU_FILE_MOVE_TO
));
290 changeTo
.setMnemonic(KeyEvent
.VK_M
);
292 Map
<String
, List
<String
>> groupedSources
= new HashMap
<String
, List
<String
>>();
294 groupedSources
= lib
.getSourcesGrouped();
295 } catch (IOException e
) {
296 UiHelper
.error(BookPopup
.this.getParent(), e
.getLocalizedMessage(), "IOException", e
);
299 JMenuItem item
= new JMenuItem(trans(StringIdGui
.MENU_FILE_MOVE_TO_NEW_TYPE
));
300 item
.addActionListener(createMoveAction(ChangeAction
.SOURCE
, null));
302 changeTo
.addSeparator();
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
));
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
;
320 item
= new JMenuItem(itemName
);
321 item
.addActionListener(createMoveAction(ChangeAction
.SOURCE
, actualType
));
332 * Create the "set author" menu item.
336 private JMenuItem
createMenuItemSetAuthor() {
337 JMenu changeTo
= new JMenu(trans(StringIdGui
.MENU_FILE_SET_AUTHOR
));
338 changeTo
.setMnemonic(KeyEvent
.VK_A
);
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));
347 Map
<String
, List
<String
>> groupedAuthors
;
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
>>();
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
));
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
));
379 * Create the "rename" menu item.
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));
390 private ActionListener
createMoveAction(final ChangeAction what
, final String type
) {
391 return new ActionListener() {
393 public void actionPerformed(ActionEvent e
) {
394 final List
<BookInfo
> selected
= informer
.getSelected();
395 if (!selected
.isEmpty()) {
396 String changeTo
= type
;
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();
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
);
419 changeTo
= rep
.toString();
422 final String fChangeTo
= changeTo
;
423 new SwingWorker
<Void
, Void
>() {
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);
441 protected void done() {
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();
447 // TODO: also refresh the Sources/Authors(/Tags?) list
449 // Even if problems occurred, still invalidate the cache
451 } catch (Exception e
) {
452 UiHelper
.error(BookPopup
.this.getParent(), e
.getLocalizedMessage(), "IOException", e
);
462 * Create the re-download (then delete original) menu item.
466 private JMenuItem
createMenuItemRedownload() {
467 JMenuItem refresh
= new JMenuItem(trans(StringIdGui
.MENU_EDIT_REDOWNLOAD
), KeyEvent
.VK_R
);
468 refresh
.addActionListener(new ActionListener() {
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() {
476 // public void run(MetaData newMeta) {
477 // if (!newMeta.getSource().equals(meta.getSource())) {
478 // reader.changeSource(newMeta.getLuid(), meta.getSource());
481 // }, trans(StringIdGui.PROGRESS_CHANGE_SOURCE));
490 * Create the download to cache menu item.
494 private JMenuItem
createMenuItemDownloadToCache() {
495 JMenuItem refresh
= new JMenuItem(trans(StringIdGui
.MENU_EDIT_DOWNLOAD_TO_CACHE
), KeyEvent
.VK_T
);
496 refresh
.addActionListener(new ActionListener() {
498 public void actionPerformed(ActionEvent e
) {
499 final List
<BookInfo
> selected
= informer
.getSelected();
501 new SwingWorker
<Void
, Void
>() {
503 protected Void
doInBackground() throws Exception
{
505 final List
<String
> luids
= new LinkedList
<String
>();
506 for (BookInfo book
: selected
) {
507 switch (book
.getType()) {
509 luids
.add(book
.getMeta().getLuid());
512 for (MetaData meta
: lib
.getList().filter(book
.getMainInfo(), null, null)) {
513 luids
.add(meta
.getLuid());
517 for (MetaData meta
: lib
.getList().filter(null, book
.getMainInfo(), null)) {
518 luids
.add(meta
.getLuid());
522 for (MetaData meta
: lib
.getList().filter(null, null, book
.getMainInfo())) {
523 luids
.add(meta
.getLuid());
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);
536 lib
.getFile(luid
, pgStep
);
543 protected void done() {
546 for (BookInfo book
: selected
) {
547 informer
.setCached(book
, true);
549 } catch (Exception e
) {
550 UiHelper
.error(BookPopup
.this.getParent(), e
.getLocalizedMessage(), "IOException", e
);
561 * Create the delete menu item.
565 private JMenuItem
createMenuItemDelete() {
566 JMenuItem delete
= new JMenuItem(trans(StringIdGui
.MENU_EDIT_DELETE
), KeyEvent
.VK_D
);
567 delete
.addActionListener(new ActionListener() {
569 public void actionPerformed(ActionEvent e
) {
570 // final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
571 // if (selectedBook != null && selectedBook.getInfo().getMeta() != null) {
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);
578 // if (rep == JOptionPane.OK_OPTION) {
579 // mainPanel.outOfUi(null, true, new Runnable() {
581 // public void run() {
582 // reader.delete(meta.getLuid());
583 // mainPanel.unsetSelectedBook();
595 * Create the properties menu item.
599 private JMenuItem
createMenuItemProperties() {
600 JMenuItem delete
= new JMenuItem(trans(StringIdGui
.MENU_FILE_PROPERTIES
), KeyEvent
.VK_P
);
601 delete
.addActionListener(new ActionListener() {
603 public void actionPerformed(ActionEvent e
) {
604 // final GuiReaderBook selectedBook = mainPanel.getSelectedBook();
605 // if (selectedBook != null) {
606 // mainPanel.outOfUi(null, false, new Runnable() {
608 // public void run() {
609 // new GuiReaderPropertiesFrame(lib, selectedBook.getInfo().getMeta())
610 // .setVisible(true);
621 * Create the open menu item for a book, a source/type or an author.
625 public JMenuItem
createMenuItemOpenBook() {
626 JMenuItem open
= new JMenuItem(trans(StringIdGui
.MENU_FILE_OPEN
), KeyEvent
.VK_O
);
627 open
.addActionListener(new ActionListener() {
629 public void actionPerformed(ActionEvent e
) {
630 final BookInfo book
= informer
.getUniqueSelected();
632 Actions
.openExternal(lib
, book
.getMeta(), BookPopup
.this.getParent(), new Runnable() {
635 informer
.setCached(book
, true);
646 * Create the SetCover menu item for a book to change the linked source cover.
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() {
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();
662 // lib.setSourceCover(source, luid);
663 // } catch (IOException e) {
664 // error(e.getLocalizedMessage(), "IOException", e);
667 // GuiReaderBookInfo sourceInfo = GuiReaderBookInfo.fromSource(lib, source);
668 // GuiReaderCoverImager.clearIcon(sourceInfo);
677 * Create the SetCover menu item for a book to change the linked source cover.
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() {
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();
692 // lib.setAuthorCover(author, luid);
693 // } catch (IOException e) {
694 // error(e.getLocalizedMessage(), "IOException", e);
697 // GuiReaderBookInfo authorInfo = GuiReaderBookInfo.fromAuthor(lib, author);
698 // GuiReaderCoverImager.clearIcon(authorInfo);