1 package be
.nikiroo
.fanfix
.reader
.ui
;
3 import java
.awt
.BorderLayout
;
7 import java
.awt
.Toolkit
;
8 import java
.awt
.datatransfer
.DataFlavor
;
9 import java
.awt
.event
.ActionEvent
;
10 import java
.awt
.event
.ActionListener
;
11 import java
.awt
.event
.KeyEvent
;
12 import java
.awt
.event
.MouseEvent
;
13 import java
.awt
.event
.WindowEvent
;
15 import java
.io
.IOException
;
17 import java
.net
.UnknownHostException
;
18 import java
.util
.ArrayList
;
19 import java
.util
.Arrays
;
20 import java
.util
.HashMap
;
21 import java
.util
.List
;
23 import java
.util
.Map
.Entry
;
25 import javax
.swing
.BorderFactory
;
26 import javax
.swing
.BoxLayout
;
27 import javax
.swing
.ImageIcon
;
28 import javax
.swing
.JFileChooser
;
29 import javax
.swing
.JFrame
;
30 import javax
.swing
.JLabel
;
31 import javax
.swing
.JMenu
;
32 import javax
.swing
.JMenuBar
;
33 import javax
.swing
.JMenuItem
;
34 import javax
.swing
.JOptionPane
;
35 import javax
.swing
.JPanel
;
36 import javax
.swing
.JPopupMenu
;
37 import javax
.swing
.JScrollPane
;
38 import javax
.swing
.JTextArea
;
39 import javax
.swing
.SwingConstants
;
40 import javax
.swing
.SwingUtilities
;
41 import javax
.swing
.filechooser
.FileFilter
;
42 import javax
.swing
.filechooser
.FileNameExtensionFilter
;
44 import be
.nikiroo
.fanfix
.Instance
;
45 import be
.nikiroo
.fanfix
.bundles
.Config
;
46 import be
.nikiroo
.fanfix
.bundles
.UiConfig
;
47 import be
.nikiroo
.fanfix
.data
.MetaData
;
48 import be
.nikiroo
.fanfix
.data
.Story
;
49 import be
.nikiroo
.fanfix
.library
.BasicLibrary
;
50 import be
.nikiroo
.fanfix
.library
.BasicLibrary
.Status
;
51 import be
.nikiroo
.fanfix
.library
.LocalLibrary
;
52 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
53 import be
.nikiroo
.fanfix
.reader
.BasicReader
;
54 import be
.nikiroo
.fanfix
.reader
.ui
.GuiReaderBook
.BookActionListener
;
55 import be
.nikiroo
.utils
.Progress
;
56 import be
.nikiroo
.utils
.Version
;
57 import be
.nikiroo
.utils
.ui
.ConfigEditor
;
58 import be
.nikiroo
.utils
.ui
.ProgressBar
;
61 * A {@link Frame} that will show a {@link GuiReaderBook} item for each
62 * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
63 * way to copy them to the {@link GuiReader} cache (
64 * {@link BasicReader#getLibrary()}), read them, delete them...
68 class GuiReaderFrame
extends JFrame
{
69 private static final long serialVersionUID
= 1L;
70 private GuiReader reader
;
71 private Map
<GuiReaderGroup
, String
> booksByType
;
72 private Map
<GuiReaderGroup
, String
> booksByAuthor
;
75 private ProgressBar pgBar
;
77 private GuiReaderBook selectedBook
;
78 private boolean words
; // words or authors (secondary info on books)
81 * A {@link Runnable} with a {@link Story} parameter.
85 private interface StoryRunnable
{
92 public void run(Story story
);
96 * Create a new {@link GuiReaderFrame}.
99 * the associated {@link GuiReader} to forward some commands and
100 * access its {@link LocalLibrary}
102 * the type of {@link Story} to load, or NULL for all types
104 public GuiReaderFrame(GuiReader reader
, String type
) {
105 super(String
.format("Fanfix %s Library", Version
.getCurrentVersion()));
107 this.reader
= reader
;
109 setDefaultCloseOperation(JFrame
.EXIT_ON_CLOSE
);
111 setLayout(new BorderLayout());
114 pane
.setLayout(new BoxLayout(pane
, BoxLayout
.PAGE_AXIS
));
116 Integer icolor
= Instance
.getUiConfig().getColor(
117 UiConfig
.BACKGROUND_COLOR
);
118 if (icolor
!= null) {
119 color
= new Color(icolor
);
120 setBackground(color
);
121 pane
.setBackground(color
);
124 JScrollPane scroll
= new JScrollPane(pane
);
125 scroll
.getVerticalScrollBar().setUnitIncrement(16);
126 add(scroll
, BorderLayout
.CENTER
);
128 String message
= reader
.getLibrary().getLibraryName();
129 if (!message
.isEmpty()) {
130 JLabel name
= new JLabel(message
, SwingConstants
.CENTER
);
131 add(name
, BorderLayout
.NORTH
);
134 pgBar
= new ProgressBar();
135 add(pgBar
, BorderLayout
.SOUTH
);
137 pgBar
.addActionListener(new ActionListener() {
139 public void actionPerformed(ActionEvent e
) {
141 pgBar
.setProgress(null);
147 pgBar
.addUpdateListener(new ActionListener() {
149 public void actionPerformed(ActionEvent e
) {
156 booksByType
= new HashMap
<GuiReaderGroup
, String
>();
157 booksByAuthor
= new HashMap
<GuiReaderGroup
, String
>();
159 pane
.setVisible(false);
160 final Progress pg
= new Progress();
161 final String typeF
= type
;
162 outOfUi(pg
, new Runnable() {
165 BasicLibrary lib
= GuiReaderFrame
.this.reader
.getLibrary();
166 Status status
= lib
.getStatus();
168 if (status
== Status
.READY
) {
171 setJMenuBar(createMenu(true));
172 addBookPane(typeF
, true);
175 pane
.setVisible(true);
178 setJMenuBar(createMenu(false));
181 String err
= lib
.getLibraryName() + "\n";
184 err
+= "Library not valid";
188 err
+= "You are not allowed to access this library";
192 err
+= "Library currently unavailable";
196 err
+= "An error occured when contacting the library";
200 error(err
, "Library error", null);
208 private void addSourcePanes() {
210 GuiReaderGroup bookPane
= new GuiReaderGroup(reader
, "Sources", color
);
212 List
<MetaData
> sources
= new ArrayList
<MetaData
>();
213 for (String source
: reader
.getLibrary().getSources()) {
214 MetaData mSource
= new MetaData();
215 mSource
.setLuid(null);
216 mSource
.setTitle(source
);
217 mSource
.setSource(source
);
218 sources
.add(mSource
);
221 bookPane
.refreshBooks(sources
, false);
229 bookPane
.setActionListener(new BookActionListener() {
231 public void select(GuiReaderBook book
) {
236 public void popupRequested(GuiReaderBook book
, MouseEvent e
) {
237 JPopupMenu popup
= new JPopupMenu();
238 popup
.add(createMenuItemOpenBook());
239 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
243 public void action(final GuiReaderBook book
) {
245 addBookPane(book
.getMeta().getSource(), true);
252 * Add a new {@link GuiReaderGroup} on the frame to display the books of the
253 * selected type or author.
256 * the author or the type, or NULL to get all the
259 * TRUE for type, FALSE for author
261 private void addBookPane(String value
, boolean type
) {
264 if (Instance
.getUiConfig().getBoolean(UiConfig
.SOURCE_PAGE
,
268 for (String tt
: reader
.getLibrary().getSources()) {
270 addBookPane(tt
, type
);
275 for (String tt
: reader
.getLibrary().getAuthors()) {
277 addBookPane(tt
, type
);
285 GuiReaderGroup bookPane
= new GuiReaderGroup(reader
, value
, color
);
287 booksByType
.put(bookPane
, value
);
289 booksByAuthor
.put(bookPane
, value
);
298 bookPane
.setActionListener(new BookActionListener() {
300 public void select(GuiReaderBook book
) {
305 public void popupRequested(GuiReaderBook book
, MouseEvent e
) {
306 JPopupMenu popup
= new JPopupMenu();
307 popup
.add(createMenuItemOpenBook());
308 popup
.addSeparator();
309 popup
.add(createMenuItemExport());
310 popup
.add(createMenuItemMoveTo(true));
311 popup
.add(createMenuItemSetCover());
312 popup
.add(createMenuItemClearCache());
313 popup
.add(createMenuItemRedownload());
314 popup
.addSeparator();
315 popup
.add(createMenuItemRename(true));
316 popup
.add(createMenuItemSetAuthor(true));
317 popup
.addSeparator();
318 popup
.add(createMenuItemDelete());
319 popup
.addSeparator();
320 popup
.add(createMenuItemProperties());
321 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
325 public void action(final GuiReaderBook book
) {
331 private void removeBookPanes() {
333 booksByAuthor
.clear();
342 * Refresh the list of {@link GuiReaderBook}s from disk.
344 private void refreshBooks() {
345 for (GuiReaderGroup group
: booksByType
.keySet()) {
346 List
<MetaData
> stories
= reader
.getLibrary().getListBySource(
347 booksByType
.get(group
));
348 group
.refreshBooks(stories
, words
);
351 for (GuiReaderGroup group
: booksByAuthor
.keySet()) {
352 List
<MetaData
> stories
= reader
.getLibrary().getListByAuthor(
353 booksByAuthor
.get(group
));
354 group
.refreshBooks(stories
, words
);
362 * Create the main menu bar.
365 * the library can be queried
369 private JMenuBar
createMenu(boolean libOk
) {
370 bar
= new JMenuBar();
372 JMenu file
= new JMenu("File");
373 file
.setMnemonic(KeyEvent
.VK_F
);
375 JMenuItem imprt
= new JMenuItem("Import URL...", KeyEvent
.VK_U
);
376 imprt
.addActionListener(new ActionListener() {
378 public void actionPerformed(ActionEvent e
) {
382 JMenuItem imprtF
= new JMenuItem("Import File...", KeyEvent
.VK_F
);
383 imprtF
.addActionListener(new ActionListener() {
385 public void actionPerformed(ActionEvent e
) {
389 JMenuItem exit
= new JMenuItem("Exit", KeyEvent
.VK_X
);
390 exit
.addActionListener(new ActionListener() {
392 public void actionPerformed(ActionEvent e
) {
393 GuiReaderFrame
.this.dispatchEvent(new WindowEvent(
394 GuiReaderFrame
.this, WindowEvent
.WINDOW_CLOSING
));
398 file
.add(createMenuItemOpenBook());
399 file
.add(createMenuItemExport());
400 file
.add(createMenuItemMoveTo(libOk
));
405 file
.add(createMenuItemRename(libOk
));
406 file
.add(createMenuItemSetAuthor(libOk
));
412 JMenu edit
= new JMenu("Edit");
413 edit
.setMnemonic(KeyEvent
.VK_E
);
415 edit
.add(createMenuItemClearCache());
416 edit
.add(createMenuItemRedownload());
418 edit
.add(createMenuItemDelete());
422 JMenu view
= new JMenu("View");
423 view
.setMnemonic(KeyEvent
.VK_V
);
424 JMenuItem vauthors
= new JMenuItem("Author");
425 vauthors
.setMnemonic(KeyEvent
.VK_A
);
426 vauthors
.addActionListener(new ActionListener() {
428 public void actionPerformed(ActionEvent e
) {
434 JMenuItem vwords
= new JMenuItem("Word count");
435 vwords
.setMnemonic(KeyEvent
.VK_W
);
436 vwords
.addActionListener(new ActionListener() {
438 public void actionPerformed(ActionEvent e
) {
446 JMenu sources
= new JMenu("Sources");
447 sources
.setMnemonic(KeyEvent
.VK_S
);
449 Map
<String
, List
<String
>> groupedSources
= new HashMap
<String
, List
<String
>>();
451 groupedSources
= reader
.getLibrary().getSourcesGrouped();
454 JMenuItem item
= new JMenuItem("All");
455 item
.addActionListener(getActionOpenSource(null));
457 sources
.addSeparator();
459 for (final String type
: groupedSources
.keySet()) {
460 List
<String
> list
= groupedSources
.get(type
);
461 if (list
.size() == 1 && list
.get(0).isEmpty()) {
462 item
= new JMenuItem(type
);
463 item
.addActionListener(getActionOpenSource(type
));
466 JMenu dir
= new JMenu(type
);
467 for (String sub
: list
) {
468 // " " instead of "" for the visual height
469 String itemName
= sub
.isEmpty() ?
" " : sub
;
470 String actualType
= type
;
471 if (!sub
.isEmpty()) {
472 actualType
+= "/" + sub
;
475 item
= new JMenuItem(itemName
);
476 item
.addActionListener(getActionOpenSource(actualType
));
485 JMenu authors
= new JMenu("Authors");
486 authors
.setMnemonic(KeyEvent
.VK_A
);
488 List
<Entry
<String
, List
<String
>>> authorGroups
= reader
.getLibrary()
489 .getAuthorsGrouped();
490 if (authorGroups
.size() > 1) {
493 // null -> "All" authors special item
494 populateMenuAuthorList(authors
, Arrays
.asList((String
) null));
496 for (Entry
<String
, List
<String
>> group
: authorGroups
) {
497 JMenu thisGroup
= new JMenu(group
.getKey());
498 populateMenuAuthorList(thisGroup
, group
.getValue());
499 authors
.add(thisGroup
);
504 // null -> "All" authors special item
505 List
<String
> authorNames
= new ArrayList
<String
>();
506 authorNames
.add(null);
507 if (authorGroups
.size() > 0) {
508 authorNames
.addAll(authorGroups
.get(0).getValue());
510 populateMenuAuthorList(authors
, authorNames
);
515 JMenu options
= new JMenu("Options");
516 options
.setMnemonic(KeyEvent
.VK_O
);
517 options
.add(createMenuItemConfig());
518 options
.add(createMenuItemUiConfig());
525 * Return an {@link ActionListener} that will set the given source (type) as
526 * the selected/displayed one.
529 * the type (source) to select
531 * @return the {@link ActionListener}
533 private ActionListener
getActionOpenSource(final String type
) {
534 return new ActionListener() {
536 public void actionPerformed(ActionEvent e
) {
538 addBookPane(type
, true);
545 * Populate a list of authors as {@link JMenuItem}s into the given
548 * Each item will select the author when clicked.
551 * the parent {@link JMenuItem}
555 private void populateMenuAuthorList(JMenu authors
, List
<String
> names
) {
556 for (final String name
: names
) {
557 JMenuItem item
= new JMenuItem(name
== null ?
"All"
558 : name
.isEmpty() ?
"[unknown]" : name
);
559 item
.addActionListener(new ActionListener() {
561 public void actionPerformed(ActionEvent e
) {
563 addBookPane(name
, false);
569 if (name
== null || name
.isEmpty()) {
570 authors
.addSeparator();
576 * Create the Fanfix Configuration menu item.
580 private JMenuItem
createMenuItemConfig() {
581 final String title
= "Fanfix Configuration";
582 JMenuItem item
= new JMenuItem(title
);
583 item
.setMnemonic(KeyEvent
.VK_F
);
585 item
.addActionListener(new ActionListener() {
587 public void actionPerformed(ActionEvent e
) {
588 ConfigEditor
<Config
> ed
= new ConfigEditor
<Config
>(
589 Config
.class, Instance
.getConfig(),
590 "This is where you configure the options of the program.");
591 JFrame frame
= new JFrame(title
);
593 frame
.setSize(800, 600);
594 frame
.setVisible(true);
602 * Create the UI Configuration menu item.
606 private JMenuItem
createMenuItemUiConfig() {
607 final String title
= "UI Configuration";
608 JMenuItem item
= new JMenuItem(title
);
609 item
.setMnemonic(KeyEvent
.VK_U
);
611 item
.addActionListener(new ActionListener() {
613 public void actionPerformed(ActionEvent e
) {
614 ConfigEditor
<UiConfig
> ed
= new ConfigEditor
<UiConfig
>(
615 UiConfig
.class, Instance
.getUiConfig(),
616 "This is where you configure the graphical appearence of the program.");
617 JFrame frame
= new JFrame(title
);
619 frame
.setSize(800, 600);
620 frame
.setVisible(true);
628 * Create the export menu item.
632 private JMenuItem
createMenuItemExport() {
633 final JFileChooser fc
= new JFileChooser();
634 fc
.setAcceptAllFileFilterUsed(false);
636 final Map
<FileFilter
, OutputType
> filters
= new HashMap
<FileFilter
, OutputType
>();
637 for (OutputType type
: OutputType
.values()) {
638 String ext
= type
.getDefaultExtension(false);
639 String desc
= type
.getDesc(false);
641 if (ext
== null || ext
.isEmpty()) {
642 filters
.put(createAllFilter(desc
), type
);
644 filters
.put(new FileNameExtensionFilter(desc
, ext
), type
);
648 // First the "ALL" filters, then, the extension filters
649 for (Entry
<FileFilter
, OutputType
> entry
: filters
.entrySet()) {
650 if (!(entry
.getKey() instanceof FileNameExtensionFilter
)) {
651 fc
.addChoosableFileFilter(entry
.getKey());
654 for (Entry
<FileFilter
, OutputType
> entry
: filters
.entrySet()) {
655 if (entry
.getKey() instanceof FileNameExtensionFilter
) {
656 fc
.addChoosableFileFilter(entry
.getKey());
661 JMenuItem export
= new JMenuItem("Save as...", KeyEvent
.VK_S
);
662 export
.addActionListener(new ActionListener() {
664 public void actionPerformed(ActionEvent e
) {
665 if (selectedBook
!= null) {
666 fc
.showDialog(GuiReaderFrame
.this, "Save");
667 if (fc
.getSelectedFile() != null) {
668 final OutputType type
= filters
.get(fc
.getFileFilter());
669 final String path
= fc
.getSelectedFile()
671 + type
.getDefaultExtension(false);
672 final Progress pg
= new Progress();
673 outOfUi(pg
, new Runnable() {
677 reader
.getLibrary().export(
678 selectedBook
.getMeta().getLuid(),
680 } catch (IOException e
) {
681 Instance
.getTraceHandler().error(e
);
694 * Create a {@link FileFilter} that accepts all files and return the given
702 private FileFilter
createAllFilter(final String desc
) {
703 return new FileFilter() {
705 public String
getDescription() {
710 public boolean accept(File f
) {
717 * Create the refresh (delete cache) menu item.
721 private JMenuItem
createMenuItemClearCache() {
722 JMenuItem refresh
= new JMenuItem("Clear cache", KeyEvent
.VK_C
);
723 refresh
.addActionListener(new ActionListener() {
725 public void actionPerformed(ActionEvent e
) {
726 if (selectedBook
!= null) {
727 outOfUi(null, new Runnable() {
730 reader
.clearLocalReaderCache(selectedBook
.getMeta()
732 selectedBook
.setCached(false);
733 GuiReaderCoverImager
.clearIcon(selectedBook
735 SwingUtilities
.invokeLater(new Runnable() {
738 selectedBook
.repaint();
751 * Create the "move to" menu item.
754 * the library can be queried
758 private JMenuItem
createMenuItemMoveTo(boolean libOk
) {
759 JMenu changeTo
= new JMenu("Move to");
760 changeTo
.setMnemonic(KeyEvent
.VK_M
);
762 Map
<String
, List
<String
>> groupedSources
= new HashMap
<String
, List
<String
>>();
764 groupedSources
= reader
.getLibrary().getSourcesGrouped();
767 JMenuItem item
= new JMenuItem("New type...");
768 item
.addActionListener(createMoveAction("SOURCE", null));
770 changeTo
.addSeparator();
772 for (final String type
: groupedSources
.keySet()) {
773 List
<String
> list
= groupedSources
.get(type
);
774 if (list
.size() == 1 && list
.get(0).isEmpty()) {
775 item
= new JMenuItem(type
);
776 item
.addActionListener(createMoveAction("SOURCE", type
));
779 JMenu dir
= new JMenu(type
);
780 for (String sub
: list
) {
781 // " " instead of "" for the visual height
782 String itemName
= sub
.isEmpty() ?
" " : sub
;
783 String actualType
= type
;
784 if (!sub
.isEmpty()) {
785 actualType
+= "/" + sub
;
788 item
= new JMenuItem(itemName
);
789 item
.addActionListener(createMoveAction("SOURCE",
801 * Create the "set author" menu item.
804 * the library can be queried
808 private JMenuItem
createMenuItemSetAuthor(boolean libOk
) {
809 JMenu changeTo
= new JMenu("Set author");
810 changeTo
.setMnemonic(KeyEvent
.VK_A
);
813 JMenuItem newItem
= new JMenuItem("New author...");
814 changeTo
.add(newItem
);
815 changeTo
.addSeparator();
816 newItem
.addActionListener(createMoveAction("AUTHOR", null));
820 List
<Entry
<String
, List
<String
>>> authorGroups
= reader
821 .getLibrary().getAuthorsGrouped();
823 if (authorGroups
.size() > 1) {
824 for (Entry
<String
, List
<String
>> entry
: authorGroups
) {
825 JMenu group
= new JMenu(entry
.getKey());
826 for (String value
: entry
.getValue()) {
827 JMenuItem item
= new JMenuItem(value
);
828 item
.addActionListener(createMoveAction("AUTHOR", value
));
833 } else if (authorGroups
.size() == 1) {
834 for (String value
: authorGroups
.get(0).getValue()) {
835 JMenuItem item
= new JMenuItem(value
);
836 item
.addActionListener(createMoveAction("AUTHOR", value
));
846 * Create the "rename" menu item.
849 * the library can be queried
853 private JMenuItem
createMenuItemRename(
854 @SuppressWarnings("unused") boolean libOk
) {
855 JMenuItem changeTo
= new JMenuItem("Rename...");
856 changeTo
.setMnemonic(KeyEvent
.VK_R
);
857 changeTo
.addActionListener(createMoveAction("TITLE", null));
861 private ActionListener
createMoveAction(final String what
, final String type
) {
862 return new ActionListener() {
864 public void actionPerformed(ActionEvent e
) {
865 if (selectedBook
!= null) {
866 String changeTo
= type
;
869 if (what
.equals("SOURCE")) {
870 init
= selectedBook
.getMeta().getSource();
871 } else if (what
.equals("TITLE")) {
872 init
= selectedBook
.getMeta().getTitle();
873 } else if (what
.equals("AUTHOR")) {
874 init
= selectedBook
.getMeta().getAuthor();
877 Object rep
= JOptionPane
.showInputDialog(
878 GuiReaderFrame
.this, "Move to:",
879 "Moving story", JOptionPane
.QUESTION_MESSAGE
,
886 changeTo
= rep
.toString();
889 final String fChangeTo
= changeTo
;
890 outOfUi(null, new Runnable() {
893 if (what
.equals("SOURCE")) {
894 reader
.changeSource(selectedBook
.getMeta()
895 .getLuid(), fChangeTo
);
896 } else if (what
.equals("TITLE")) {
897 reader
.changeTitle(selectedBook
.getMeta()
898 .getLuid(), fChangeTo
);
899 } else if (what
.equals("AUTHOR")) {
900 reader
.changeAuthor(selectedBook
.getMeta()
901 .getLuid(), fChangeTo
);
906 SwingUtilities
.invokeLater(new Runnable() {
909 setJMenuBar(createMenu(true));
920 * Create the redownload (then delete original) menu item.
924 private JMenuItem
createMenuItemRedownload() {
925 JMenuItem refresh
= new JMenuItem("Redownload", KeyEvent
.VK_R
);
926 refresh
.addActionListener(new ActionListener() {
928 public void actionPerformed(ActionEvent e
) {
929 if (selectedBook
!= null) {
930 final MetaData meta
= selectedBook
.getMeta();
931 imprt(meta
.getUrl(), new StoryRunnable() {
933 public void run(Story story
) {
934 reader
.delete(meta
.getLuid());
935 GuiReaderFrame
.this.selectedBook
= null;
936 MetaData newMeta
= story
.getMeta();
937 if (!newMeta
.getSource().equals(meta
.getSource())) {
938 reader
.changeSource(newMeta
.getLuid(),
942 }, "Removing old copy");
951 * Create the delete menu item.
955 private JMenuItem
createMenuItemDelete() {
956 JMenuItem delete
= new JMenuItem("Delete", KeyEvent
.VK_D
);
957 delete
.addActionListener(new ActionListener() {
959 public void actionPerformed(ActionEvent e
) {
960 if (selectedBook
!= null) {
961 outOfUi(null, new Runnable() {
964 reader
.delete(selectedBook
.getMeta().getLuid());
976 * Create the properties menu item.
980 private JMenuItem
createMenuItemProperties() {
981 JMenuItem delete
= new JMenuItem("Properties", KeyEvent
.VK_P
);
982 delete
.addActionListener(new ActionListener() {
984 public void actionPerformed(ActionEvent e
) {
985 if (selectedBook
!= null) {
986 outOfUi(null, new Runnable() {
989 final MetaData meta
= selectedBook
.getMeta();
991 private static final long serialVersionUID
= 1L;
992 @SuppressWarnings("unused")
993 private Object init
= init();
995 private Object
init() {
1001 ImageIcon img
= GuiReaderCoverImager
1003 reader
.getLibrary(), meta
);
1006 setTitle(meta
.getLuid() + ": "
1009 setSize(800, img
.getIconHeight() + 2 * top
);
1010 setLayout(new BorderLayout());
1013 JPanel mainPanel
= new JPanel(
1014 new BorderLayout());
1015 JPanel mainPanelKeys
= new JPanel();
1016 mainPanelKeys
.setLayout(new BoxLayout(
1017 mainPanelKeys
, BoxLayout
.Y_AXIS
));
1018 JPanel mainPanelValues
= new JPanel();
1019 mainPanelValues
.setLayout(new BoxLayout(
1020 mainPanelValues
, BoxLayout
.Y_AXIS
));
1022 mainPanel
.add(mainPanelKeys
,
1024 mainPanel
.add(mainPanelValues
,
1025 BorderLayout
.CENTER
);
1027 List
<Entry
<String
, String
>> infos
= BasicReader
1030 Color trans
= new Color(0, 0, 0, 1);
1031 for (Entry
<String
, String
> info
: infos
) {
1032 JTextArea key
= new JTextArea(info
1034 key
.setFont(new Font(key
.getFont()
1035 .getFontName(), Font
.BOLD
, key
1036 .getFont().getSize()));
1037 key
.setEditable(false);
1038 key
.setLineWrap(false);
1039 key
.setBackground(trans
);
1040 mainPanelKeys
.add(key
);
1042 JTextArea value
= new JTextArea(info
1044 value
.setEditable(false);
1045 value
.setLineWrap(false);
1046 value
.setBackground(trans
);
1047 mainPanelValues
.add(value
);
1051 JLabel imgLabel
= new JLabel(img
);
1052 imgLabel
.setVerticalAlignment(JLabel
.TOP
);
1055 mainPanelKeys
.setBorder(BorderFactory
1056 .createEmptyBorder(top
, space
, 0, 0));
1057 mainPanelValues
.setBorder(BorderFactory
1058 .createEmptyBorder(top
, space
, 0, 0));
1059 imgLabel
.setBorder(BorderFactory
1060 .createEmptyBorder(0, space
, 0, 0));
1063 add(imgLabel
, BorderLayout
.WEST
);
1064 add(mainPanel
, BorderLayout
.CENTER
);
1080 * Create the open menu item for a book or a source (no LUID).
1084 private JMenuItem
createMenuItemOpenBook() {
1085 JMenuItem open
= new JMenuItem("Open", KeyEvent
.VK_O
);
1086 open
.addActionListener(new ActionListener() {
1088 public void actionPerformed(ActionEvent e
) {
1089 if (selectedBook
!= null) {
1090 if (selectedBook
.getMeta().getLuid() == null) {
1092 addBookPane(selectedBook
.getMeta().getSource(), true);
1095 openBook(selectedBook
);
1105 * Create the SetCover menu item for a book to change the linked source
1110 private JMenuItem
createMenuItemSetCover() {
1111 JMenuItem open
= new JMenuItem("Set as cover for source", KeyEvent
.VK_C
);
1112 open
.addActionListener(new ActionListener() {
1114 public void actionPerformed(ActionEvent e
) {
1115 if (selectedBook
!= null) {
1116 reader
.getLibrary().setSourceCover(
1117 selectedBook
.getMeta().getSource(),
1118 selectedBook
.getMeta().getLuid());
1119 MetaData source
= selectedBook
.getMeta().clone();
1120 source
.setLuid(null);
1121 GuiReaderCoverImager
.clearIcon(source
);
1130 * Open a {@link GuiReaderBook} item.
1133 * the {@link GuiReaderBook} to open
1135 private void openBook(final GuiReaderBook book
) {
1136 final Progress pg
= new Progress();
1137 outOfUi(pg
, new Runnable() {
1141 reader
.read(book
.getMeta().getLuid(), false, pg
);
1142 SwingUtilities
.invokeLater(new Runnable() {
1145 book
.setCached(true);
1148 } catch (IOException e
) {
1149 // TODO: error message?
1150 Instance
.getTraceHandler().error(e
);
1157 * Process the given action out of the Swing UI thread and link the given
1158 * {@link ProgressBar} to the action.
1160 * The code will make sure that the {@link ProgressBar} (if not NULL) is set
1161 * to done when the action is done.
1164 * the {@link ProgressBar} or NULL
1168 private void outOfUi(Progress progress
, final Runnable run
) {
1169 final Progress pg
= new Progress();
1170 final Progress reload
= new Progress("Reload books");
1171 if (progress
== null) {
1172 progress
= new Progress();
1175 pg
.addProgress(progress
, 90);
1176 pg
.addProgress(reload
, 10);
1179 pgBar
.setProgress(pg
);
1183 new Thread(new Runnable() {
1192 // will trigger pgBar ActionListener:
1197 }, "outOfUi thread").start();
1201 * Import a {@link Story} into the main {@link LocalLibrary}.
1203 * Should be called inside the UI thread.
1206 * TRUE for an {@link URL}, false for a {@link File}
1208 private void imprt(boolean askUrl
) {
1209 JFileChooser fc
= new JFileChooser();
1213 String clipboard
= "";
1215 clipboard
= ("" + Toolkit
.getDefaultToolkit()
1216 .getSystemClipboard().getData(DataFlavor
.stringFlavor
))
1218 } catch (Exception e
) {
1219 // No data will be handled
1222 if (clipboard
== null || !clipboard
.startsWith("http")) {
1226 url
= JOptionPane
.showInputDialog(GuiReaderFrame
.this,
1227 "url of the story to import?", "Importing from URL",
1228 JOptionPane
.QUESTION_MESSAGE
, null, null, clipboard
);
1229 } else if (fc
.showOpenDialog(this) != JFileChooser
.CANCEL_OPTION
) {
1230 url
= fc
.getSelectedFile().getAbsolutePath();
1235 if (url
!= null && !url
.toString().isEmpty()) {
1236 imprt(url
.toString(), null, null);
1241 * Actually import the {@link Story} into the main {@link LocalLibrary}.
1243 * Should be called inside the UI thread.
1246 * the {@link Story} to import by {@link URL}
1248 * Action to execute on success
1250 private void imprt(final String url
, final StoryRunnable onSuccess
,
1251 String onSuccessPgName
) {
1252 final Progress pg
= new Progress();
1253 final Progress pgImprt
= new Progress();
1254 final Progress pgOnSuccess
= new Progress(onSuccessPgName
);
1255 pg
.addProgress(pgImprt
, 95);
1256 pg
.addProgress(pgOnSuccess
, 5);
1258 outOfUi(pg
, new Runnable() {
1261 Exception ex
= null;
1264 story
= reader
.getLibrary().imprt(BasicReader
.getUrl(url
),
1266 } catch (IOException e
) {
1270 final Exception e
= ex
;
1272 final boolean ok
= (e
== null);
1274 pgOnSuccess
.setProgress(0);
1276 if (e
instanceof UnknownHostException
) {
1277 error("URL not supported: " + url
, "Cannot import URL",
1280 error("Failed to import " + url
+ ": \n"
1281 + e
.getMessage(), "Cannot import URL", e
);
1284 if (onSuccess
!= null) {
1285 onSuccess
.run(story
);
1294 * Enables or disables this component, depending on the value of the
1295 * parameter <code>b</code>. An enabled component can respond to user input
1296 * and generate events. Components are enabled initially by default.
1298 * Disabling this component will also affect its children.
1301 * If <code>true</code>, this component is enabled; otherwise
1302 * this component is disabled
1305 public void setEnabled(boolean b
) {
1310 for (GuiReaderGroup group
: booksByType
.keySet()) {
1311 group
.setEnabled(b
);
1313 for (GuiReaderGroup group
: booksByAuthor
.keySet()) {
1314 group
.setEnabled(b
);
1316 super.setEnabled(b
);
1321 * Display an error message and log the linked {@link Exception}.
1326 * the title of the error message
1328 * the exception to log if any
1330 private void error(final String message
, final String title
, Exception e
) {
1331 Instance
.getTraceHandler().error(title
+ ": " + message
);
1333 Instance
.getTraceHandler().error(e
);
1336 SwingUtilities
.invokeLater(new Runnable() {
1339 JOptionPane
.showMessageDialog(GuiReaderFrame
.this, message
,
1340 title
, JOptionPane
.ERROR_MESSAGE
);