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
.HashMap
;
20 import java
.util
.List
;
22 import java
.util
.Map
.Entry
;
24 import javax
.swing
.BorderFactory
;
25 import javax
.swing
.BoxLayout
;
26 import javax
.swing
.ImageIcon
;
27 import javax
.swing
.JFileChooser
;
28 import javax
.swing
.JFrame
;
29 import javax
.swing
.JLabel
;
30 import javax
.swing
.JMenu
;
31 import javax
.swing
.JMenuBar
;
32 import javax
.swing
.JMenuItem
;
33 import javax
.swing
.JOptionPane
;
34 import javax
.swing
.JPanel
;
35 import javax
.swing
.JPopupMenu
;
36 import javax
.swing
.JScrollPane
;
37 import javax
.swing
.JTextArea
;
38 import javax
.swing
.SwingConstants
;
39 import javax
.swing
.SwingUtilities
;
40 import javax
.swing
.filechooser
.FileFilter
;
41 import javax
.swing
.filechooser
.FileNameExtensionFilter
;
43 import be
.nikiroo
.fanfix
.Instance
;
44 import be
.nikiroo
.fanfix
.bundles
.Config
;
45 import be
.nikiroo
.fanfix
.bundles
.UiConfig
;
46 import be
.nikiroo
.fanfix
.data
.MetaData
;
47 import be
.nikiroo
.fanfix
.data
.Story
;
48 import be
.nikiroo
.fanfix
.library
.BasicLibrary
;
49 import be
.nikiroo
.fanfix
.library
.BasicLibrary
.Status
;
50 import be
.nikiroo
.fanfix
.library
.LocalLibrary
;
51 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
52 import be
.nikiroo
.fanfix
.reader
.BasicReader
;
53 import be
.nikiroo
.fanfix
.reader
.ui
.GuiReaderBook
.BookActionListener
;
54 import be
.nikiroo
.utils
.Progress
;
55 import be
.nikiroo
.utils
.Version
;
56 import be
.nikiroo
.utils
.ui
.ConfigEditor
;
57 import be
.nikiroo
.utils
.ui
.ProgressBar
;
60 * A {@link Frame} that will show a {@link GuiReaderBook} item for each
61 * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
62 * way to copy them to the {@link GuiReader} cache (
63 * {@link BasicReader#getLibrary()}), read them, delete them...
67 class GuiReaderFrame
extends JFrame
{
68 private static final long serialVersionUID
= 1L;
69 private GuiReader reader
;
70 private Map
<GuiReaderGroup
, String
> booksByType
;
71 private Map
<GuiReaderGroup
, String
> booksByAuthor
;
74 private ProgressBar pgBar
;
76 private GuiReaderBook selectedBook
;
77 private boolean words
; // words or authors (secondary info on books)
80 * A {@link Runnable} with a {@link Story} parameter.
84 private interface StoryRunnable
{
91 public void run(Story story
);
95 * Create a new {@link GuiReaderFrame}.
98 * the associated {@link GuiReader} to forward some commands and
99 * access its {@link LocalLibrary}
101 * the type of {@link Story} to load, or NULL for all types
103 public GuiReaderFrame(GuiReader reader
, String type
) {
104 super(String
.format("Fanfix %s Library", Version
.getCurrentVersion()));
106 this.reader
= reader
;
108 setDefaultCloseOperation(JFrame
.EXIT_ON_CLOSE
);
110 setLayout(new BorderLayout());
113 pane
.setLayout(new BoxLayout(pane
, BoxLayout
.PAGE_AXIS
));
115 Integer icolor
= Instance
.getUiConfig().getColor(
116 UiConfig
.BACKGROUND_COLOR
);
117 if (icolor
!= null) {
118 color
= new Color(icolor
);
119 setBackground(color
);
120 pane
.setBackground(color
);
123 JScrollPane scroll
= new JScrollPane(pane
);
124 scroll
.getVerticalScrollBar().setUnitIncrement(16);
125 add(scroll
, BorderLayout
.CENTER
);
127 String message
= reader
.getLibrary().getLibraryName();
128 if (!message
.isEmpty()) {
129 JLabel name
= new JLabel(message
, SwingConstants
.CENTER
);
130 add(name
, BorderLayout
.NORTH
);
133 pgBar
= new ProgressBar();
134 add(pgBar
, BorderLayout
.SOUTH
);
136 pgBar
.addActionListener(new ActionListener() {
138 public void actionPerformed(ActionEvent e
) {
140 pgBar
.setProgress(null);
146 pgBar
.addUpdateListener(new ActionListener() {
148 public void actionPerformed(ActionEvent e
) {
155 booksByType
= new HashMap
<GuiReaderGroup
, String
>();
156 booksByAuthor
= new HashMap
<GuiReaderGroup
, String
>();
158 pane
.setVisible(false);
159 final Progress pg
= new Progress();
160 final String typeF
= type
;
161 outOfUi(pg
, new Runnable() {
164 BasicLibrary lib
= GuiReaderFrame
.this.reader
.getLibrary();
165 Status status
= lib
.getStatus();
167 if (status
== Status
.READY
) {
170 setJMenuBar(createMenu(true));
172 addBookPane(true, false);
174 addBookPane(typeF
, true);
178 pane
.setVisible(true);
181 setJMenuBar(createMenu(false));
184 String err
= lib
.getLibraryName() + "\n";
187 err
+= "Library not valid";
191 err
+= "You are not allowed to access this library";
195 err
+= "Library currently unavailable";
199 err
+= "An error occured when contacting the library";
203 error(err
, "Library error", null);
211 private void addListPane(String name
, List
<String
> values
,
212 final boolean type
) {
214 GuiReaderGroup bookPane
= new GuiReaderGroup(reader
, name
, color
);
216 List
<MetaData
> metas
= new ArrayList
<MetaData
>();
217 for (String source
: values
) {
218 MetaData mSource
= new MetaData();
219 mSource
.setLuid(null);
220 mSource
.setTitle(source
);
221 mSource
.setSource(source
);
225 bookPane
.refreshBooks(metas
, false);
233 bookPane
.setActionListener(new BookActionListener() {
235 public void select(GuiReaderBook book
) {
240 public void popupRequested(GuiReaderBook book
, MouseEvent e
) {
241 JPopupMenu popup
= new JPopupMenu();
242 popup
.add(createMenuItemOpenBook());
243 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
247 public void action(final GuiReaderBook book
) {
249 addBookPane(book
.getMeta().getSource(), type
);
256 * Add a new {@link GuiReaderGroup} on the frame to display all the
257 * sources/types or all the authors, or a listing of all the books sorted
258 * either by source or author.
260 * A display of all the sources/types or all the authors will show one icon
261 * per source/type or author.
263 * A listing of all the books sorted by source/type or author will display
267 * TRUE for type/source, FALSE for author
269 * TRUE to get a listing of all the sources or authors, FALSE to
270 * get one icon per source or author
272 private void addBookPane(boolean type
, boolean listMode
) {
275 addListPane("Sources", reader
.getLibrary().getSources(), type
);
277 for (String tt
: reader
.getLibrary().getSources()) {
279 addBookPane(tt
, type
);
285 addListPane("Authors", reader
.getLibrary().getAuthors(), type
);
287 for (String tt
: reader
.getLibrary().getAuthors()) {
289 addBookPane(tt
, type
);
297 * Add a new {@link GuiReaderGroup} on the frame to display the books of the
298 * selected type or author.
301 * the author or the type, or NULL to get all the
304 * TRUE for type/source, FALSE for author
307 private void addBookPane(String value
, boolean type
) {
308 GuiReaderGroup bookPane
= new GuiReaderGroup(reader
, value
, color
);
310 booksByType
.put(bookPane
, value
);
312 booksByAuthor
.put(bookPane
, value
);
321 bookPane
.setActionListener(new BookActionListener() {
323 public void select(GuiReaderBook book
) {
328 public void popupRequested(GuiReaderBook book
, MouseEvent e
) {
329 JPopupMenu popup
= new JPopupMenu();
330 popup
.add(createMenuItemOpenBook());
331 popup
.addSeparator();
332 popup
.add(createMenuItemExport());
333 popup
.add(createMenuItemMoveTo(true));
334 popup
.add(createMenuItemSetCover());
335 popup
.add(createMenuItemClearCache());
336 popup
.add(createMenuItemRedownload());
337 popup
.addSeparator();
338 popup
.add(createMenuItemRename(true));
339 popup
.add(createMenuItemSetAuthor(true));
340 popup
.addSeparator();
341 popup
.add(createMenuItemDelete());
342 popup
.addSeparator();
343 popup
.add(createMenuItemProperties());
344 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
348 public void action(final GuiReaderBook book
) {
355 * Clear the pane from any book that may be present, usually prior to adding
358 private void removeBookPanes() {
360 booksByAuthor
.clear();
369 * Refresh the list of {@link GuiReaderBook}s from disk.
371 private void refreshBooks() {
372 for (GuiReaderGroup group
: booksByType
.keySet()) {
373 List
<MetaData
> stories
= reader
.getLibrary().getListBySource(
374 booksByType
.get(group
));
375 group
.refreshBooks(stories
, words
);
378 for (GuiReaderGroup group
: booksByAuthor
.keySet()) {
379 List
<MetaData
> stories
= reader
.getLibrary().getListByAuthor(
380 booksByAuthor
.get(group
));
381 group
.refreshBooks(stories
, words
);
389 * Create the main menu bar.
392 * the library can be queried
396 private JMenuBar
createMenu(boolean libOk
) {
397 bar
= new JMenuBar();
399 JMenu file
= new JMenu("File");
400 file
.setMnemonic(KeyEvent
.VK_F
);
402 JMenuItem imprt
= new JMenuItem("Import URL...", KeyEvent
.VK_U
);
403 imprt
.addActionListener(new ActionListener() {
405 public void actionPerformed(ActionEvent e
) {
409 JMenuItem imprtF
= new JMenuItem("Import File...", KeyEvent
.VK_F
);
410 imprtF
.addActionListener(new ActionListener() {
412 public void actionPerformed(ActionEvent e
) {
416 JMenuItem exit
= new JMenuItem("Exit", KeyEvent
.VK_X
);
417 exit
.addActionListener(new ActionListener() {
419 public void actionPerformed(ActionEvent e
) {
420 GuiReaderFrame
.this.dispatchEvent(new WindowEvent(
421 GuiReaderFrame
.this, WindowEvent
.WINDOW_CLOSING
));
425 file
.add(createMenuItemOpenBook());
426 file
.add(createMenuItemExport());
427 file
.add(createMenuItemMoveTo(libOk
));
432 file
.add(createMenuItemRename(libOk
));
433 file
.add(createMenuItemSetAuthor(libOk
));
439 JMenu edit
= new JMenu("Edit");
440 edit
.setMnemonic(KeyEvent
.VK_E
);
442 edit
.add(createMenuItemClearCache());
443 edit
.add(createMenuItemRedownload());
445 edit
.add(createMenuItemDelete());
449 JMenu view
= new JMenu("View");
450 view
.setMnemonic(KeyEvent
.VK_V
);
451 JMenuItem vauthors
= new JMenuItem("Author");
452 vauthors
.setMnemonic(KeyEvent
.VK_A
);
453 vauthors
.addActionListener(new ActionListener() {
455 public void actionPerformed(ActionEvent e
) {
461 JMenuItem vwords
= new JMenuItem("Word count");
462 vwords
.setMnemonic(KeyEvent
.VK_W
);
463 vwords
.addActionListener(new ActionListener() {
465 public void actionPerformed(ActionEvent e
) {
473 Map
<String
, List
<String
>> groupedSources
= new HashMap
<String
, List
<String
>>();
475 groupedSources
= reader
.getLibrary().getSourcesGrouped();
477 JMenu sources
= new JMenu("Sources");
478 sources
.setMnemonic(KeyEvent
.VK_S
);
479 populateMenuSA(sources
, groupedSources
, true);
482 Map
<String
, List
<String
>> goupedAuthors
= new HashMap
<String
, List
<String
>>();
484 goupedAuthors
= reader
.getLibrary().getAuthorsGrouped();
486 JMenu authors
= new JMenu("Authors");
487 authors
.setMnemonic(KeyEvent
.VK_A
);
488 populateMenuSA(authors
, goupedAuthors
, false);
491 JMenu options
= new JMenu("Options");
492 options
.setMnemonic(KeyEvent
.VK_O
);
493 options
.add(createMenuItemConfig());
494 options
.add(createMenuItemUiConfig());
501 private void populateMenuSA(JMenu menu
,
502 Map
<String
, List
<String
>> groupedValues
, boolean type
) {
504 // "All" and "Listing" special items
505 JMenuItem item
= new JMenuItem("All");
506 item
.addActionListener(getActionOpenList(type
, false));
508 item
= new JMenuItem("Listing");
509 item
.addActionListener(getActionOpenList(type
, true));
513 for (final String value
: groupedValues
.keySet()) {
514 List
<String
> list
= groupedValues
.get(value
);
515 if (type
&& list
.size() == 1 && list
.get(0).isEmpty()) {
516 // leaf item source/type
517 item
= new JMenuItem(value
.isEmpty() ?
"[unknown]" : value
);
518 item
.addActionListener(getActionOpen(value
, type
));
522 if (!type
&& groupedValues
.size() == 1) {
523 // only one group of authors
526 dir
= new JMenu(value
.isEmpty() ?
"[unknown]" : value
);
529 for (String sub
: list
) {
530 // " " instead of "" for the visual height
531 String itemName
= sub
.isEmpty() ?
" " : sub
;
532 String actualValue
= value
;
535 if (!sub
.isEmpty()) {
536 actualValue
+= "/" + sub
;
542 item
= new JMenuItem(itemName
);
543 item
.addActionListener(getActionOpen(actualValue
, type
));
555 * Return an {@link ActionListener} that will set the given source (type) as
556 * the selected/displayed one.
559 * the type (source) to select, cannot be NULL
561 * @return the {@link ActionListener}
563 private ActionListener
getActionOpen(final String source
, final boolean type
) {
564 return new ActionListener() {
566 public void actionPerformed(ActionEvent e
) {
568 addBookPane(source
, type
);
574 private ActionListener
getActionOpenList(final boolean type
,
575 final boolean listMode
) {
576 return new ActionListener() {
578 public void actionPerformed(ActionEvent e
) {
580 addBookPane(type
, listMode
);
587 * Create the Fanfix Configuration menu item.
591 private JMenuItem
createMenuItemConfig() {
592 final String title
= "Fanfix Configuration";
593 JMenuItem item
= new JMenuItem(title
);
594 item
.setMnemonic(KeyEvent
.VK_F
);
596 item
.addActionListener(new ActionListener() {
598 public void actionPerformed(ActionEvent e
) {
599 ConfigEditor
<Config
> ed
= new ConfigEditor
<Config
>(
600 Config
.class, Instance
.getConfig(),
601 "This is where you configure the options of the program.");
602 JFrame frame
= new JFrame(title
);
604 frame
.setSize(800, 600);
605 frame
.setVisible(true);
613 * Create the UI Configuration menu item.
617 private JMenuItem
createMenuItemUiConfig() {
618 final String title
= "UI Configuration";
619 JMenuItem item
= new JMenuItem(title
);
620 item
.setMnemonic(KeyEvent
.VK_U
);
622 item
.addActionListener(new ActionListener() {
624 public void actionPerformed(ActionEvent e
) {
625 ConfigEditor
<UiConfig
> ed
= new ConfigEditor
<UiConfig
>(
626 UiConfig
.class, Instance
.getUiConfig(),
627 "This is where you configure the graphical appearence of the program.");
628 JFrame frame
= new JFrame(title
);
630 frame
.setSize(800, 600);
631 frame
.setVisible(true);
639 * Create the export menu item.
643 private JMenuItem
createMenuItemExport() {
644 final JFileChooser fc
= new JFileChooser();
645 fc
.setAcceptAllFileFilterUsed(false);
647 final Map
<FileFilter
, OutputType
> filters
= new HashMap
<FileFilter
, OutputType
>();
648 for (OutputType type
: OutputType
.values()) {
649 String ext
= type
.getDefaultExtension(false);
650 String desc
= type
.getDesc(false);
652 if (ext
== null || ext
.isEmpty()) {
653 filters
.put(createAllFilter(desc
), type
);
655 filters
.put(new FileNameExtensionFilter(desc
, ext
), type
);
659 // First the "ALL" filters, then, the extension filters
660 for (Entry
<FileFilter
, OutputType
> entry
: filters
.entrySet()) {
661 if (!(entry
.getKey() instanceof FileNameExtensionFilter
)) {
662 fc
.addChoosableFileFilter(entry
.getKey());
665 for (Entry
<FileFilter
, OutputType
> entry
: filters
.entrySet()) {
666 if (entry
.getKey() instanceof FileNameExtensionFilter
) {
667 fc
.addChoosableFileFilter(entry
.getKey());
672 JMenuItem export
= new JMenuItem("Save as...", KeyEvent
.VK_S
);
673 export
.addActionListener(new ActionListener() {
675 public void actionPerformed(ActionEvent e
) {
676 if (selectedBook
!= null) {
677 fc
.showDialog(GuiReaderFrame
.this, "Save");
678 if (fc
.getSelectedFile() != null) {
679 final OutputType type
= filters
.get(fc
.getFileFilter());
680 final String path
= fc
.getSelectedFile()
682 + type
.getDefaultExtension(false);
683 final Progress pg
= new Progress();
684 outOfUi(pg
, new Runnable() {
688 reader
.getLibrary().export(
689 selectedBook
.getMeta().getLuid(),
691 } catch (IOException e
) {
692 Instance
.getTraceHandler().error(e
);
705 * Create a {@link FileFilter} that accepts all files and return the given
713 private FileFilter
createAllFilter(final String desc
) {
714 return new FileFilter() {
716 public String
getDescription() {
721 public boolean accept(File f
) {
728 * Create the refresh (delete cache) menu item.
732 private JMenuItem
createMenuItemClearCache() {
733 JMenuItem refresh
= new JMenuItem("Clear cache", KeyEvent
.VK_C
);
734 refresh
.addActionListener(new ActionListener() {
736 public void actionPerformed(ActionEvent e
) {
737 if (selectedBook
!= null) {
738 outOfUi(null, new Runnable() {
741 reader
.clearLocalReaderCache(selectedBook
.getMeta()
743 selectedBook
.setCached(false);
744 GuiReaderCoverImager
.clearIcon(selectedBook
746 SwingUtilities
.invokeLater(new Runnable() {
749 selectedBook
.repaint();
762 * Create the "move to" menu item.
765 * the library can be queried
769 private JMenuItem
createMenuItemMoveTo(boolean libOk
) {
770 JMenu changeTo
= new JMenu("Move to");
771 changeTo
.setMnemonic(KeyEvent
.VK_M
);
773 Map
<String
, List
<String
>> groupedSources
= new HashMap
<String
, List
<String
>>();
775 groupedSources
= reader
.getLibrary().getSourcesGrouped();
778 JMenuItem item
= new JMenuItem("New type...");
779 item
.addActionListener(createMoveAction("SOURCE", null));
781 changeTo
.addSeparator();
783 for (final String type
: groupedSources
.keySet()) {
784 List
<String
> list
= groupedSources
.get(type
);
785 if (list
.size() == 1 && list
.get(0).isEmpty()) {
786 item
= new JMenuItem(type
);
787 item
.addActionListener(createMoveAction("SOURCE", type
));
790 JMenu dir
= new JMenu(type
);
791 for (String sub
: list
) {
792 // " " instead of "" for the visual height
793 String itemName
= sub
.isEmpty() ?
" " : sub
;
794 String actualType
= type
;
795 if (!sub
.isEmpty()) {
796 actualType
+= "/" + sub
;
799 item
= new JMenuItem(itemName
);
800 item
.addActionListener(createMoveAction("SOURCE",
812 * Create the "set author" menu item.
815 * the library can be queried
819 private JMenuItem
createMenuItemSetAuthor(boolean libOk
) {
820 JMenu changeTo
= new JMenu("Set author");
821 changeTo
.setMnemonic(KeyEvent
.VK_A
);
824 JMenuItem newItem
= new JMenuItem("New author...");
825 changeTo
.add(newItem
);
826 changeTo
.addSeparator();
827 newItem
.addActionListener(createMoveAction("AUTHOR", null));
831 Map
<String
, List
<String
>> groupedAuthors
= reader
.getLibrary()
832 .getAuthorsGrouped();
834 if (groupedAuthors
.size() > 1) {
835 for (String key
: groupedAuthors
.keySet()) {
836 JMenu group
= new JMenu(key
);
837 for (String value
: groupedAuthors
.get(key
)) {
838 JMenuItem item
= new JMenuItem(value
);
839 item
.addActionListener(createMoveAction("AUTHOR", value
));
844 } else if (groupedAuthors
.size() == 1) {
845 for (String value
: groupedAuthors
.values().iterator().next()) {
846 JMenuItem item
= new JMenuItem(value
);
847 item
.addActionListener(createMoveAction("AUTHOR", value
));
857 * Create the "rename" menu item.
860 * the library can be queried
864 private JMenuItem
createMenuItemRename(
865 @SuppressWarnings("unused") boolean libOk
) {
866 JMenuItem changeTo
= new JMenuItem("Rename...");
867 changeTo
.setMnemonic(KeyEvent
.VK_R
);
868 changeTo
.addActionListener(createMoveAction("TITLE", null));
872 private ActionListener
createMoveAction(final String what
, final String type
) {
873 return new ActionListener() {
875 public void actionPerformed(ActionEvent e
) {
876 if (selectedBook
!= null) {
877 String changeTo
= type
;
880 if (what
.equals("SOURCE")) {
881 init
= selectedBook
.getMeta().getSource();
882 } else if (what
.equals("TITLE")) {
883 init
= selectedBook
.getMeta().getTitle();
884 } else if (what
.equals("AUTHOR")) {
885 init
= selectedBook
.getMeta().getAuthor();
888 Object rep
= JOptionPane
.showInputDialog(
889 GuiReaderFrame
.this, "Move to:",
890 "Moving story", JOptionPane
.QUESTION_MESSAGE
,
897 changeTo
= rep
.toString();
900 final String fChangeTo
= changeTo
;
901 outOfUi(null, new Runnable() {
904 if (what
.equals("SOURCE")) {
905 reader
.changeSource(selectedBook
.getMeta()
906 .getLuid(), fChangeTo
);
907 } else if (what
.equals("TITLE")) {
908 reader
.changeTitle(selectedBook
.getMeta()
909 .getLuid(), fChangeTo
);
910 } else if (what
.equals("AUTHOR")) {
911 reader
.changeAuthor(selectedBook
.getMeta()
912 .getLuid(), fChangeTo
);
917 SwingUtilities
.invokeLater(new Runnable() {
920 setJMenuBar(createMenu(true));
931 * Create the redownload (then delete original) menu item.
935 private JMenuItem
createMenuItemRedownload() {
936 JMenuItem refresh
= new JMenuItem("Redownload", KeyEvent
.VK_R
);
937 refresh
.addActionListener(new ActionListener() {
939 public void actionPerformed(ActionEvent e
) {
940 if (selectedBook
!= null) {
941 final MetaData meta
= selectedBook
.getMeta();
942 imprt(meta
.getUrl(), new StoryRunnable() {
944 public void run(Story story
) {
945 reader
.delete(meta
.getLuid());
946 GuiReaderFrame
.this.selectedBook
= null;
947 MetaData newMeta
= story
.getMeta();
948 if (!newMeta
.getSource().equals(meta
.getSource())) {
949 reader
.changeSource(newMeta
.getLuid(),
953 }, "Removing old copy");
962 * Create the delete menu item.
966 private JMenuItem
createMenuItemDelete() {
967 JMenuItem delete
= new JMenuItem("Delete", KeyEvent
.VK_D
);
968 delete
.addActionListener(new ActionListener() {
970 public void actionPerformed(ActionEvent e
) {
971 if (selectedBook
!= null) {
972 outOfUi(null, new Runnable() {
975 reader
.delete(selectedBook
.getMeta().getLuid());
987 * Create the properties menu item.
991 private JMenuItem
createMenuItemProperties() {
992 JMenuItem delete
= new JMenuItem("Properties", KeyEvent
.VK_P
);
993 delete
.addActionListener(new ActionListener() {
995 public void actionPerformed(ActionEvent e
) {
996 if (selectedBook
!= null) {
997 outOfUi(null, new Runnable() {
1000 final MetaData meta
= selectedBook
.getMeta();
1002 private static final long serialVersionUID
= 1L;
1003 @SuppressWarnings("unused")
1004 private Object init
= init();
1006 private Object
init() {
1012 ImageIcon img
= GuiReaderCoverImager
1014 reader
.getLibrary(), meta
);
1017 setTitle(meta
.getLuid() + ": "
1020 setSize(800, img
.getIconHeight() + 2 * top
);
1021 setLayout(new BorderLayout());
1024 JPanel mainPanel
= new JPanel(
1025 new BorderLayout());
1026 JPanel mainPanelKeys
= new JPanel();
1027 mainPanelKeys
.setLayout(new BoxLayout(
1028 mainPanelKeys
, BoxLayout
.Y_AXIS
));
1029 JPanel mainPanelValues
= new JPanel();
1030 mainPanelValues
.setLayout(new BoxLayout(
1031 mainPanelValues
, BoxLayout
.Y_AXIS
));
1033 mainPanel
.add(mainPanelKeys
,
1035 mainPanel
.add(mainPanelValues
,
1036 BorderLayout
.CENTER
);
1038 List
<Entry
<String
, String
>> infos
= BasicReader
1041 Color trans
= new Color(0, 0, 0, 1);
1042 for (Entry
<String
, String
> info
: infos
) {
1043 JTextArea key
= new JTextArea(info
1045 key
.setFont(new Font(key
.getFont()
1046 .getFontName(), Font
.BOLD
, key
1047 .getFont().getSize()));
1048 key
.setEditable(false);
1049 key
.setLineWrap(false);
1050 key
.setBackground(trans
);
1051 mainPanelKeys
.add(key
);
1053 JTextArea value
= new JTextArea(info
1055 value
.setEditable(false);
1056 value
.setLineWrap(false);
1057 value
.setBackground(trans
);
1058 mainPanelValues
.add(value
);
1062 JLabel imgLabel
= new JLabel(img
);
1063 imgLabel
.setVerticalAlignment(JLabel
.TOP
);
1066 mainPanelKeys
.setBorder(BorderFactory
1067 .createEmptyBorder(top
, space
, 0, 0));
1068 mainPanelValues
.setBorder(BorderFactory
1069 .createEmptyBorder(top
, space
, 0, 0));
1070 imgLabel
.setBorder(BorderFactory
1071 .createEmptyBorder(0, space
, 0, 0));
1074 add(imgLabel
, BorderLayout
.WEST
);
1075 add(mainPanel
, BorderLayout
.CENTER
);
1091 * Create the open menu item for a book or a source (no LUID).
1095 private JMenuItem
createMenuItemOpenBook() {
1096 JMenuItem open
= new JMenuItem("Open", KeyEvent
.VK_O
);
1097 open
.addActionListener(new ActionListener() {
1099 public void actionPerformed(ActionEvent e
) {
1100 if (selectedBook
!= null) {
1101 if (selectedBook
.getMeta().getLuid() == null) {
1103 addBookPane(selectedBook
.getMeta().getSource(), true);
1106 openBook(selectedBook
);
1116 * Create the SetCover menu item for a book to change the linked source
1121 private JMenuItem
createMenuItemSetCover() {
1122 JMenuItem open
= new JMenuItem("Set as cover for source", KeyEvent
.VK_C
);
1123 open
.addActionListener(new ActionListener() {
1125 public void actionPerformed(ActionEvent e
) {
1126 if (selectedBook
!= null) {
1127 reader
.getLibrary().setSourceCover(
1128 selectedBook
.getMeta().getSource(),
1129 selectedBook
.getMeta().getLuid());
1130 MetaData source
= selectedBook
.getMeta().clone();
1131 source
.setLuid(null);
1132 GuiReaderCoverImager
.clearIcon(source
);
1141 * Open a {@link GuiReaderBook} item.
1144 * the {@link GuiReaderBook} to open
1146 private void openBook(final GuiReaderBook book
) {
1147 final Progress pg
= new Progress();
1148 outOfUi(pg
, new Runnable() {
1152 reader
.read(book
.getMeta().getLuid(), false, pg
);
1153 SwingUtilities
.invokeLater(new Runnable() {
1156 book
.setCached(true);
1159 } catch (IOException e
) {
1160 // TODO: error message?
1161 Instance
.getTraceHandler().error(e
);
1168 * Process the given action out of the Swing UI thread and link the given
1169 * {@link ProgressBar} to the action.
1171 * The code will make sure that the {@link ProgressBar} (if not NULL) is set
1172 * to done when the action is done.
1175 * the {@link ProgressBar} or NULL
1179 private void outOfUi(Progress progress
, final Runnable run
) {
1180 final Progress pg
= new Progress();
1181 final Progress reload
= new Progress("Reload books");
1182 if (progress
== null) {
1183 progress
= new Progress();
1186 pg
.addProgress(progress
, 90);
1187 pg
.addProgress(reload
, 10);
1190 pgBar
.setProgress(pg
);
1194 new Thread(new Runnable() {
1203 // will trigger pgBar ActionListener:
1208 }, "outOfUi thread").start();
1212 * Import a {@link Story} into the main {@link LocalLibrary}.
1214 * Should be called inside the UI thread.
1217 * TRUE for an {@link URL}, false for a {@link File}
1219 private void imprt(boolean askUrl
) {
1220 JFileChooser fc
= new JFileChooser();
1224 String clipboard
= "";
1226 clipboard
= ("" + Toolkit
.getDefaultToolkit()
1227 .getSystemClipboard().getData(DataFlavor
.stringFlavor
))
1229 } catch (Exception e
) {
1230 // No data will be handled
1233 if (clipboard
== null || !clipboard
.startsWith("http")) {
1237 url
= JOptionPane
.showInputDialog(GuiReaderFrame
.this,
1238 "url of the story to import?", "Importing from URL",
1239 JOptionPane
.QUESTION_MESSAGE
, null, null, clipboard
);
1240 } else if (fc
.showOpenDialog(this) != JFileChooser
.CANCEL_OPTION
) {
1241 url
= fc
.getSelectedFile().getAbsolutePath();
1246 if (url
!= null && !url
.toString().isEmpty()) {
1247 imprt(url
.toString(), null, null);
1252 * Actually import the {@link Story} into the main {@link LocalLibrary}.
1254 * Should be called inside the UI thread.
1257 * the {@link Story} to import by {@link URL}
1259 * Action to execute on success
1261 private void imprt(final String url
, final StoryRunnable onSuccess
,
1262 String onSuccessPgName
) {
1263 final Progress pg
= new Progress();
1264 final Progress pgImprt
= new Progress();
1265 final Progress pgOnSuccess
= new Progress(onSuccessPgName
);
1266 pg
.addProgress(pgImprt
, 95);
1267 pg
.addProgress(pgOnSuccess
, 5);
1269 outOfUi(pg
, new Runnable() {
1272 Exception ex
= null;
1275 story
= reader
.getLibrary().imprt(BasicReader
.getUrl(url
),
1277 } catch (IOException e
) {
1281 final Exception e
= ex
;
1283 final boolean ok
= (e
== null);
1285 pgOnSuccess
.setProgress(0);
1287 if (e
instanceof UnknownHostException
) {
1288 error("URL not supported: " + url
, "Cannot import URL",
1291 error("Failed to import " + url
+ ": \n"
1292 + e
.getMessage(), "Cannot import URL", e
);
1295 if (onSuccess
!= null) {
1296 onSuccess
.run(story
);
1305 * Enables or disables this component, depending on the value of the
1306 * parameter <code>b</code>. An enabled component can respond to user input
1307 * and generate events. Components are enabled initially by default.
1309 * Disabling this component will also affect its children.
1312 * If <code>true</code>, this component is enabled; otherwise
1313 * this component is disabled
1316 public void setEnabled(boolean b
) {
1321 for (GuiReaderGroup group
: booksByType
.keySet()) {
1322 group
.setEnabled(b
);
1324 for (GuiReaderGroup group
: booksByAuthor
.keySet()) {
1325 group
.setEnabled(b
);
1327 super.setEnabled(b
);
1332 * Display an error message and log the linked {@link Exception}.
1337 * the title of the error message
1339 * the exception to log if any
1341 private void error(final String message
, final String title
, Exception e
) {
1342 Instance
.getTraceHandler().error(title
+ ": " + message
);
1344 Instance
.getTraceHandler().error(e
);
1347 SwingUtilities
.invokeLater(new Runnable() {
1350 JOptionPane
.showMessageDialog(GuiReaderFrame
.this, message
,
1351 title
, JOptionPane
.ERROR_MESSAGE
);