X-Git-Url: http://git.nikiroo.be/?p=fanfix.git;a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Ffanfix%2Freader%2FLocalReaderFrame.java;h=4e8d9ace0f801b45f3089152c534a45dc6ef3aaa;hp=81ebbeff11a8732325e570388181c61402b6f57d;hb=5130ce84f2b7fce5b451aed15fdaa12bc663331f;hpb=9843a5e5c44825ac404f45ddccd6f63e554567a4 diff --git a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java index 81ebbef..4e8d9ac 100644 --- a/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java +++ b/src/be/nikiroo/fanfix/reader/LocalReaderFrame.java @@ -2,17 +2,22 @@ package be.nikiroo.fanfix.reader; import java.awt.BorderLayout; import java.awt.Color; -import java.awt.Desktop; +import java.awt.Frame; +import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; +import java.net.URL; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import javax.swing.JFileChooser; import javax.swing.JFrame; @@ -24,15 +29,29 @@ import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.SwingUtilities; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; import be.nikiroo.fanfix.Instance; +import be.nikiroo.fanfix.Library; import be.nikiroo.fanfix.bundles.UiConfig; import be.nikiroo.fanfix.data.MetaData; +import be.nikiroo.fanfix.data.Story; +import be.nikiroo.fanfix.output.BasicOutput.OutputType; import be.nikiroo.fanfix.reader.LocalReaderBook.BookActionListener; import be.nikiroo.utils.Progress; +import be.nikiroo.utils.Version; import be.nikiroo.utils.ui.ProgressBar; import be.nikiroo.utils.ui.WrapLayout; +/** + * A {@link Frame} that will show a {@link LocalReaderBook} item for each + * {@link Story} in the main cache ({@link Instance#getCache()}), and offer a + * way to copy them to the {@link LocalReader} cache ({@link LocalReader#lib}), + * read them, delete them... + * + * @author niki + */ class LocalReaderFrame extends JFrame { private static final long serialVersionUID = 1L; private LocalReader reader; @@ -45,8 +64,17 @@ class LocalReaderFrame extends JFrame { private JMenuBar bar; private LocalReaderBook selectedBook; + /** + * Create a new {@link LocalReaderFrame}. + * + * @param reader + * the associated {@link LocalReader} to forward some commands + * and access its {@link Library} + * @param type + * the type of {@link Story} to load, or NULL for all types + */ public LocalReaderFrame(LocalReader reader, String type) { - super("Fanfix Library"); + super(String.format("Fanfix %s Library", Version.getCurrentVersion())); this.reader = reader; @@ -77,10 +105,17 @@ class LocalReaderFrame extends JFrame { setVisible(true); } + /** + * Refresh the list of {@link LocalReaderBook}s from disk. + * + * @param type + * the type of {@link Story} to load, or NULL for all types + */ private void refreshBooks(String type) { this.type = type; stories = Instance.getLibrary().getList(type); books.clear(); + bookPane.invalidate(); bookPane.removeAll(); for (MetaData meta : stories) { LocalReaderBook book = new LocalReaderBook(meta, @@ -89,37 +124,8 @@ class LocalReaderFrame extends JFrame { book.setBackground(color); } - book.addMouseListener(new MouseListener() { - public void mouseReleased(MouseEvent e) { - if (e.isPopupTrigger()) - pop(e); - } - - public void mousePressed(MouseEvent e) { - if (e.isPopupTrigger()) - pop(e); - } - - public void mouseExited(MouseEvent e) { - } - - public void mouseEntered(MouseEvent e) { - } - - public void mouseClicked(MouseEvent e) { - } - - private void pop(MouseEvent e) { - JPopupMenu popup = new JPopupMenu(); - popup.add(createMenuItemExport()); - popup.add(createMenuItemRefresh()); - popup.addSeparator(); - popup.add(createMenuItemDelete()); - // popup.show(e.getComponent(), e.getX(), e.getY()); - } - }); - books.add(book); + book.addActionListener(new BookActionListener() { public void select(LocalReaderBook book) { selectedBook = book; @@ -133,7 +139,8 @@ class LocalReaderFrame extends JFrame { popup.add(createMenuItemOpenBook()); popup.addSeparator(); popup.add(createMenuItemExport()); - popup.add(createMenuItemRefresh()); + popup.add(createMenuItemClearCache()); + popup.add(createMenuItemRedownload()); popup.addSeparator(); popup.add(createMenuItemDelete()); popup.show(e.getComponent(), e.getX(), e.getY()); @@ -151,19 +158,24 @@ class LocalReaderFrame extends JFrame { bookPane.repaint(); } + /** + * Create the main menu bar. + * + * @return the bar + */ private JMenuBar createMenu() { bar = new JMenuBar(); JMenu file = new JMenu("File"); file.setMnemonic(KeyEvent.VK_F); - JMenuItem imprt = new JMenuItem("Import URL", KeyEvent.VK_U); + JMenuItem imprt = new JMenuItem("Import URL...", KeyEvent.VK_U); imprt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { imprt(true); } }); - JMenuItem imprtF = new JMenuItem("Import File", KeyEvent.VK_F); + JMenuItem imprtF = new JMenuItem("Import File...", KeyEvent.VK_F); imprtF.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { imprt(false); @@ -190,7 +202,8 @@ class LocalReaderFrame extends JFrame { JMenu edit = new JMenu("Edit"); edit.setMnemonic(KeyEvent.VK_E); - edit.add(createMenuItemRefresh()); + edit.add(createMenuItemClearCache()); + edit.add(createMenuItemRedownload()); edit.addSeparator(); edit.add(createMenuItemDelete()); @@ -202,7 +215,6 @@ class LocalReaderFrame extends JFrame { List tt = Instance.getLibrary().getTypes(); tt.add(0, null); for (final String type : tt) { - JMenuItem item = new JMenuItem(type == null ? "All books" : type); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -221,28 +233,102 @@ class LocalReaderFrame extends JFrame { return bar; } + /** + * Create the export menu item. + * + * @return the item + */ private JMenuItem createMenuItemExport() { - // TODO - final String notYet = "[TODO] not ready yet, but you can do it on command line, see: fanfix --help"; + final JFileChooser fc = new JFileChooser(); + fc.setAcceptAllFileFilterUsed(false); + + final Map filters = new HashMap(); + for (OutputType type : OutputType.values()) { + String ext = type.getDefaultExtension(false); + String desc = type.getDesc(false); + if (ext == null || ext.isEmpty()) { + filters.put(createAllFilter(desc), type); + } else { + filters.put(new FileNameExtensionFilter(desc, ext), type); + } + } + + // First the "ALL" filters, then, the extension filters + for (Entry entry : filters.entrySet()) { + if (!(entry.getKey() instanceof FileNameExtensionFilter)) { + fc.addChoosableFileFilter(entry.getKey()); + } + } + for (Entry entry : filters.entrySet()) { + if (entry.getKey() instanceof FileNameExtensionFilter) { + fc.addChoosableFileFilter(entry.getKey()); + } + } + // - JMenuItem export = new JMenuItem("Save as...", KeyEvent.VK_E); + JMenuItem export = new JMenuItem("Save as...", KeyEvent.VK_S); export.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - JOptionPane.showMessageDialog(LocalReaderFrame.this, notYet); + if (selectedBook != null) { + fc.showDialog(LocalReaderFrame.this, "Save"); + final OutputType type = filters.get(fc.getFileFilter()); + final String path = fc.getSelectedFile().getAbsolutePath() + + type.getDefaultExtension(false); + final Progress pg = new Progress(); + outOfUi(pg, new Runnable() { + public void run() { + try { + Instance.getLibrary().export( + selectedBook.getMeta().getLuid(), type, + path, pg); + } catch (IOException e) { + Instance.syserr(e); + } + } + }); + } } }); return export; } - private JMenuItem createMenuItemRefresh() { - JMenuItem refresh = new JMenuItem("Refresh", KeyEvent.VK_R); + /** + * Create a {@link FileFilter} that accepts all files and return the given + * description. + * + * @param desc + * the description + * + * @return the filter + */ + private FileFilter createAllFilter(final String desc) { + return new FileFilter() { + @Override + public String getDescription() { + return desc; + } + + @Override + public boolean accept(File f) { + return true; + } + }; + } + + /** + * Create the refresh (delete cache) menu item. + * + * @return the item + */ + private JMenuItem createMenuItemClearCache() { + JMenuItem refresh = new JMenuItem("Clear cache", KeyEvent.VK_C); refresh.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (selectedBook != null) { outOfUi(null, new Runnable() { public void run() { - reader.refresh(selectedBook.getLuid()); + reader.refresh(selectedBook.getMeta().getLuid()); selectedBook.setCached(false); SwingUtilities.invokeLater(new Runnable() { public void run() { @@ -258,6 +344,34 @@ class LocalReaderFrame extends JFrame { return refresh; } + /** + * Create the redownload (then delete original) menu item. + * + * @return the item + */ + private JMenuItem createMenuItemRedownload() { + JMenuItem refresh = new JMenuItem("Redownload", KeyEvent.VK_R); + refresh.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (selectedBook != null) { + imprt(selectedBook.getMeta().getUrl(), new Runnable() { + public void run() { + reader.delete(selectedBook.getMeta().getLuid()); + selectedBook = null; + } + }); + } + } + }); + + return refresh; + } + + /** + * Create the delete menu item. + * + * @return the item + */ private JMenuItem createMenuItemDelete() { JMenuItem delete = new JMenuItem("Delete", KeyEvent.VK_D); delete.addActionListener(new ActionListener() { @@ -265,7 +379,7 @@ class LocalReaderFrame extends JFrame { if (selectedBook != null) { outOfUi(null, new Runnable() { public void run() { - reader.delete(selectedBook.getLuid()); + reader.delete(selectedBook.getMeta().getLuid()); selectedBook = null; SwingUtilities.invokeLater(new Runnable() { public void run() { @@ -281,6 +395,11 @@ class LocalReaderFrame extends JFrame { return delete; } + /** + * Create the open menu item. + * + * @return the item + */ private JMenuItem createMenuItemOpenBook() { JMenuItem open = new JMenuItem("Open", KeyEvent.VK_O); open.addActionListener(new ActionListener() { @@ -294,59 +413,51 @@ class LocalReaderFrame extends JFrame { return open; } + /** + * Open a {@link LocalReaderBook} item. + * + * @param book + * the {@link LocalReaderBook} to open + */ private void openBook(final LocalReaderBook book) { final Progress pg = new Progress(); outOfUi(pg, new Runnable() { public void run() { try { - File target = LocalReaderFrame.this.reader.getTarget( - book.getLuid(), pg); - book.setCached(true); - // TODO: allow custom programs, with - // Desktop/xdg-open fallback - try { - Desktop.getDesktop().browse(target.toURI()); - } catch (UnsupportedOperationException e) { - String browsers[] = new String[] { "xdg-open", - "epiphany", "konqueror", "firefox", "chrome", - "google-chrome", "mozilla" }; - - Runtime runtime = Runtime.getRuntime(); - for (String browser : browsers) { - try { - runtime.exec(new String[] { browser, - target.getAbsolutePath() }); - runtime = null; - break; - } catch (IOException ioe) { - // continue, try next browser - } - } - - if (runtime != null) { - throw new IOException( - "Cannot find a working GUI browser..."); + reader.open(book.getMeta().getLuid(), pg); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + book.setCached(true); } - } + }); } catch (IOException e) { + // TODO: error message? Instance.syserr(e); } } }); } + /** + * Process the given action out of the Swing UI thread and link the given + * {@link ProgressBar} to the action. + *

+ * The code will make sure that the {@link ProgressBar} (if not NULL) is set + * to done when the action is done. + * + * @param pg + * the {@link ProgressBar} or NULL + * @param run + * the action to run + */ private void outOfUi(final Progress pg, final Runnable run) { pgBar.setProgress(pg); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - setAllEnabled(false); - pgBar.addActioListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - pgBar.setProgress(null); - setAllEnabled(true); - } - }); + setEnabled(false); + pgBar.addActioListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + pgBar.setProgress(null); + setEnabled(true); } }); @@ -356,7 +467,7 @@ class LocalReaderFrame extends JFrame { if (pg == null) { SwingUtilities.invokeLater(new Runnable() { public void run() { - setAllEnabled(true); + setEnabled(true); } }); } else if (!pg.isDone()) { @@ -366,69 +477,116 @@ class LocalReaderFrame extends JFrame { }).start(); } + /** + * Import a {@link Story} into the main {@link Library}. + *

+ * Should be called inside the UI thread. + * + * @param askUrl + * TRUE for an {@link URL}, false for a {@link File} + */ private void imprt(boolean askUrl) { JFileChooser fc = new JFileChooser(); - final String url; + Object url; if (askUrl) { + String clipboard = ""; + try { + clipboard = ("" + Toolkit.getDefaultToolkit() + .getSystemClipboard().getData(DataFlavor.stringFlavor)) + .trim(); + } catch (Exception e) { + // No data will be handled + } + + if (clipboard == null || !clipboard.startsWith("http")) { + clipboard = ""; + } + url = JOptionPane.showInputDialog(LocalReaderFrame.this, "url of the story to import?", "Importing from URL", - JOptionPane.QUESTION_MESSAGE); + JOptionPane.QUESTION_MESSAGE, null, null, clipboard); } else if (fc.showOpenDialog(this) != JFileChooser.CANCEL_OPTION) { url = fc.getSelectedFile().getAbsolutePath(); } else { url = null; } - if (url != null && !url.isEmpty()) { - final Progress pg = new Progress("Importing " + url); - outOfUi(pg, new Runnable() { - public void run() { - Exception ex = null; - try { - Instance.getLibrary() - .imprt(BasicReader.getUrl(url), pg); - } catch (IOException e) { - ex = e; - } + if (url != null && !url.toString().isEmpty()) { + imprt(url.toString(), null); + } + } - final Exception e = ex; + /** + * Actually import the {@link Story} into the main {@link Library}. + *

+ * Should be called inside the UI thread. + * + * @param url + * the {@link Story} to import by {@link URL} + * @param onSuccess + * Action to execute on success + */ + private void imprt(final String url, final Runnable onSuccess) { + final Progress pg = new Progress("Importing " + url); + outOfUi(pg, new Runnable() { + public void run() { + Exception ex = null; + try { + Instance.getLibrary().imprt(BasicReader.getUrl(url), pg); + } catch (IOException e) { + ex = e; + } - final boolean ok = (e == null); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - if (!ok) { - JOptionPane.showMessageDialog( - LocalReaderFrame.this, - "Cannot import: " + url, - e.getMessage(), - JOptionPane.ERROR_MESSAGE); - - setAllEnabled(true); - } else { + final Exception e = ex; + + final boolean ok = (e == null); + SwingUtilities.invokeLater(new Runnable() { + public void run() { + if (!ok) { + Instance.syserr(e); + JOptionPane.showMessageDialog( + LocalReaderFrame.this, "Cannot import: " + + url, e.getMessage(), + JOptionPane.ERROR_MESSAGE); + + setEnabled(true); + } else { + refreshBooks(type); + if (onSuccess != null) { + onSuccess.run(); refreshBooks(type); } } - }); - } - }); - } + } + }); + } + }); } - public void setAllEnabled(boolean enabled) { + /** + * Enables or disables this component, depending on the value of the + * parameter b. An enabled component can respond to user input + * and generate events. Components are enabled initially by default. + *

+ * Disabling this component will also affect its children. + * + * @param b + * If true, this component is enabled; otherwise + * this component is disabled + */ + @Override + public void setEnabled(boolean b) { for (LocalReaderBook book : books) { - book.setEnabled(enabled); - book.validate(); + book.setEnabled(b); book.repaint(); } - bar.setEnabled(enabled); - bookPane.setEnabled(enabled); - bookPane.validate(); + bar.setEnabled(b); + bookPane.setEnabled(b); bookPane.repaint(); - setEnabled(enabled); - validate(); + super.setEnabled(b); repaint(); } }