import java.awt.Component;
import java.awt.Dimension;
import java.awt.Image;
-import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.util.Map;
import java.util.concurrent.ExecutionException;
-import javax.swing.DefaultListModel;
import javax.swing.JList;
-import javax.swing.JPopupMenu;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.SwingWorker;
import be.nikiroo.fanfix_swing.gui.book.BookInfo;
import be.nikiroo.fanfix_swing.gui.book.BookLine;
import be.nikiroo.fanfix_swing.gui.book.BookPopup;
+import be.nikiroo.fanfix_swing.gui.book.BookPopup.Informer;
import be.nikiroo.fanfix_swing.gui.utils.DelayWorker;
+import be.nikiroo.fanfix_swing.gui.utils.ListModel;
+import be.nikiroo.fanfix_swing.gui.utils.ListModel.Predicate;
import be.nikiroo.fanfix_swing.gui.utils.ListenerPanel;
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(this, index, index);
- }
- }
- }
-
static public final String INVALIDATE_CACHE = "invalidate_cache";
- private List<BookInfo> bookInfos = new ArrayList<BookInfo>();
private Map<BookInfo, BookLine> books = new HashMap<BookInfo, BookLine>();
private boolean seeWordCount;
private boolean listMode;
private JList<BookInfo> list;
- private int hoveredIndex = -1;
- private ListModel data = new ListModel();
+ private ListModel<BookInfo> data;
private DelayWorker bookCoverUpdater;
private String filter = "";
bookCoverUpdater = new DelayWorker(20);
bookCoverUpdater.start();
- add(UiHelper.scroll(initList(listMode)), BorderLayout.CENTER);
+
+ list = initList();
+ setListMode(listMode);
+ add(UiHelper.scroll(list), BorderLayout.CENTER);
}
// null or empty -> all sources
}
public void load(List<BookInfo> bookInfos) {
- this.bookInfos.clear();
- this.bookInfos.addAll(bookInfos);
+ data.clearItems();
+ data.addAllItems(bookInfos);
bookCoverUpdater.clear();
filter();
}
private void filter() {
- data.clear();
- for (BookInfo bookInfo : bookInfos) {
- if (bookInfo.getMainInfo() == null || filter.isEmpty()
- || bookInfo.getMainInfo().toLowerCase()
- .contains(filter.toLowerCase())) {
- data.addElement(bookInfo);
+ data.filter(new Predicate<BookInfo>() {
+ @Override
+ public boolean test(BookInfo item) {
+ return item.getMainInfo() == null || filter.isEmpty()
+ || item.getMainInfo().toLowerCase()
+ .contains(filter.toLowerCase());
}
- }
- list.repaint();
+ });
}
/**
}
}
- private JList<BookInfo> initList(boolean listMode) {
- final JList<BookInfo> list = new JList<BookInfo>(data);
-
- final JPopupMenu popup = new BookPopup(
- Instance.getInstance().getLibrary(), new BookPopup.Informer() {
- @Override
- public void setCached(BookInfo book, boolean cached) {
- 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() {
- List<BookInfo> selected = new ArrayList<BookInfo>();
- for (int index : list.getSelectedIndices()) {
- selected.add(data.get(index));
- }
-
- return selected;
- }
-
- @Override
- public BookInfo getUniqueSelected() {
- List<BookInfo> selected = getSelected();
- if (selected.size() == 1) {
- return selected.get(0);
- }
- return null;
- }
-
- @Override
- public void invalidateCache() {
- // TODO: also reset the popup menu for sources/author
- fireActionPerformed(INVALIDATE_CACHE);
- }
- });
+ private JList<BookInfo> initList() {
+ final JList<BookInfo> list = new JList<BookInfo>();
+ data = new ListModel<BookInfo>(list, new BookPopup(
+ Instance.getInstance().getLibrary(), initInformer()));
- list.addMouseMotionListener(new MouseAdapter() {
- @Override
- public void mouseMoved(MouseEvent me) {
- if (popup.isShowing())
- return;
-
- Point p = new Point(me.getX(), me.getY());
- int index = list.locationToIndex(p);
- if (index != hoveredIndex) {
- hoveredIndex = index;
- list.repaint();
- }
- }
- });
list.addMouseListener(new MouseAdapter() {
- @Override
- public void mousePressed(MouseEvent e) {
- check(e);
- }
-
- @Override
- public void mouseReleased(MouseEvent e) {
- check(e);
- }
-
- @Override
- public void mouseExited(MouseEvent e) {
- if (popup.isShowing())
- return;
-
- if (hoveredIndex > -1) {
- int index = hoveredIndex;
- hoveredIndex = -1;
- data.fireElementChanged(index);
- }
- }
-
@Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
});
}
}
-
- private void check(MouseEvent e) {
- if (e.isPopupTrigger()) {
- if (list.getSelectedIndices().length <= 1) {
- list.setSelectedIndex(
- list.locationToIndex(e.getPoint()));
- }
-
- popup.show(list, e.getX(), e.getY());
- }
- }
});
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
list.setCellRenderer(generateRenderer());
list.setVisibleRowCount(0);
- this.list = list;
- setListMode(listMode);
- return this.list;
+ return list;
+ }
+
+ private Informer initInformer() {
+ return new BookPopup.Informer() {
+ @Override
+ public void setCached(BookInfo book, boolean cached) {
+ 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() {
+ return data.getSelectedElements();
+ }
+
+ @Override
+ public BookInfo getUniqueSelected() {
+ return data.getUniqueSelectedElement();
+ }
+
+ @Override
+ public void invalidateCache() {
+ // TODO: also reset the popup menu for sources/author
+ fireActionPerformed(INVALIDATE_CACHE);
+ }
+ };
}
private ListCellRenderer<BookInfo> generateRenderer() {
}
book.setSelected(isSelected);
- book.setHovered(index == hoveredIndex);
+ book.setHovered(data.isHovered(index));
return book;
}
};
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.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.fanfix_swing.gui.utils.ListModel;
+import be.nikiroo.fanfix_swing.gui.utils.ListModel.Predicate;
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 ListModel<ImporterItem> data;
private String filter = "";
- private int hoveredIndex = -1;
public ImporterFrame() {
setLayout(new BorderLayout());
- list = new JList<ImporterItem>(data);
- this.add(list, BorderLayout.CENTER);
+ JList<ImporterItem> list = new JList<ImporterItem>();
+ data = new ListModel<ImporterItem>(list);
+ list.setCellRenderer(ListModel.generateRenderer(data));
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);
- }
- }
- });
+ this.add(list, BorderLayout.CENTER);
JPanel top = new JPanel();
top.setLayout(new BorderLayout());
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;
- }
- }
+ boolean changed = data
+ .removeItemIf(new Predicate<ImporterItem>() {
+ @Override
+ public boolean test(ImporterItem item) {
+ return item.isDone();
+ }
+ });
if (changed) {
filter();
}
});
- items.add(item);
+ data.addItem(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>() {
+ data.filter(new Predicate<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;
+ public boolean test(ImporterItem item) {
+ String text = item.getStoryName() + " " + item.getAction();
+ return filter.isEmpty() || text.isEmpty()
+ || text.toLowerCase().contains(filter.toLowerCase());
}
- };
+ });
}
}
import java.awt.BorderLayout;
import java.awt.Color;
+import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import be.nikiroo.fanfix_swing.gui.utils.CoverImager;
+import be.nikiroo.fanfix_swing.gui.utils.ListModel;
import be.nikiroo.fanfix_swing.gui.utils.ListenerPanel;
import be.nikiroo.utils.Progress;
import be.nikiroo.utils.Progress.ProgressListener;
progress = pg.getRelativeProgress();
action = currentAction;
- // The rest must be done in the UI thead
+ // The rest must be done in the UI thread
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
--- /dev/null
+package be.nikiroo.fanfix_swing.gui.utils;
+
+import java.awt.Component;
+import java.awt.Point;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.swing.DefaultListModel;
+import javax.swing.JList;
+import javax.swing.JPopupMenu;
+import javax.swing.ListCellRenderer;
+
+/**
+ * A {@link javax.swing.ListModel} that can maintain 2 lists; one with the
+ * actual data (the elements), and a second one with the items that are
+ * currently displayed (the items).
+ * <p>
+ * It also offers filter options, supports hovered changes and some more utility
+ * functions.
+ *
+ * @author niki
+ *
+ * @param <T>
+ * the type of elements and items (the same type)
+ */
+public class ListModel<T> extends DefaultListModel<T> {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * A filter interface, to check for a condition (note that a Predicate class
+ * already exists in Java 1.8+, and is compatible with this one if you
+ * change the signatures -- but I support java 1.6+).
+ *
+ * @author niki
+ *
+ * @param <T>
+ * the type of elements and items (the same type)
+ */
+ public interface Predicate<T> {
+ /**
+ * Check if an item or an element pass a filter.
+ *
+ * @param item
+ * the item to test
+ *
+ * @return TRUE if the test passed, FALSE if not
+ */
+ public boolean test(T item);
+ }
+
+ /**
+ * A simple interface your elements must implement if you want to use
+ * {@link ListModel#generateRenderer(ListModel)}.
+ *
+ * @author niki
+ */
+ public interface Hoverable {
+ /**
+ * The element is currently selected.
+ *
+ * @param selected
+ * TRUE for selected, FALSE for unselected
+ */
+ public void setSelected(boolean selected);
+
+ /**
+ * The element is currently under the mouse cursor.
+ *
+ * @param hovered
+ * TRUE if it is, FALSE if not
+ */
+ public void setHovered(boolean hovered);
+ }
+
+ private int hoveredIndex;
+ private List<T> items = new ArrayList<T>();
+ private JList<T> list;
+
+ /**
+ * Create a new {@link ListModel}.
+ *
+ * @param list
+ * the {@link JList} we will handle the data of (cannot be NULL)
+ */
+ public ListModel(JList<T> list) {
+ this(list, null);
+ }
+
+ /**
+ * Create a new {@link ListModel}.
+ *
+ * @param list
+ * the {@link JList} we will handle the data of (cannot be NULL)
+ * @param popup
+ * the popup to use and keep track of (can be NULL)
+ */
+ public ListModel(final JList<T> list, final JPopupMenu popup) {
+ this.list = list;
+ list.setModel(this);
+
+ list.addMouseMotionListener(new MouseAdapter() {
+ @Override
+ public void mouseMoved(MouseEvent me) {
+ if (popup != null && popup.isShowing())
+ return;
+
+ Point p = new Point(me.getX(), me.getY());
+ int index = list.locationToIndex(p);
+ if (index != hoveredIndex) {
+ int oldIndex = hoveredIndex;
+ hoveredIndex = index;
+ fireElementChanged(oldIndex);
+ fireElementChanged(index);
+ }
+ }
+ });
+
+ list.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ check(e);
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ check(e);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ if (popup != null && popup.isShowing())
+ return;
+
+ if (hoveredIndex > -1) {
+ int oldIndex = hoveredIndex;
+ hoveredIndex = -1;
+ fireElementChanged(oldIndex);
+ }
+ }
+
+ private void check(MouseEvent e) {
+ if (popup == null) {
+ return;
+ }
+
+ if (e.isPopupTrigger()) {
+ if (list.getSelectedIndices().length <= 1) {
+ list.setSelectedIndex(
+ list.locationToIndex(e.getPoint()));
+ }
+
+ popup.show(list, e.getX(), e.getY());
+ }
+ }
+ });
+ }
+
+ /**
+ * Check if this element is currently under the mouse.
+ *
+ * @param element
+ * the element to check
+ *
+ * @return TRUE if it is
+ */
+ public boolean isHovered(T element) {
+ return indexOf(element) == hoveredIndex;
+ }
+
+ /**
+ * Check if this element is currently under the mouse.
+ *
+ * @param index
+ * the index of the element to check
+ *
+ * @return TRUE if it is
+ */
+ public boolean isHovered(int index) {
+ return index == hoveredIndex;
+ }
+
+ /**
+ * Add an item to the model.
+ *
+ * @param item
+ * the new item to add
+ */
+ public void addItem(T item) {
+ items.add(item);
+ }
+
+ /**
+ * Add items to the model.
+ *
+ * @param items
+ * the new items to add
+ */
+ public void addAllItems(Collection<T> items) {
+ this.items.addAll(items);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from this list, if
+ * it is present (optional operation).
+ *
+ * @param item
+ * the item to remove if possible (can be NULL)
+ *
+ * @return TRUE if one element was removed, FALSE if not found
+ */
+ public boolean removeItem(T item) {
+ return items.remove(item);
+ }
+
+ /**
+ * Remove the items that pass the given filter (or all items if the filter
+ * is NULL).
+ *
+ * @param filter
+ * the filter (if the filter returns TRUE, the item will be
+ * removed)
+ *
+ * @return TRUE if at least one item was removed
+ */
+ public boolean removeItemIf(Predicate<T> filter) {
+ boolean changed = false;
+ if (filter == null) {
+ changed = !items.isEmpty();
+ clearItems();
+ } else {
+ for (int i = 0; i < items.size(); i++) {
+ if (filter.test(items.get(i))) {
+ items.remove(i--);
+ changed = true;
+ }
+ }
+ }
+
+ return changed;
+ }
+
+ /**
+ * Removes all the items from this model.
+ */
+ public void clearItems() {
+ items.clear();
+ }
+
+ /**
+ * Filter the current elements.
+ * <p>
+ * This method will clear all the elements then look into all the items:
+ * those that pass the given filter will be copied as elements.
+ *
+ * @param filter
+ * the filter to select which elements to keep; an item that pass
+ * the filter will be copied as an element (can be NULL, in that
+ * case all items will be copied as elements)
+ */
+ public void filter(Predicate<T> filter) {
+ clear();
+ for (T item : items) {
+ if (filter == null || filter.test(item)) {
+ addElement(item);
+ }
+ }
+
+ list.repaint();
+ }
+
+ /**
+ * Return the currently selected elements.
+ *
+ * @return the selected elements
+ */
+ public List<T> getSelectedElements() {
+ List<T> selected = new ArrayList<T>();
+ for (int index : list.getSelectedIndices()) {
+ selected.add(get(index));
+ }
+
+ return selected;
+ }
+
+ /**
+ * Return the selected element if <b>one</b> and <b>only one</b> element is
+ * selected. I.E., if zero, two or more elements are selected, NULL will be
+ * returned.
+ *
+ * @return the element if it is the only selected element, NULL otherwise
+ */
+ public T getUniqueSelectedElement() {
+ List<T> selected = getSelectedElements();
+ if (selected.size() == 1) {
+ return selected.get(0);
+ }
+
+ return null;
+ }
+
+ /**
+ * Notify that this element has been changed.
+ *
+ * @param index
+ * the index of the element
+ */
+ public void fireElementChanged(int index) {
+ if (index >= 0) {
+ fireContentsChanged(this, index, index);
+ }
+ }
+
+ /**
+ * Notify that this element has been changed.
+ *
+ * @param element
+ * the element
+ */
+ public void fireElementChanged(T element) {
+ int index = indexOf(element);
+ if (index >= 0) {
+ fireContentsChanged(this, index, index);
+ }
+ }
+
+ /**
+ * Generate a {@link ListCellRenderer} that supports {@link Hoverable}
+ * elements.
+ *
+ * @param <T>
+ * the type of elements and items (the same type), which should
+ * implement {@link Hoverable} (it will not cause issues if not,
+ * but then, it will be a default renderer)
+ * @param model
+ * the model to use
+ *
+ * @return a suitable, {@link Hoverable} compatible renderer
+ */
+ static public <T extends Component> ListCellRenderer<T> generateRenderer(
+ final ListModel<T> model) {
+ return new ListCellRenderer<T>() {
+ @Override
+ public Component getListCellRendererComponent(
+ JList<? extends T> list, T item, int index,
+ boolean isSelected, boolean cellHasFocus) {
+ if (item instanceof Hoverable) {
+ Hoverable hoverable = (Hoverable) item;
+ hoverable.setSelected(isSelected);
+ hoverable.setHovered(model.isHovered(index));
+ }
+
+ return item;
+ }
+ };
+ }
+}