ImporterFrame usage
authorNiki Roo <niki@nikiroo.be>
Sat, 18 Apr 2020 15:01:38 +0000 (17:01 +0200)
committerNiki Roo <niki@nikiroo.be>
Sat, 18 Apr 2020 15:01:38 +0000 (17:01 +0200)
src/be/nikiroo/fanfix_swing/Actions.java
src/be/nikiroo/fanfix_swing/gui/BooksPanel.java
src/be/nikiroo/fanfix_swing/gui/ImporterFrame.java [deleted file]
src/be/nikiroo/fanfix_swing/gui/MainFrame.java
src/be/nikiroo/fanfix_swing/gui/SearchBar.java
src/be/nikiroo/fanfix_swing/gui/book/BookCoverImager.java
src/be/nikiroo/fanfix_swing/gui/book/BookLine.java
src/be/nikiroo/fanfix_swing/gui/book/BookPopup.java [deleted file]
src/be/nikiroo/fanfix_swing/gui/importer/ImporterFrame.java [new file with mode: 0644]
src/be/nikiroo/fanfix_swing/gui/importer/ImporterItem.java [new file with mode: 0644]

index ff635b7adb0f5e5d13887a715e9ae18eb1c8081d..4c26e08b37aeb80accf4196bfeed468ffb8e5985 100644 (file)
@@ -148,8 +148,8 @@ public class Actions {
                Process proc = null;
                if (program == null) {
                        boolean ok = false;
-                       for (String starter : new String[] { "xdg-open", "see",
-                                       "start", "run", "open" }) {
+                       for (String starter : new String[] { "xdg-open", "see", "start",
+                                       "run", "open" }) {
                                try {
                                        Instance.getInstance().getTraceHandler()
                                                        .trace("starting external program: " + starter);
@@ -195,7 +195,7 @@ public class Actions {
         *            Action to execute on success
         */
        static public void imprt(final Container parent, final String url,
-                       final Progress pg, final Runnable onSuccess) {
+                       Progress pg, final Runnable onSuccess) {
                final Progress fpg = pg;
                new SwingWorker<Void, Void>() {
                        @Override
@@ -206,14 +206,14 @@ public class Actions {
 
                                try {
                                        Instance.getInstance().getLibrary()
-                                                       .imprt(BasicReader.getUrl(url), fpg);
+                                                       .imprt(BasicReader.getUrl(url), pg);
 
-                                       fpg.done();
+                                       pg.done();
                                        if (onSuccess != null) {
                                                onSuccess.run();
                                        }
                                } catch (IOException e) {
-                                       fpg.done();
+                                       pg.done();
                                        if (e instanceof UnknownHostException) {
                                                UiHelper.error(parent,
                                                                Instance.getInstance().getTransGui().getString(
index 20e28af116f4aba190ad0e643eacc47019ef4812..3f4094bd1803f6c3fb8714ab33956aeb4afa4b1b 100644 (file)
@@ -36,10 +36,16 @@ import be.nikiroo.fanfix_swing.gui.utils.UiHelper;
 
 public class BooksPanel extends ListenerPanel {
        private class ListModel extends DefaultListModel<BookInfo> {
+               public void fireElementChanged(int index) {
+                       if (index >= 0) {
+                               fireContentsChanged(this, index, index);
+                       }
+               }
+
                public void fireElementChanged(BookInfo element) {
                        int index = indexOf(element);
                        if (index >= 0) {
-                               fireContentsChanged(element, index, index);
+                               fireContentsChanged(this, index, index);
                        }
                }
        }
@@ -55,19 +61,19 @@ public class BooksPanel extends ListenerPanel {
        private int hoveredIndex = -1;
        private ListModel data = new ListModel();
        private DelayWorker bookCoverUpdater;
-
-       private SearchBar searchBar;
+       private String filter = "";
 
        public BooksPanel(boolean listMode) {
                setLayout(new BorderLayout());
 
-               searchBar = new SearchBar();
-               add(searchBar, BorderLayout.NORTH);
+               final SearchBar search = new SearchBar();
+               add(search, BorderLayout.NORTH);
 
-               searchBar.addActionListener(new ActionListener() {
+               search.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
-                               filter(searchBar.getText());
+                               filter = search.getText();
+                               filter();
                        }
                });
 
@@ -113,11 +119,10 @@ public class BooksPanel extends ListenerPanel {
                this.bookInfos.addAll(bookInfos);
                bookCoverUpdater.clear();
 
-               filter(searchBar.getText());
+               filter();
        }
 
-       // cannot be NULL
-       private void filter(String filter) {
+       private void filter() {
                data.clear();
                for (BookInfo bookInfo : bookInfos) {
                        if (bookInfo.getMainInfo() == null || filter.isEmpty()
@@ -166,10 +171,16 @@ public class BooksPanel extends ListenerPanel {
                                                book.setCached(cached);
                                                fireElementChanged(book);
                                        }
-
+                                       
+                                       @Override
                                        public void fireElementChanged(BookInfo book) {
                                                data.fireElementChanged(book);
                                        }
+                                       
+                                       @Override
+                                       public void removeElement(BookInfo book) {
+                                               data.removeElement(book);
+                                       }
 
                                        @Override
                                        public List<BookInfo> getSelected() {
@@ -228,8 +239,9 @@ public class BooksPanel extends ListenerPanel {
                                        return;
 
                                if (hoveredIndex > -1) {
+                                       int index = hoveredIndex;
                                        hoveredIndex = -1;
-                                       list.repaint();
+                                       data.fireElementChanged(index);
                                }
                        }
 
diff --git a/src/be/nikiroo/fanfix_swing/gui/ImporterFrame.java b/src/be/nikiroo/fanfix_swing/gui/ImporterFrame.java
deleted file mode 100644 (file)
index d4ab526..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-package be.nikiroo.fanfix_swing.gui;
-
-import java.awt.Container;
-import java.awt.Toolkit;
-import java.awt.datatransfer.DataFlavor;
-import java.io.File;
-import java.net.URL;
-
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JOptionPane;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.library.LocalLibrary;
-import be.nikiroo.fanfix_swing.Actions;
-import be.nikiroo.utils.Progress;
-
-public class ImporterFrame extends JFrame {
-       public ImporterFrame() {
-
-       }
-
-       /**
-        * Ask for and import an {@link URL} into the main {@link LocalLibrary}.
-        * <p>
-        * Should be called inside the UI thread.
-        * 
-        * @param parent
-        *            a container we can use to display the {@link URL} chooser and
-        *            to show error messages if any
-        * @param onSuccess
-        *            Action to execute on success
-        */
-       public void imprtUrl(final Container parent, final Runnable onSuccess) {
-               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.startsWith("https://"))) {
-                       clipboard = "";
-               }
-
-               Object url = JOptionPane.showInputDialog(parent,
-                               Instance.getInstance().getTransGui()
-                                               .getString(StringIdGui.SUBTITLE_IMPORT_URL),
-                               Instance.getInstance().getTransGui()
-                                               .getString(StringIdGui.TITLE_IMPORT_URL),
-                               JOptionPane.QUESTION_MESSAGE, null, null, clipboard);
-
-               Progress pg = null;
-               if (url != null && !url.toString().isEmpty()) {
-                       Actions.imprt(parent, url.toString(), pg, onSuccess);
-               }
-       }
-
-       /**
-        * Ask for and import a {@link File} into the main {@link LocalLibrary}.
-        * <p>
-        * Should be called inside the UI thread.
-        * 
-        * @param parent
-        *            a container we can use to display the {@link File} chooser and
-        *            to show error messages if any
-        * @param onSuccess
-        *            Action to execute on success
-        */
-
-       public void imprtFile(final Container parent, final Runnable onSuccess) {
-               JFileChooser fc = new JFileChooser();
-
-               Progress pg = null;
-               if (fc.showOpenDialog(parent) != JFileChooser.CANCEL_OPTION) {
-                       Object url = fc.getSelectedFile().getAbsolutePath();
-                       if (url != null && !url.toString().isEmpty()) {
-                               Actions.imprt(parent, url.toString(), pg, onSuccess);
-                       }
-               }
-       }
-}
index 313f086eb18f01b1d9b60654c0f19ad08f97900b..948f3ccd3eb3160b1cba0a93abf9b1933453460c 100644 (file)
@@ -12,12 +12,14 @@ import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
 import javax.swing.JSplitPane;
 
+import be.nikiroo.fanfix_swing.gui.importer.ImporterFrame;
 import be.nikiroo.utils.Version;
 
 public class MainFrame extends JFrame {
        private BooksPanel books;
        private DetailsPanel details;
        private BrowserPanel browser;
+       private ImporterFrame importer = new ImporterFrame();
 
        public MainFrame(boolean sidePanel, boolean detailsPanel) {
                super("Fanfix " + Version.getCurrentVersion());
@@ -94,8 +96,7 @@ public class MainFrame extends JFrame {
                item1.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
-                               // TODO: correctly use the importer (wip)
-                               new ImporterFrame().imprtUrl(MainFrame.this, new Runnable() {
+                               importer.imprtUrl(MainFrame.this, new Runnable() {
                                        @Override
                                        public void run() {
                                                browser.reloadData();
@@ -112,8 +113,7 @@ public class MainFrame extends JFrame {
                item2.addActionListener(new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
-                               // TODO: correctly use the importer (wip)
-                               new ImporterFrame().imprtFile(MainFrame.this, new Runnable() {
+                               importer.imprtFile(MainFrame.this, new Runnable() {
                                        @Override
                                        public void run() {
                                                browser.reloadData();
index eb0c5876f07b35d852ac5ee63dad40006b4206d8..0be2f080f1d4b70255acc268f8e8549e0e23f4f3 100644 (file)
@@ -102,11 +102,14 @@ public class SearchBar extends ListenerPanel {
        }
 
        /**
-        * Return the current text displayed by this {@link SearchBar}.
+        * Return the current text displayed by this {@link SearchBar}, or an empty
+        * {@link String} if none.
         * 
-        * @return the text
+        * @return the text, cannot be NULL
         */
        public String getText() {
-               return text.getText();
+               // Should usually not be NULL, but not impossible
+               String text = this.text.getText();
+               return text == null ? "" : text;
        }
 }
index 100b79f0b79403a7f05f23f601dc0bfb9d1fc8b0..2b0795f617042e8766a611022ad9dafea802b5fe 100644 (file)
@@ -79,7 +79,7 @@ class BookCoverImager {
        static public void paintOverlay(Graphics g, boolean enabled,
                        boolean selected, boolean hovered, boolean cached) {
                Rectangle clip = g.getClipBounds();
-               if (clip.getWidth() <= 0 || clip.getHeight() <= 0) {
+               if (clip == null || clip.getWidth() <= 0 || clip.getHeight() <= 0) {
                        return;
                }
 
index d601dd6c728cd47e540ced17eaeb4d0cfe53298c..4e2e3f4c6b641902b869ff736f14b3c34dfba569 100644 (file)
@@ -23,7 +23,7 @@ public class BookLine extends JPanel {
 
        private static final int MAX_DISPLAY_SIZE = 40;
 
-       /** Colour used for the seconday item (author/word count). */
+       /** Colour used for the secondary item (author/word count). */
        protected static final Color AUTHOR_COLOR = new Color(128, 128, 128);
 
        private boolean selected;
diff --git a/src/be/nikiroo/fanfix_swing/gui/book/BookPopup.java b/src/be/nikiroo/fanfix_swing/gui/book/BookPopup.java
deleted file mode 100644 (file)
index 80827c0..0000000
+++ /dev/null
@@ -1,772 +0,0 @@
-package be.nikiroo.fanfix_swing.gui.book;
-
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.KeyEvent;
-import java.io.File;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import javax.swing.JFileChooser;
-import javax.swing.JFrame;
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
-import javax.swing.JOptionPane;
-import javax.swing.JPopupMenu;
-import javax.swing.SwingWorker;
-import javax.swing.filechooser.FileFilter;
-import javax.swing.filechooser.FileNameExtensionFilter;
-
-import be.nikiroo.fanfix.Instance;
-import be.nikiroo.fanfix.bundles.Config;
-import be.nikiroo.fanfix.bundles.StringIdGui;
-import be.nikiroo.fanfix.bundles.UiConfig;
-import be.nikiroo.fanfix.data.MetaData;
-import be.nikiroo.fanfix.data.Story;
-import be.nikiroo.fanfix.library.BasicLibrary;
-import be.nikiroo.fanfix.library.BasicLibrary.Status;
-import be.nikiroo.fanfix.output.BasicOutput.OutputType;
-import be.nikiroo.fanfix_swing.Actions;
-import be.nikiroo.fanfix_swing.gui.utils.UiHelper;
-import be.nikiroo.utils.Progress;
-import be.nikiroo.utils.ui.ConfigEditor;
-
-public class BookPopup extends JPopupMenu {
-       public abstract interface Informer {
-
-               // not null
-               public List<BookInfo> getSelected();
-
-               public void setCached(BookInfo book, boolean cached);
-
-               public BookInfo getUniqueSelected();
-
-               public void fireElementChanged(BookInfo book);
-
-               public void invalidateCache();
-       }
-
-       /**
-        * The different modification actions you can use on {@link Story} items.
-        * 
-        * @author niki
-        */
-       private enum ChangeAction {
-               /** Change the source/type, that is, move it to another source. */
-               SOURCE,
-               /** Change its name. */
-               TITLE,
-               /** Change its author. */
-               AUTHOR
-       }
-
-       // be careful with that
-       private BasicLibrary lib;
-
-       private Informer informer;
-
-       public BookPopup(BasicLibrary lib, Informer informer) {
-               this.lib = lib;
-               this.informer = informer;
-
-               Status status = lib.getStatus();
-               add(createMenuItemOpenBook());
-               addSeparator();
-               add(createMenuItemExport());
-               if (status.isWritable()) {
-                       add(createMenuItemMoveTo());
-                       add(createMenuItemSetCoverForSource());
-                       add(createMenuItemSetCoverForAuthor());
-               }
-               add(createMenuItemDownloadToCache());
-               add(createMenuItemClearCache());
-               if (status.isWritable()) {
-                       add(createMenuItemRedownload());
-                       addSeparator();
-                       add(createMenuItemRename());
-                       add(createMenuItemSetAuthor());
-                       addSeparator();
-                       add(createMenuItemDelete());
-               }
-               addSeparator();
-               add(createMenuItemProperties());
-       }
-
-       private String trans(StringIdGui id) {
-               return Instance.getInstance().getTransGui().getString(id);
-       }
-
-       /**
-        * Create the Fanfix Configuration menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemConfig() {
-               final String title = trans(StringIdGui.TITLE_CONFIG);
-               JMenuItem item = new JMenuItem(title);
-               item.setMnemonic(KeyEvent.VK_F);
-
-               item.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               ConfigEditor<Config> ed = new ConfigEditor<Config>(Config.class,
-                                               Instance.getInstance().getConfig(),
-                                               trans(StringIdGui.SUBTITLE_CONFIG));
-                               JFrame frame = new JFrame(title);
-                               frame.add(ed);
-                               frame.setSize(850, 600);
-                               frame.setVisible(true);
-                       }
-               });
-
-               return item;
-       }
-
-       /**
-        * Create the UI Configuration menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemUiConfig() {
-               final String title = trans(StringIdGui.TITLE_CONFIG_UI);
-               JMenuItem item = new JMenuItem(title);
-               item.setMnemonic(KeyEvent.VK_U);
-
-               item.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               ConfigEditor<UiConfig> ed = new ConfigEditor<UiConfig>(
-                                               UiConfig.class, Instance.getInstance().getUiConfig(),
-                                               trans(StringIdGui.SUBTITLE_CONFIG_UI));
-                               JFrame frame = new JFrame(title);
-                               frame.add(ed);
-                               frame.setSize(800, 600);
-                               frame.setVisible(true);
-                       }
-               });
-
-               return item;
-       }
-
-       /**
-        * Create the export menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemExport() {
-
-               // TODO: allow dir for multiple selection?
-
-               final JFileChooser fc = new JFileChooser();
-               fc.setAcceptAllFileFilterUsed(false);
-
-               // Add the "ALL" filters first, then the others
-               final Map<FileFilter, OutputType> otherFilters = new HashMap<FileFilter, OutputType>();
-               for (OutputType type : OutputType.values()) {
-                       String ext = type.getDefaultExtension(false);
-                       String desc = type.getDesc(false);
-
-                       if (ext == null || ext.isEmpty()) {
-                               fc.addChoosableFileFilter(createAllFilter(desc));
-                       } else {
-                               otherFilters.put(new FileNameExtensionFilter(desc, ext), type);
-                       }
-               }
-
-               for (Entry<FileFilter, OutputType> entry : otherFilters.entrySet()) {
-                       fc.addChoosableFileFilter(entry.getKey());
-               }
-               //
-
-               JMenuItem export = new JMenuItem(trans(StringIdGui.MENU_FILE_EXPORT),
-                               KeyEvent.VK_S);
-               export.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final BookInfo book = informer.getUniqueSelected();
-                               if (book != null) {
-                                       fc.showDialog(BookPopup.this.getParent(),
-                                                       trans(StringIdGui.TITLE_SAVE));
-                                       if (fc.getSelectedFile() != null) {
-                                               final OutputType type = otherFilters
-                                                               .get(fc.getFileFilter());
-                                               final String path = fc.getSelectedFile()
-                                                               .getAbsolutePath()
-                                                               + type.getDefaultExtension(false);
-                                               final Progress pg = new Progress();
-
-                                               new SwingWorker<Void, Void>() {
-                                                       @Override
-                                                       protected Void doInBackground() throws Exception {
-                                                               lib.export(book.getMeta().getLuid(), type, path,
-                                                                               pg);
-                                                               return null;
-                                                       }
-
-                                                       @Override
-                                                       protected void done() {
-                                                               try {
-                                                                       get();
-                                                               } catch (Exception e) {
-                                                                       UiHelper.error(BookPopup.this.getParent(),
-                                                                                       e.getLocalizedMessage(),
-                                                                                       "IOException", e);
-                                                               }
-                                                       }
-                                               }.execute();
-                                       }
-                               }
-                       }
-               });
-
-               return export;
-       }
-
-       /**
-        * 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(
-                               trans(StringIdGui.MENU_EDIT_CLEAR_CACHE), KeyEvent.VK_C);
-               refresh.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final List<BookInfo> selected = informer.getSelected();
-                               if (!selected.isEmpty()) {
-                                       new SwingWorker<Void, Void>() {
-                                               @Override
-                                               protected Void doInBackground() throws Exception {
-                                                       for (BookInfo book : selected) {
-                                                               lib.clearFromCache(book.getMeta().getLuid());
-                                                               BookCoverImager.clearIcon(book);
-                                                       }
-                                                       return null;
-                                               }
-
-                                               @Override
-                                               protected void done() {
-                                                       try {
-                                                               get();
-                                                               for (BookInfo book : selected) {
-                                                                       informer.setCached(book, false);
-                                                               }
-                                                       } catch (Exception e) {
-                                                               UiHelper.error(BookPopup.this.getParent(),
-                                                                               e.getLocalizedMessage(), "IOException",
-                                                                               e);
-                                                       }
-                                               }
-                                       }.execute();
-                               }
-                       }
-               });
-
-               return refresh;
-       }
-
-       /**
-        * Create the "move to" menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemMoveTo() {
-               JMenu changeTo = new JMenu(trans(StringIdGui.MENU_FILE_MOVE_TO));
-               changeTo.setMnemonic(KeyEvent.VK_M);
-
-               Map<String, List<String>> groupedSources = new HashMap<String, List<String>>();
-               try {
-                       groupedSources = lib.getSourcesGrouped();
-               } catch (IOException e) {
-                       UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(),
-                                       "IOException", e);
-               }
-
-               JMenuItem item = new JMenuItem(
-                               trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_TYPE));
-               item.addActionListener(createMoveAction(ChangeAction.SOURCE, null));
-               changeTo.add(item);
-               changeTo.addSeparator();
-
-               for (final String type : groupedSources.keySet()) {
-                       List<String> list = groupedSources.get(type);
-                       if (list.size() == 1 && list.get(0).isEmpty()) {
-                               item = new JMenuItem(type);
-                               item.addActionListener(
-                                               createMoveAction(ChangeAction.SOURCE, type));
-                               changeTo.add(item);
-                       } else {
-                               JMenu dir = new JMenu(type);
-                               for (String sub : list) {
-                                       // " " instead of "" for the visual height
-                                       String itemName = sub.isEmpty() ? " " : sub;
-                                       String actualType = type;
-                                       if (!sub.isEmpty()) {
-                                               actualType += "/" + sub;
-                                       }
-
-                                       item = new JMenuItem(itemName);
-                                       item.addActionListener(
-                                                       createMoveAction(ChangeAction.SOURCE, actualType));
-                                       dir.add(item);
-                               }
-                               changeTo.add(dir);
-                       }
-               }
-
-               return changeTo;
-       }
-
-       /**
-        * Create the "set author" menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemSetAuthor() {
-               JMenu changeTo = new JMenu(trans(StringIdGui.MENU_FILE_SET_AUTHOR));
-               changeTo.setMnemonic(KeyEvent.VK_A);
-
-               // New author
-               JMenuItem newItem = new JMenuItem(
-                               trans(StringIdGui.MENU_FILE_MOVE_TO_NEW_AUTHOR));
-               changeTo.add(newItem);
-               changeTo.addSeparator();
-               newItem.addActionListener(createMoveAction(ChangeAction.AUTHOR, null));
-
-               // Existing authors
-               Map<String, List<String>> groupedAuthors;
-
-               try {
-                       groupedAuthors = lib.getAuthorsGrouped();
-               } catch (IOException e) {
-                       UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(),
-                                       "IOException", e);
-                       groupedAuthors = new HashMap<String, List<String>>();
-
-               }
-
-               if (groupedAuthors.size() > 1) {
-                       for (String key : groupedAuthors.keySet()) {
-                               JMenu group = new JMenu(key);
-                               for (String value : groupedAuthors.get(key)) {
-                                       JMenuItem item = new JMenuItem(value.isEmpty()
-                                                       ? trans(StringIdGui.MENU_AUTHORS_UNKNOWN)
-                                                       : value);
-                                       item.addActionListener(
-                                                       createMoveAction(ChangeAction.AUTHOR, value));
-                                       group.add(item);
-                               }
-                               changeTo.add(group);
-                       }
-               } else if (groupedAuthors.size() == 1) {
-                       for (String value : groupedAuthors.values().iterator().next()) {
-                               JMenuItem item = new JMenuItem(value.isEmpty()
-                                               ? trans(StringIdGui.MENU_AUTHORS_UNKNOWN)
-                                               : value);
-                               item.addActionListener(
-                                               createMoveAction(ChangeAction.AUTHOR, value));
-                               changeTo.add(item);
-                       }
-               }
-
-               return changeTo;
-       }
-
-       /**
-        * Create the "rename" menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemRename() {
-               JMenuItem changeTo = new JMenuItem(trans(StringIdGui.MENU_FILE_RENAME));
-               changeTo.setMnemonic(KeyEvent.VK_R);
-               changeTo.addActionListener(createMoveAction(ChangeAction.TITLE, null));
-               return changeTo;
-       }
-
-       private ActionListener createMoveAction(final ChangeAction what,
-                       final String type) {
-               return new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final List<BookInfo> selected = informer.getSelected();
-                               if (!selected.isEmpty()) {
-                                       String changeTo = type;
-                                       if (type == null) {
-                                               String init = "";
-
-                                               if (selected.size() == 1) {
-                                                       MetaData meta = selected.get(0).getMeta();
-                                                       if (what == ChangeAction.SOURCE) {
-                                                               init = meta.getSource();
-                                                       } else if (what == ChangeAction.TITLE) {
-                                                               init = meta.getTitle();
-                                                       } else if (what == ChangeAction.AUTHOR) {
-                                                               init = meta.getAuthor();
-                                                       }
-                                               }
-
-                                               Object rep = JOptionPane.showInputDialog(
-                                                               BookPopup.this.getParent(),
-                                                               trans(StringIdGui.SUBTITLE_MOVE_TO),
-                                                               trans(StringIdGui.TITLE_MOVE_TO),
-                                                               JOptionPane.QUESTION_MESSAGE, null, null, init);
-
-                                               if (rep == null) {
-                                                       return;
-                                               }
-
-                                               changeTo = rep.toString();
-                                       }
-
-                                       final String fChangeTo = changeTo;
-                                       new SwingWorker<Void, Void>() {
-                                               @Override
-                                               protected Void doInBackground() throws Exception {
-                                                       for (BookInfo book : selected) {
-                                                               String luid = book.getMeta().getLuid();
-                                                               if (what == ChangeAction.SOURCE) {
-                                                                       lib.changeSource(luid, fChangeTo, null);
-                                                               } else if (what == ChangeAction.TITLE) {
-                                                                       lib.changeTitle(luid, fChangeTo, null);
-                                                               } else if (what == ChangeAction.AUTHOR) {
-                                                                       lib.changeAuthor(luid, fChangeTo, null);
-                                                               }
-                                                       }
-
-                                                       return null;
-                                               }
-
-                                               @Override
-                                               protected void done() {
-                                                       try {
-                                                               // this can create new sources/authors, so a
-                                                               // simple fireElementChanged is not
-                                                               // enough, we need to clear the whole cache (for
-                                                               // BrowserPanel for instance)
-                                                               informer.invalidateCache();
-                                                               
-                                                               // But we ALSO fire those, because they appear
-                                                               // before the whole refresh...
-                                                               for (BookInfo book : selected) {
-                                                                       informer.fireElementChanged(book);
-                                                               }
-
-                                                               // TODO: also refresh the
-                                                               // Sources/Authors(/Tags?) list
-
-                                                               // Even if problems occurred, still invalidate
-                                                               // the cache
-                                                               get();
-                                                       } catch (Exception e) {
-                                                               UiHelper.error(BookPopup.this.getParent(),
-                                                                               e.getLocalizedMessage(), "IOException",
-                                                                               e);
-                                                       }
-                                               }
-                                       }.execute();
-                               }
-                       }
-               };
-       }
-
-       /**
-        * Create the re-download (then delete original) menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemRedownload() {
-               JMenuItem refresh = new JMenuItem(
-                               trans(StringIdGui.MENU_EDIT_REDOWNLOAD), KeyEvent.VK_R);
-               refresh.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               // final GuiReaderBook selectedBook =
-                               // mainPanel.getSelectedBook();
-                               // if (selectedBook != null) {
-                               // final MetaData meta = selectedBook.getInfo().getMeta();
-                               // mainPanel.imprt(meta.getUrl(), new MetaDataRunnable() {
-                               // @Override
-                               // public void run(MetaData newMeta) {
-                               // if (!newMeta.getSource().equals(meta.getSource())) {
-                               // reader.changeSource(newMeta.getLuid(), meta.getSource());
-                               // }
-                               // }
-                               // }, trans(StringIdGui.PROGRESS_CHANGE_SOURCE));
-                               // }
-                       }
-               });
-
-               return refresh;
-       }
-
-       /**
-        * Create the download to cache menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemDownloadToCache() {
-               JMenuItem refresh = new JMenuItem(
-                               trans(StringIdGui.MENU_EDIT_DOWNLOAD_TO_CACHE), KeyEvent.VK_T);
-               refresh.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final List<BookInfo> selected = informer.getSelected();
-
-                               new SwingWorker<Void, Void>() {
-                                       @Override
-                                       protected Void doInBackground() throws Exception {
-
-                                               final List<String> luids = new LinkedList<String>();
-                                               for (BookInfo book : selected) {
-                                                       switch (book.getType()) {
-                                                       case STORY:
-                                                               luids.add(book.getMeta().getLuid());
-                                                               break;
-                                                       case SOURCE:
-                                                               for (MetaData meta : lib.getList().filter(
-                                                                               book.getMainInfo(), null, null)) {
-                                                                       luids.add(meta.getLuid());
-                                                               }
-                                                               break;
-                                                       case AUTHOR:
-                                                               for (MetaData meta : lib.getList().filter(null,
-                                                                               book.getMainInfo(), null)) {
-                                                                       luids.add(meta.getLuid());
-                                                               }
-                                                               break;
-                                                       case TAG:
-                                                               for (MetaData meta : lib.getList().filter(null,
-                                                                               null, book.getMainInfo())) {
-                                                                       luids.add(meta.getLuid());
-                                                               }
-                                                               break;
-                                                       }
-                                               }
-
-                                               // TODO: do something with pg?
-                                               final Progress pg = new Progress();
-                                               pg.setMax(luids.size());
-                                               for (String luid : luids) {
-                                                       Progress pgStep = new Progress();
-                                                       pg.addProgress(pgStep, 1);
-
-                                                       lib.getFile(luid, pgStep);
-                                               }
-
-                                               return null;
-                                       }
-
-                                       @Override
-                                       protected void done() {
-                                               try {
-                                                       get();
-                                                       for (BookInfo book : selected) {
-                                                               informer.setCached(book, true);
-                                                       }
-                                               } catch (Exception e) {
-                                                       UiHelper.error(BookPopup.this.getParent(),
-                                                                       e.getLocalizedMessage(), "IOException", e);
-                                               }
-                                       }
-                               }.execute();
-                       }
-               });
-
-               return refresh;
-       }
-
-       /**
-        * Create the delete menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemDelete() {
-               JMenuItem delete = new JMenuItem(trans(StringIdGui.MENU_EDIT_DELETE),
-                               KeyEvent.VK_D);
-               delete.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               // final GuiReaderBook selectedBook =
-                               // mainPanel.getSelectedBook();
-                               // if (selectedBook != null && selectedBook.getInfo().getMeta()
-                               // != null) {
-                               //
-                               // final MetaData meta = selectedBook.getInfo().getMeta();
-                               // int rep = JOptionPane.showConfirmDialog(GuiReaderFrame.this,
-                               // trans(StringIdGui.SUBTITLE_DELETE, meta.getLuid(),
-                               // meta.getTitle()),
-                               // trans(StringIdGui.TITLE_DELETE),
-                               // JOptionPane.OK_CANCEL_OPTION);
-                               //
-                               // if (rep == JOptionPane.OK_OPTION) {
-                               // mainPanel.outOfUi(null, true, new Runnable() {
-                               // @Override
-                               // public void run() {
-                               // reader.delete(meta.getLuid());
-                               // mainPanel.unsetSelectedBook();
-                               // }
-                               // });
-                               // }
-                               // }
-                       }
-               });
-
-               return delete;
-       }
-
-       /**
-        * Create the properties menu item.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemProperties() {
-               JMenuItem delete = new JMenuItem(
-                               trans(StringIdGui.MENU_FILE_PROPERTIES), KeyEvent.VK_P);
-               delete.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               // final GuiReaderBook selectedBook =
-                               // mainPanel.getSelectedBook();
-                               // if (selectedBook != null) {
-                               // mainPanel.outOfUi(null, false, new Runnable() {
-                               // @Override
-                               // public void run() {
-                               // new GuiReaderPropertiesFrame(lib,
-                               // selectedBook.getInfo().getMeta())
-                               // .setVisible(true);
-                               // }
-                               // });
-                               // }
-                       }
-               });
-
-               return delete;
-       }
-
-       /**
-        * Create the open menu item for a book, a source/type or an author.
-        * 
-        * @return the item
-        */
-       public JMenuItem createMenuItemOpenBook() {
-               JMenuItem open = new JMenuItem(trans(StringIdGui.MENU_FILE_OPEN),
-                               KeyEvent.VK_O);
-               open.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent e) {
-                               final BookInfo book = informer.getUniqueSelected();
-                               if (book != null) {
-                                       Actions.openExternal(lib, book.getMeta(),
-                                                       BookPopup.this.getParent(), new Runnable() {
-                                                               @Override
-                                                               public void run() {
-                                                                       informer.setCached(book, true);
-                                                               }
-                                                       });
-                               }
-                       }
-               });
-
-               return open;
-       }
-
-       /**
-        * Create the SetCover menu item for a book to change the linked source
-        * cover.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemSetCoverForSource() {
-               JMenuItem open = new JMenuItem(
-                               trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_SOURCE),
-                               KeyEvent.VK_C);
-               open.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent ae) {
-                               // final GuiReaderBook selectedBook =
-                               // mainPanel.getSelectedBook();
-                               // if (selectedBook != null) {
-                               // BasicLibrary lib = lib;
-                               // String luid = selectedBook.getInfo().getMeta().getLuid();
-                               // String source = selectedBook.getInfo().getMeta().getSource();
-                               //
-                               // try {
-                               // lib.setSourceCover(source, luid);
-                               // } catch (IOException e) {
-                               // error(e.getLocalizedMessage(), "IOException", e);
-                               // }
-                               //
-                               // GuiReaderBookInfo sourceInfo =
-                               // GuiReaderBookInfo.fromSource(lib, source);
-                               // GuiReaderCoverImager.clearIcon(sourceInfo);
-                               // }
-                       }
-               });
-
-               return open;
-       }
-
-       /**
-        * Create the SetCover menu item for a book to change the linked source
-        * cover.
-        * 
-        * @return the item
-        */
-       private JMenuItem createMenuItemSetCoverForAuthor() {
-               JMenuItem open = new JMenuItem(
-                               trans(StringIdGui.MENU_EDIT_SET_COVER_FOR_AUTHOR),
-                               KeyEvent.VK_A);
-               open.addActionListener(new ActionListener() {
-                       @Override
-                       public void actionPerformed(ActionEvent ae) {
-                               // final GuiReaderBook selectedBook =
-                               // mainPanel.getSelectedBook();
-                               // if (selectedBook != null) {
-                               // String luid = selectedBook.getInfo().getMeta().getLuid();
-                               // String author = selectedBook.getInfo().getMeta().getAuthor();
-                               //
-                               // try {
-                               // lib.setAuthorCover(author, luid);
-                               // } catch (IOException e) {
-                               // error(e.getLocalizedMessage(), "IOException", e);
-                               // }
-                               //
-                               // GuiReaderBookInfo authorInfo =
-                               // GuiReaderBookInfo.fromAuthor(lib, author);
-                               // GuiReaderCoverImager.clearIcon(authorInfo);
-                               // }
-                       }
-               });
-
-               return open;
-       }
-}
diff --git a/src/be/nikiroo/fanfix_swing/gui/importer/ImporterFrame.java b/src/be/nikiroo/fanfix_swing/gui/importer/ImporterFrame.java
new file mode 100644 (file)
index 0000000..b59215b
--- /dev/null
@@ -0,0 +1,253 @@
+package be.nikiroo.fanfix_swing.gui.importer;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Point;
+import java.awt.Toolkit;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.ListCellRenderer;
+import javax.swing.ListSelectionModel;
+
+import be.nikiroo.fanfix.Instance;
+import be.nikiroo.fanfix.bundles.StringIdGui;
+import be.nikiroo.fanfix.library.LocalLibrary;
+import be.nikiroo.fanfix.reader.BasicReader;
+import be.nikiroo.fanfix.supported.BasicSupport;
+import be.nikiroo.fanfix_swing.Actions;
+import be.nikiroo.fanfix_swing.gui.SearchBar;
+import be.nikiroo.fanfix_swing.gui.book.BookBlock;
+import be.nikiroo.fanfix_swing.gui.book.BookInfo;
+import be.nikiroo.fanfix_swing.gui.book.BookLine;
+import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.Progress.ProgressListener;
+
+public class ImporterFrame extends JFrame {
+       private class ListModel extends DefaultListModel<ImporterItem> {
+               public void fireElementChanged(int index) {
+                       if (index >= 0) {
+                               fireContentsChanged(this, index, index);
+                       }
+               }
+
+               public void fireElementChanged(ImporterItem element) {
+                       int index = indexOf(element);
+                       if (index >= 0) {
+                               fireContentsChanged(this, index, index);
+                       }
+               }
+       }
+
+       private JList<ImporterItem> list;
+       private ListModel data = new ListModel();
+       private List<ImporterItem> items = new ArrayList<ImporterItem>();
+       private String filter = "";
+       private int hoveredIndex = -1;
+
+       public ImporterFrame() {
+               setLayout(new BorderLayout());
+
+               list = new JList<ImporterItem>(data);
+               this.add(list, BorderLayout.CENTER);
+
+               list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+               list.setSelectedIndex(0);
+               list.setCellRenderer(generateRenderer());
+               list.setVisibleRowCount(5);
+
+               list.addMouseMotionListener(new MouseAdapter() {
+                       @Override
+                       public void mouseMoved(MouseEvent me) {
+                               Point p = new Point(me.getX(), me.getY());
+                               int index = list.locationToIndex(p);
+                               if (index != hoveredIndex) {
+                                       int oldIndex = hoveredIndex;
+                                       hoveredIndex = index;
+                                       data.fireElementChanged(oldIndex);
+                                       data.fireElementChanged(index);
+                               }
+                       }
+               });
+               list.addMouseListener(new MouseAdapter() {
+                       @Override
+                       public void mouseExited(MouseEvent e) {
+                               if (hoveredIndex > -1) {
+                                       int oldIndex = hoveredIndex;
+                                       hoveredIndex = -1;
+                                       data.fireElementChanged(oldIndex);
+                               }
+                       }
+               });
+
+               JPanel top = new JPanel();
+               top.setLayout(new BorderLayout());
+
+               final SearchBar search = new SearchBar();
+               top.add(search, BorderLayout.CENTER);
+
+               search.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               filter = search.getText();
+                               filter();
+                       }
+               });
+
+               JButton clear = new JButton("Clear");
+               top.add(clear, BorderLayout.EAST);
+               clear.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               boolean changed = false;
+                               for (int i = 0; i < items.size(); i++) {
+                                       if (items.get(i).isDone()) {
+                                               items.remove(i--);
+                                               changed = true;
+                                       }
+                               }
+
+                               if (changed) {
+                                       filter();
+                               }
+                       }
+               });
+
+               this.add(top, BorderLayout.NORTH);
+
+               setSize(800, 600);
+       }
+
+       /**
+        * Ask for and import an {@link URL} into the main {@link LocalLibrary}.
+        * <p>
+        * Should be called inside the UI thread.
+        * 
+        * @param parent
+        *            a container we can use to display the {@link URL} chooser and
+        *            to show error messages if any
+        * @param onSuccess
+        *            Action to execute on success
+        */
+       public void imprtUrl(final Container parent, final Runnable onSuccess) {
+               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.startsWith("https://"))) {
+                       clipboard = "";
+               }
+
+               Object url = JOptionPane.showInputDialog(parent,
+                               Instance.getInstance().getTransGui()
+                                               .getString(StringIdGui.SUBTITLE_IMPORT_URL),
+                               Instance.getInstance().getTransGui()
+                                               .getString(StringIdGui.TITLE_IMPORT_URL),
+                               JOptionPane.QUESTION_MESSAGE, null, null, clipboard);
+
+               Progress pg = new Progress();
+               String basename = null;
+               try {
+                       BasicSupport support = BasicSupport
+                                       .getSupport(BasicReader.getUrl(url.toString()));
+                       basename = support.getType().getSourceName();
+               } catch (Exception e) {
+               }
+
+               add(pg, basename); // TODO: what when null?
+
+               if (url != null && !url.toString().isEmpty()) {
+                       Actions.imprt(parent, url.toString(), pg, onSuccess);
+               }
+               // TODO what when not ok?
+
+               setVisible(true);
+       }
+
+       /**
+        * Ask for and import a {@link File} into the main {@link LocalLibrary}.
+        * <p>
+        * Should be called inside the UI thread.
+        * 
+        * @param parent
+        *            a container we can use to display the {@link File} chooser and
+        *            to show error messages if any
+        * @param onSuccess
+        *            Action to execute on success
+        */
+
+       public void imprtFile(final Container parent, final Runnable onSuccess) {
+               JFileChooser fc = new JFileChooser();
+
+               Progress pg = new Progress();
+               add(pg, "File");
+
+               if (fc.showOpenDialog(parent) != JFileChooser.CANCEL_OPTION) {
+                       Object url = fc.getSelectedFile().getAbsolutePath();
+                       if (url != null && !url.toString().isEmpty()) {
+                               Actions.imprt(parent, url.toString(), pg, onSuccess);
+                       }
+               }
+
+               setVisible(true);
+       }
+
+       private void add(Progress pg, final String basename) {
+               final ImporterItem item = new ImporterItem(pg, basename);
+               item.addActionListener(new ActionListener() {
+                       @Override
+                       public void actionPerformed(ActionEvent e) {
+                               data.fireElementChanged(item);
+                       }
+               });
+
+               items.add(item);
+               filter();
+       }
+
+       private void filter() {
+               data.clear();
+               for (ImporterItem item : items) {
+                       String text = item.getStoryName() + " " + item.getAction();
+                       if (filter.isEmpty() || text.isEmpty()
+                                       || text.toLowerCase().contains(filter.toLowerCase())) {
+                               data.addElement(item);
+                       }
+               }
+               list.repaint();
+       }
+
+       private ListCellRenderer<ImporterItem> generateRenderer() {
+               return new ListCellRenderer<ImporterItem>() {
+                       @Override
+                       public Component getListCellRendererComponent(
+                                       JList<? extends ImporterItem> list, ImporterItem item,
+                                       int index, boolean isSelected, boolean cellHasFocus) {
+                               item.setSelected(isSelected);
+                               item.setHovered(index == hoveredIndex);
+                               return item;
+                       }
+               };
+       }
+}
diff --git a/src/be/nikiroo/fanfix_swing/gui/importer/ImporterItem.java b/src/be/nikiroo/fanfix_swing/gui/importer/ImporterItem.java
new file mode 100644 (file)
index 0000000..2e88c63
--- /dev/null
@@ -0,0 +1,171 @@
+package be.nikiroo.fanfix_swing.gui.importer;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Rectangle;
+
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+
+import be.nikiroo.fanfix_swing.gui.utils.ListenerPanel;
+import be.nikiroo.utils.Progress;
+import be.nikiroo.utils.Progress.ProgressListener;
+
+public class ImporterItem extends ListenerPanel {
+       static public final String CHANGE = "change";
+
+       private String basename = "";
+       private String storyName = "";
+       private String action = "";
+       private double progress = -1;
+
+       private boolean hovered;
+       private boolean selected;
+       private boolean done;
+
+       private JLabel labelName;
+       private JLabel labelAction;
+
+       public ImporterItem(Progress pg, String basename) {
+               this.basename = basename == null ? "" : basename;
+
+               labelName = new JLabel(getStoryName());
+               labelAction = new JLabel(getAction());
+
+               setDone(true);
+               setDone(false); // to trigger the colour change
+
+               setLayout(new BorderLayout());
+               add(labelName, BorderLayout.NORTH);
+               add(labelAction, BorderLayout.SOUTH);
+
+               init(pg);
+       }
+
+       static public Color getBackground(boolean enabled, boolean selected,
+                       boolean hovered) {
+               Color color = new Color(255, 255, 255, 0);
+               if (!enabled) {
+               } else if (selected && !hovered) {
+                       color = new Color(80, 80, 100, 40);
+               } else if (!selected && hovered) {
+                       color = new Color(230, 230, 255, 100);
+               } else if (selected && hovered) {
+                       color = new Color(200, 200, 255, 100);
+               }
+
+               return color;
+       }
+
+       public String getStoryName() {
+               return basename + ": " + storyName;
+       }
+
+       public String getAction() {
+               // space is for the default size
+               if (done) {
+                       return "Done";
+               }
+
+               return action.isEmpty() ? " " : action;
+       }
+
+       public boolean isSelected() {
+               return selected;
+       }
+
+       public void setSelected(boolean selected) {
+               if (this.selected != selected) {
+                       this.selected = selected;
+                       setBackground(getBackground(isEnabled(), selected, hovered));
+               }
+       }
+
+       public boolean isHovered() {
+               return hovered;
+       }
+
+       public void setHovered(boolean hovered) {
+               if (this.hovered != hovered) {
+                       this.hovered = hovered;
+                       setBackground(getBackground(isEnabled(), selected, hovered));
+               }
+       }
+
+       @Override
+       public void setEnabled(boolean enabled) {
+               if (isEnabled() != enabled) {
+                       super.setEnabled(enabled);
+                       setBackground(getBackground(isEnabled(), selected, hovered));
+               }
+       }
+
+       public boolean isDone() {
+               return done;
+       }
+
+       public void setDone(boolean done) {
+               if (this.done != done) {
+                       this.done = done;
+                       if (done) {
+                               labelAction.setForeground(Color.green.darker());
+                               labelAction
+                                               .setFont(labelAction.getFont().deriveFont(Font.BOLD));
+                       } else {
+                               labelAction.setForeground(Color.gray);
+                               labelAction
+                                               .setFont(labelAction.getFont().deriveFont(Font.PLAIN));
+                       }
+               }
+       }
+
+       private void init(final Progress pg) {
+               pg.addProgressListener(new ProgressListener() {
+                       @Override
+                       public void progress(Progress notUsed, String currentAction) {
+                               // TODO: get/setSubject on Progress?
+                               currentAction = currentAction == null ? "" : currentAction;
+
+                               if (storyName.isEmpty()
+                                               && !currentAction.equals("Initialising")) {
+                                       storyName = currentAction;
+                               }
+
+                               if (storyName.equals(currentAction)) {
+                                       currentAction = "";
+                               }
+
+                               if (pg.getRelativeProgress() != progress
+                                               || !action.equals(currentAction)) {
+                                       progress = pg.getRelativeProgress();
+                                       action = currentAction;
+
+                                       // The rest must be done in the UI thead
+                                       SwingUtilities.invokeLater(new Runnable() {
+                                               @Override
+                                               public void run() {
+                                                       setDone(pg.isDone());
+                                                       labelName.setText(" " + getStoryName());
+                                                       labelAction.setText(" " + getAction());
+                                                       fireActionPerformed(CHANGE);
+                                               }
+                                       });
+                               }
+                       }
+               });
+       }
+
+       @Override
+       public void paint(Graphics g) {
+               Rectangle clip = g.getClipBounds();
+               if (!(clip == null || clip.getWidth() <= 0 || clip.getHeight() <= 0)) {
+                       g.setColor(new Color(200, 200, 255, 128));
+                       g.fillRect(clip.x, clip.y, (int) Math.round(clip.width * progress),
+                                       clip.height);
+               }
+
+               super.paint(g);
+       }
+}
\ No newline at end of file