1 package be
.nikiroo
.fanfix
.reader
;
3 import java
.awt
.BorderLayout
;
5 import java
.awt
.Desktop
;
7 import java
.awt
.event
.ActionEvent
;
8 import java
.awt
.event
.ActionListener
;
9 import java
.awt
.event
.KeyEvent
;
10 import java
.awt
.event
.MouseEvent
;
11 import java
.awt
.event
.WindowEvent
;
13 import java
.io
.IOException
;
15 import java
.util
.ArrayList
;
16 import java
.util
.HashMap
;
17 import java
.util
.List
;
19 import java
.util
.Map
.Entry
;
21 import javax
.swing
.JFileChooser
;
22 import javax
.swing
.JFrame
;
23 import javax
.swing
.JMenu
;
24 import javax
.swing
.JMenuBar
;
25 import javax
.swing
.JMenuItem
;
26 import javax
.swing
.JOptionPane
;
27 import javax
.swing
.JPanel
;
28 import javax
.swing
.JPopupMenu
;
29 import javax
.swing
.JScrollPane
;
30 import javax
.swing
.SwingUtilities
;
31 import javax
.swing
.filechooser
.FileFilter
;
32 import javax
.swing
.filechooser
.FileNameExtensionFilter
;
34 import be
.nikiroo
.fanfix
.Instance
;
35 import be
.nikiroo
.fanfix
.Library
;
36 import be
.nikiroo
.fanfix
.bundles
.UiConfig
;
37 import be
.nikiroo
.fanfix
.data
.MetaData
;
38 import be
.nikiroo
.fanfix
.data
.Story
;
39 import be
.nikiroo
.fanfix
.output
.BasicOutput
.OutputType
;
40 import be
.nikiroo
.fanfix
.reader
.LocalReaderBook
.BookActionListener
;
41 import be
.nikiroo
.utils
.Progress
;
42 import be
.nikiroo
.utils
.ui
.ProgressBar
;
43 import be
.nikiroo
.utils
.ui
.WrapLayout
;
46 * A {@link Frame} that will show a {@link LocalReaderBook} item for each
47 * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a
48 * way to copy them to the {@link LocalReader} cache ({@link LocalReader#lib}),
49 * read them, delete them...
53 class LocalReaderFrame
extends JFrame
{
54 private static final long serialVersionUID
= 1L;
55 private LocalReader reader
;
56 private List
<MetaData
> stories
;
57 private List
<LocalReaderBook
> books
;
58 private JPanel bookPane
;
61 private ProgressBar pgBar
;
63 private LocalReaderBook selectedBook
;
66 * Create a new {@link LocalReaderFrame}.
69 * the associated {@link LocalReader} to forward some commands
70 * and access its {@link Library}
72 * the type of {@link Story} to load, or NULL for all types
74 public LocalReaderFrame(LocalReader reader
, String type
) {
75 super("Fanfix Library");
79 setDefaultCloseOperation(JFrame
.EXIT_ON_CLOSE
);
81 setLayout(new BorderLayout());
83 books
= new ArrayList
<LocalReaderBook
>();
84 bookPane
= new JPanel(new WrapLayout(WrapLayout
.LEADING
, 5, 5));
86 color
= Instance
.getUiConfig().getColor(UiConfig
.BACKGROUND_COLOR
);
90 bookPane
.setBackground(color
);
93 JScrollPane scroll
= new JScrollPane(bookPane
);
94 scroll
.getVerticalScrollBar().setUnitIncrement(16);
95 add(scroll
, BorderLayout
.CENTER
);
97 pgBar
= new ProgressBar();
98 add(pgBar
, BorderLayout
.SOUTH
);
101 setJMenuBar(createMenu());
107 * Refresh the list of {@link LocalReaderBook}s from disk.
110 * the type of {@link Story} to load, or NULL for all types
112 private void refreshBooks(String type
) {
114 stories
= Instance
.getLibrary().getList(type
);
116 bookPane
.removeAll();
117 for (MetaData meta
: stories
) {
118 LocalReaderBook book
= new LocalReaderBook(meta
,
119 reader
.isCached(meta
.getLuid()));
121 book
.setBackground(color
);
126 book
.addActionListener(new BookActionListener() {
127 public void select(LocalReaderBook book
) {
129 for (LocalReaderBook abook
: books
) {
130 abook
.setSelected(abook
== book
);
134 public void popupRequested(LocalReaderBook book
, MouseEvent e
) {
135 JPopupMenu popup
= new JPopupMenu();
136 popup
.add(createMenuItemOpenBook());
137 popup
.addSeparator();
138 popup
.add(createMenuItemExport());
139 popup
.add(createMenuItemRefresh());
140 popup
.addSeparator();
141 popup
.add(createMenuItemDelete());
142 popup
.show(e
.getComponent(), e
.getX(), e
.getY());
145 public void action(final LocalReaderBook book
) {
158 * Create the main menu bar.
162 private JMenuBar
createMenu() {
163 bar
= new JMenuBar();
165 JMenu file
= new JMenu("File");
166 file
.setMnemonic(KeyEvent
.VK_F
);
168 JMenuItem imprt
= new JMenuItem("Import URL", KeyEvent
.VK_U
);
169 imprt
.addActionListener(new ActionListener() {
170 public void actionPerformed(ActionEvent e
) {
174 JMenuItem imprtF
= new JMenuItem("Import File", KeyEvent
.VK_F
);
175 imprtF
.addActionListener(new ActionListener() {
176 public void actionPerformed(ActionEvent e
) {
180 JMenuItem exit
= new JMenuItem("Exit", KeyEvent
.VK_X
);
181 exit
.addActionListener(new ActionListener() {
182 public void actionPerformed(ActionEvent e
) {
183 LocalReaderFrame
.this.dispatchEvent(new WindowEvent(
184 LocalReaderFrame
.this, WindowEvent
.WINDOW_CLOSING
));
188 file
.add(createMenuItemOpenBook());
189 file
.add(createMenuItemExport());
198 JMenu edit
= new JMenu("Edit");
199 edit
.setMnemonic(KeyEvent
.VK_E
);
201 edit
.add(createMenuItemRefresh());
203 edit
.add(createMenuItemDelete());
207 JMenu view
= new JMenu("View");
208 view
.setMnemonic(KeyEvent
.VK_V
);
210 List
<String
> tt
= Instance
.getLibrary().getTypes();
212 for (final String type
: tt
) {
214 JMenuItem item
= new JMenuItem(type
== null ?
"All books" : type
);
215 item
.addActionListener(new ActionListener() {
216 public void actionPerformed(ActionEvent e
) {
233 * Create the export menu item.
237 private JMenuItem
createMenuItemExport() {
238 final JFileChooser fc
= new JFileChooser();
239 fc
.setAcceptAllFileFilterUsed(false);
241 final Map
<FileFilter
, OutputType
> filters
= new HashMap
<FileFilter
, OutputType
>();
242 for (OutputType type
: OutputType
.values()) {
243 String ext
= type
.getDefaultExtension(false);
244 String desc
= type
.getDesc(false);
245 if (ext
== null || ext
.isEmpty()) {
246 filters
.put(createAllFilter(desc
), type
);
248 filters
.put(new FileNameExtensionFilter(desc
, ext
), type
);
252 // First the "ALL" filters, then, the extension filters
253 for (Entry
<FileFilter
, OutputType
> entry
: filters
.entrySet()) {
254 if (!(entry
.getKey() instanceof FileNameExtensionFilter
)) {
255 fc
.addChoosableFileFilter(entry
.getKey());
258 for (Entry
<FileFilter
, OutputType
> entry
: filters
.entrySet()) {
259 if (entry
.getKey() instanceof FileNameExtensionFilter
) {
260 fc
.addChoosableFileFilter(entry
.getKey());
265 JMenuItem export
= new JMenuItem("Save as...", KeyEvent
.VK_S
);
266 export
.addActionListener(new ActionListener() {
267 public void actionPerformed(ActionEvent e
) {
268 if (selectedBook
!= null) {
269 fc
.showDialog(LocalReaderFrame
.this, "Save");
270 final OutputType type
= filters
.get(fc
.getFileFilter());
271 final String path
= fc
.getSelectedFile().getAbsolutePath()
272 + type
.getDefaultExtension(false);
273 final Progress pg
= new Progress();
274 outOfUi(pg
, new Runnable() {
277 Instance
.getLibrary().export(
278 selectedBook
.getLuid(), type
, path
, pg
);
279 } catch (IOException e
) {
291 private FileFilter
createAllFilter(final String desc
) {
292 return new FileFilter() {
294 public String
getDescription() {
299 public boolean accept(File f
) {
306 * Create the refresh (delete cache) menu item.
310 private JMenuItem
createMenuItemRefresh() {
311 JMenuItem refresh
= new JMenuItem("Clear cache", KeyEvent
.VK_C
);
312 refresh
.addActionListener(new ActionListener() {
313 public void actionPerformed(ActionEvent e
) {
314 if (selectedBook
!= null) {
315 outOfUi(null, new Runnable() {
317 reader
.refresh(selectedBook
.getLuid());
318 selectedBook
.setCached(false);
319 SwingUtilities
.invokeLater(new Runnable() {
321 selectedBook
.repaint();
334 * Create the delete menu item.
338 private JMenuItem
createMenuItemDelete() {
339 JMenuItem delete
= new JMenuItem("Delete", KeyEvent
.VK_D
);
340 delete
.addActionListener(new ActionListener() {
341 public void actionPerformed(ActionEvent e
) {
342 if (selectedBook
!= null) {
343 outOfUi(null, new Runnable() {
345 reader
.delete(selectedBook
.getLuid());
347 SwingUtilities
.invokeLater(new Runnable() {
362 * Create the open menu item.
366 private JMenuItem
createMenuItemOpenBook() {
367 JMenuItem open
= new JMenuItem("Open", KeyEvent
.VK_O
);
368 open
.addActionListener(new ActionListener() {
369 public void actionPerformed(ActionEvent e
) {
370 if (selectedBook
!= null) {
371 openBook(selectedBook
);
380 * Open a {@link LocalReaderBook} item.
383 * the {@link LocalReaderBook} to open
385 private void openBook(final LocalReaderBook book
) {
386 final Progress pg
= new Progress();
387 outOfUi(pg
, new Runnable() {
390 File target
= LocalReaderFrame
.this.reader
.getTarget(
392 book
.setCached(true);
393 // TODO: allow custom programs, with
394 // Desktop/xdg-open fallback
396 Desktop
.getDesktop().browse(target
.toURI());
397 } catch (UnsupportedOperationException e
) {
398 String browsers
[] = new String
[] { "xdg-open",
399 "epiphany", "konqueror", "firefox", "chrome",
400 "google-chrome", "mozilla" };
402 Runtime runtime
= Runtime
.getRuntime();
403 for (String browser
: browsers
) {
405 runtime
.exec(new String
[] { browser
,
406 target
.getAbsolutePath() });
409 } catch (IOException ioe
) {
410 // continue, try next browser
414 if (runtime
!= null) {
415 throw new IOException(
416 "Cannot find a working GUI browser...");
419 } catch (IOException e
) {
427 * Process the given action out of the Swing UI thread and link the given
428 * {@link ProgressBar} to the action.
430 * The code will make sure that the {@link ProgressBar} (if not NULL) is set
431 * to done when the action is done.
434 * the {@link ProgressBar} or NULL
438 private void outOfUi(final Progress pg
, final Runnable run
) {
439 pgBar
.setProgress(pg
);
441 SwingUtilities
.invokeLater(new Runnable() {
444 pgBar
.addActioListener(new ActionListener() {
445 public void actionPerformed(ActionEvent e
) {
446 pgBar
.setProgress(null);
453 new Thread(new Runnable() {
457 SwingUtilities
.invokeLater(new Runnable() {
462 } else if (!pg
.isDone()) {
463 pg
.setProgress(pg
.getMax());
470 * Import a {@link Story} into the main {@link Library}.
473 * TRUE for an {@link URL}, false for a {@link File}
475 private void imprt(boolean askUrl
) {
476 JFileChooser fc
= new JFileChooser();
480 url
= JOptionPane
.showInputDialog(LocalReaderFrame
.this,
481 "url of the story to import?", "Importing from URL",
482 JOptionPane
.QUESTION_MESSAGE
);
483 } else if (fc
.showOpenDialog(this) != JFileChooser
.CANCEL_OPTION
) {
484 url
= fc
.getSelectedFile().getAbsolutePath();
489 if (url
!= null && !url
.isEmpty()) {
490 final Progress pg
= new Progress("Importing " + url
);
491 outOfUi(pg
, new Runnable() {
495 Instance
.getLibrary()
496 .imprt(BasicReader
.getUrl(url
), pg
);
497 } catch (IOException e
) {
501 final Exception e
= ex
;
503 final boolean ok
= (e
== null);
504 SwingUtilities
.invokeLater(new Runnable() {
507 JOptionPane
.showMessageDialog(
508 LocalReaderFrame
.this,
509 "Cannot import: " + url
,
511 JOptionPane
.ERROR_MESSAGE
);
525 * Enables or disables this component, depending on the value of the
526 * parameter <code>b</code>. An enabled component can respond to user input
527 * and generate events. Components are enabled initially by default.
529 * Disabling this component will also affect its children.
532 * If <code>true</code>, this component is enabled; otherwise
533 * this component is disabled
536 public void setEnabled(boolean b
) {
537 for (LocalReaderBook book
: books
) {
544 bookPane
.setEnabled(b
);