1 package be
.nikiroo
.fanfix
.reader
;
3 import java
.awt
.BorderLayout
;
6 import java
.awt
.Toolkit
;
7 import java
.awt
.datatransfer
.DataFlavor
;
8 import java
.awt
.event
.ActionEvent
;
9 import java
.awt
.event
.ActionListener
;
10 import java
.awt
.event
.KeyEvent
;
11 import java
.awt
.event
.MouseEvent
;
12 import java
.awt
.event
.WindowEvent
;
14 import java
.io
.IOException
;
16 import java
.util
.ArrayList
;
17 import java
.util
.HashMap
;
18 import java
.util
.List
;
20 import java
.util
.Map
.Entry
;
22 import javax
.swing
.JFileChooser
;
23 import javax
.swing
.JFrame
;
24 import javax
.swing
.JMenu
;
25 import javax
.swing
.JMenuBar
;
26 import javax
.swing
.JMenuItem
;
27 import javax
.swing
.JOptionPane
;
28 import javax
.swing
.JPanel
;
29 import javax
.swing
.JPopupMenu
;
30 import javax
.swing
.JScrollPane
;
31 import javax
.swing
.SwingUtilities
;
32 import javax
.swing
.filechooser
.FileFilter
;
33 import javax
.swing
.filechooser
.FileNameExtensionFilter
;
35 import be
.nikiroo
.fanfix
.Instance
;
36 import be
.nikiroo
.fanfix
.Library
;
37 import be
.nikiroo
.fanfix
.bundles
.UiConfig
;
38 import be
.nikiroo
.fanfix
.data
.MetaData
;
39 import be
.nikiroo
.fanfix
.data
.Story
;
40 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
41 import be
.nikiroo
.fanfix
.reader
.LocalReaderBook
.BookActionListener
;
42 import be
.nikiroo
.utils
.Progress
;
43 import be
.nikiroo
.utils
.Version
;
44 import be
.nikiroo
.utils
.ui
.ProgressBar
;
45 import be
.nikiroo
.utils
.ui
.WrapLayout
;
48 * A {@link Frame} that will show a {@link LocalReaderBook} item for each
49 * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
50 * way to copy them to the {@link LocalReader} cache ({@link LocalReader#lib}),
51 * read them, delete them...
55 class LocalReaderFrame
extends JFrame
{
56 private static final long serialVersionUID
= 1L;
57 private LocalReader reader
;
58 private List
<MetaData
> stories
;
59 private List
<LocalReaderBook
> books
;
60 private JPanel bookPane
;
63 private ProgressBar pgBar
;
65 private LocalReaderBook selectedBook
;
68 * Create a new {@link LocalReaderFrame}.
71 * the associated {@link LocalReader} to forward some commands
72 * and access its {@link Library}
74 * the type of {@link Story} to load, or NULL for all types
76 public LocalReaderFrame(LocalReader reader
, String type
) {
77 super(String
.format("Fanfix %s Library", Version
.getCurrentVersion()));
81 setDefaultCloseOperation(JFrame
.EXIT_ON_CLOSE
);
83 setLayout(new BorderLayout());
85 books
= new ArrayList
<LocalReaderBook
>();
86 bookPane
= new JPanel(new WrapLayout(WrapLayout
.LEADING
, 5, 5));
88 color
= Instance
.getUiConfig().getColor(UiConfig
.BACKGROUND_COLOR
);
92 bookPane
.setBackground(color
);
95 JScrollPane scroll
= new JScrollPane(bookPane
);
96 scroll
.getVerticalScrollBar().setUnitIncrement(16);
97 add(scroll
, BorderLayout
.CENTER
);
99 pgBar
= new ProgressBar();
100 add(pgBar
, BorderLayout
.SOUTH
);
103 setJMenuBar(createMenu());
109 * Refresh the list of {@link LocalReaderBook}s from disk.
112 * the type of {@link Story} to load, or NULL for all types
114 private void refreshBooks(String type
) {
116 stories
= Instance
.getLibrary().getList(type
);
118 bookPane
.invalidate();
119 bookPane
.removeAll();
120 for (MetaData meta
: stories
) {
121 LocalReaderBook book
= new LocalReaderBook(meta
,
122 reader
.isCached(meta
.getLuid()));
124 book
.setBackground(color
);
129 book
.addActionListener(new BookActionListener() {
130 public void select(LocalReaderBook book
) {
132 for (LocalReaderBook abook
: books
) {
133 abook
.setSelected(abook
== book
);
137 public void popupRequested(LocalReaderBook book
, MouseEvent e
) {
138 JPopupMenu popup
= new JPopupMenu();
139 popup
.add(createMenuItemOpenBook());
140 popup
.addSeparator();
141 popup
.add(createMenuItemExport());
142 popup
.add(createMenuItemClearCache());
143 popup
.add(createMenuItemRedownload());
144 popup
.addSeparator();
145 popup
.add(createMenuItemDelete());
146 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
149 public void action(final LocalReaderBook book
) {
162 * Create the main menu bar.
166 private JMenuBar
createMenu() {
167 bar
= new JMenuBar();
169 JMenu file
= new JMenu("File");
170 file
.setMnemonic(KeyEvent
.VK_F
);
172 JMenuItem imprt
= new JMenuItem("Import URL...", KeyEvent
.VK_U
);
173 imprt
.addActionListener(new ActionListener() {
174 public void actionPerformed(ActionEvent e
) {
178 JMenuItem imprtF
= new JMenuItem("Import File...", KeyEvent
.VK_F
);
179 imprtF
.addActionListener(new ActionListener() {
180 public void actionPerformed(ActionEvent e
) {
184 JMenuItem exit
= new JMenuItem("Exit", KeyEvent
.VK_X
);
185 exit
.addActionListener(new ActionListener() {
186 public void actionPerformed(ActionEvent e
) {
187 LocalReaderFrame
.this.dispatchEvent(new WindowEvent(
188 LocalReaderFrame
.this, WindowEvent
.WINDOW_CLOSING
));
192 file
.add(createMenuItemOpenBook());
193 file
.add(createMenuItemExport());
202 JMenu edit
= new JMenu("Edit");
203 edit
.setMnemonic(KeyEvent
.VK_E
);
205 edit
.add(createMenuItemClearCache());
206 edit
.add(createMenuItemRedownload());
208 edit
.add(createMenuItemDelete());
212 JMenu view
= new JMenu("View");
213 view
.setMnemonic(KeyEvent
.VK_V
);
215 List
<String
> tt
= Instance
.getLibrary().getTypes();
217 for (final String type
: tt
) {
218 JMenuItem item
= new JMenuItem(type
== null ?
"All books" : type
);
219 item
.addActionListener(new ActionListener() {
220 public void actionPerformed(ActionEvent e
) {
237 * Create the export menu item.
241 private JMenuItem
createMenuItemExport() {
242 final JFileChooser fc
= new JFileChooser();
243 fc
.setAcceptAllFileFilterUsed(false);
245 final Map
<FileFilter
, OutputType
> filters
= new HashMap
<FileFilter
, OutputType
>();
246 for (OutputType type
: OutputType
.values()) {
247 String ext
= type
.getDefaultExtension(false);
248 String desc
= type
.getDesc(false);
249 if (ext
== null || ext
.isEmpty()) {
250 filters
.put(createAllFilter(desc
), type
);
252 filters
.put(new FileNameExtensionFilter(desc
, ext
), type
);
256 // First the "ALL" filters, then, the extension filters
257 for (Entry
<FileFilter
, OutputType
> entry
: filters
.entrySet()) {
258 if (!(entry
.getKey() instanceof FileNameExtensionFilter
)) {
259 fc
.addChoosableFileFilter(entry
.getKey());
262 for (Entry
<FileFilter
, OutputType
> entry
: filters
.entrySet()) {
263 if (entry
.getKey() instanceof FileNameExtensionFilter
) {
264 fc
.addChoosableFileFilter(entry
.getKey());
269 JMenuItem export
= new JMenuItem("Save as...", KeyEvent
.VK_S
);
270 export
.addActionListener(new ActionListener() {
271 public void actionPerformed(ActionEvent e
) {
272 if (selectedBook
!= null) {
273 fc
.showDialog(LocalReaderFrame
.this, "Save");
274 final OutputType type
= filters
.get(fc
.getFileFilter());
275 final String path
= fc
.getSelectedFile().getAbsolutePath()
276 + type
.getDefaultExtension(false);
277 final Progress pg
= new Progress();
278 outOfUi(pg
, new Runnable() {
281 Instance
.getLibrary().export(
282 selectedBook
.getMeta().getLuid(), type
,
284 } catch (IOException e
) {
297 * Create a {@link FileFilter} that accepts all files and return the given
305 private FileFilter
createAllFilter(final String desc
) {
306 return new FileFilter() {
308 public String
getDescription() {
313 public boolean accept(File f
) {
320 * Create the refresh (delete cache) menu item.
324 private JMenuItem
createMenuItemClearCache() {
325 JMenuItem refresh
= new JMenuItem("Clear cache", KeyEvent
.VK_C
);
326 refresh
.addActionListener(new ActionListener() {
327 public void actionPerformed(ActionEvent e
) {
328 if (selectedBook
!= null) {
329 outOfUi(null, new Runnable() {
331 reader
.refresh(selectedBook
.getMeta().getLuid());
332 selectedBook
.setCached(false);
333 SwingUtilities
.invokeLater(new Runnable() {
335 selectedBook
.repaint();
348 * Create the redownload (then delete original) menu item.
352 private JMenuItem
createMenuItemRedownload() {
353 JMenuItem refresh
= new JMenuItem("Redownload", KeyEvent
.VK_R
);
354 refresh
.addActionListener(new ActionListener() {
355 public void actionPerformed(ActionEvent e
) {
356 if (selectedBook
!= null) {
357 imprt(selectedBook
.getMeta().getUrl(), new Runnable() {
359 reader
.delete(selectedBook
.getMeta().getLuid());
371 * Create the delete menu item.
375 private JMenuItem
createMenuItemDelete() {
376 JMenuItem delete
= new JMenuItem("Delete", KeyEvent
.VK_D
);
377 delete
.addActionListener(new ActionListener() {
378 public void actionPerformed(ActionEvent e
) {
379 if (selectedBook
!= null) {
380 outOfUi(null, new Runnable() {
382 reader
.delete(selectedBook
.getMeta().getLuid());
384 SwingUtilities
.invokeLater(new Runnable() {
399 * Create the open menu item.
403 private JMenuItem
createMenuItemOpenBook() {
404 JMenuItem open
= new JMenuItem("Open", KeyEvent
.VK_O
);
405 open
.addActionListener(new ActionListener() {
406 public void actionPerformed(ActionEvent e
) {
407 if (selectedBook
!= null) {
408 openBook(selectedBook
);
417 * Open a {@link LocalReaderBook} item.
420 * the {@link LocalReaderBook} to open
422 private void openBook(final LocalReaderBook book
) {
423 final Progress pg
= new Progress();
424 outOfUi(pg
, new Runnable() {
427 reader
.open(book
.getMeta().getLuid(), pg
);
428 SwingUtilities
.invokeLater(new Runnable() {
430 book
.setCached(true);
433 } catch (IOException e
) {
434 // TODO: error message?
442 * Process the given action out of the Swing UI thread and link the given
443 * {@link ProgressBar} to the action.
445 * The code will make sure that the {@link ProgressBar} (if not NULL) is set
446 * to done when the action is done.
449 * the {@link ProgressBar} or NULL
453 private void outOfUi(final Progress pg
, final Runnable run
) {
454 pgBar
.setProgress(pg
);
457 pgBar
.addActioListener(new ActionListener() {
458 public void actionPerformed(ActionEvent e
) {
459 pgBar
.setProgress(null);
464 new Thread(new Runnable() {
468 SwingUtilities
.invokeLater(new Runnable() {
473 } else if (!pg
.isDone()) {
474 pg
.setProgress(pg
.getMax());
481 * Import a {@link Story} into the main {@link Library}.
483 * Should be called inside the UI thread.
486 * TRUE for an {@link URL}, false for a {@link File}
488 private void imprt(boolean askUrl
) {
489 JFileChooser fc
= new JFileChooser();
493 String clipboard
= "";
495 clipboard
= ("" + Toolkit
.getDefaultToolkit()
496 .getSystemClipboard().getData(DataFlavor
.stringFlavor
))
498 } catch (Exception e
) {
499 // No data will be handled
502 if (clipboard
== null || !clipboard
.startsWith("http")) {
506 url
= JOptionPane
.showInputDialog(LocalReaderFrame
.this,
507 "url of the story to import?", "Importing from URL",
508 JOptionPane
.QUESTION_MESSAGE
, null, null, clipboard
);
509 } else if (fc
.showOpenDialog(this) != JFileChooser
.CANCEL_OPTION
) {
510 url
= fc
.getSelectedFile().getAbsolutePath();
515 if (url
!= null && !url
.toString().isEmpty()) {
516 imprt(url
.toString(), null);
521 * Actually import the {@link Story} into the main {@link Library}.
523 * Should be called inside the UI thread.
526 * the {@link Story} to import by {@link URL}
528 * Action to execute on success
530 private void imprt(final String url
, final Runnable onSuccess
) {
531 final Progress pg
= new Progress("Importing " + url
);
532 outOfUi(pg
, new Runnable() {
536 Instance
.getLibrary().imprt(BasicReader
.getUrl(url
), pg
);
537 } catch (IOException e
) {
541 final Exception e
= ex
;
543 final boolean ok
= (e
== null);
544 SwingUtilities
.invokeLater(new Runnable() {
548 JOptionPane
.showMessageDialog(
549 LocalReaderFrame
.this, "Cannot import: "
550 + url
, e
.getMessage(),
551 JOptionPane
.ERROR_MESSAGE
);
556 if (onSuccess
!= null) {
568 * Enables or disables this component, depending on the value of the
569 * parameter <code>b</code>. An enabled component can respond to user input
570 * and generate events. Components are enabled initially by default.
572 * Disabling this component will also affect its children.
575 * If <code>true</code>, this component is enabled; otherwise
576 * this component is disabled
579 public void setEnabled(boolean b
) {
580 for (LocalReaderBook book
: books
) {
586 bookPane
.setEnabled(b
);