import javax.swing.DefaultListModel;
import javax.swing.JList;
-import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
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.utils.ListenerPanel;
import be.nikiroo.fanfix_swing.gui.utils.UiHelper;
-public class BooksPanel extends JPanel {
- class ListModel extends DefaultListModel<BookInfo> {
+public class BooksPanel extends ListenerPanel {
+ private class ListModel extends DefaultListModel<BookInfo> {
public void fireElementChanged(BookInfo element) {
int index = indexOf(element);
if (index >= 0) {
}
}
+ 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;
searchBar.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- reload(searchBar.getText());
+ filter(searchBar.getText());
}
});
updateBookQueue.clear();
}
- reload(searchBar.getText());
+ filter(searchBar.getText());
}
// cannot be NULL
- private void reload(String filter) {
+ private void filter(String filter) {
data.clear();
for (BookInfo bookInfo : bookInfos) {
if (filter.isEmpty() || bookInfo.getMainInfo().toLowerCase().contains(filter.toLowerCase())) {
}
return null;
}
+
+ @Override
+ public void invalidateCache() {
+ fireActionPerformed(INVALIDATE_CACHE);
+ }
});
list.addMouseMotionListener(new MouseAdapter() {
*/
public BookInfo getHighlight() {
BasicLibrary lib = Instance.getInstance().getLibrary();
- if (tabs.getSelectedComponent() == sourceTab) {
- List<String> sel = sourceTab.getSelectedElements();
- if (!sel.isEmpty()) {
+ List<String> sel = sourceTab.getSelectedElements();
+ if (!sel.isEmpty()) {
+ if (tabs.getSelectedComponent() == sourceTab) {
return BookInfo.fromSource(lib, sel.get(0));
- }
- } else if (tabs.getSelectedComponent() == authorTab) {
- List<String> sel = authorTab.getSelectedElements();
- if (!sel.isEmpty()) {
+ } else if (tabs.getSelectedComponent() == authorTab) {
return BookInfo.fromAuthor(lib, sel.get(0));
- }
- } else if (tabs.getSelectedComponent() == tagsTab) {
- List<String> sel = tagsTab.getSelectedElements();
- if (!sel.isEmpty()) {
+ } else if (tabs.getSelectedComponent() == tagsTab) {
return BookInfo.fromTag(lib, sel.get(0));
}
}
return tagsTab.getSelectedElements();
}
+ /**
+ * Reload all the data from the 3 tabs.
+ */
+ public void reloadData() {
+ sourceTab.reloadData();
+ authorTab.reloadData();
+ tagsTab.reloadData();
+ }
+
/**
* Adds the specified action listener to receive action events from this
* {@link SearchBar}.
package be.nikiroo.fanfix_swing.gui;
-import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
-import java.io.IOException;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
-import javax.swing.JPanel;
import javax.swing.JSplitPane;
-import javax.swing.SwingWorker;
import be.nikiroo.utils.Version;
}
books = new BooksPanel(true);
+
browser.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
details.setBook(browser.getHighlight());
}
});
+ books.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (BooksPanel.INVALIDATE_CACHE.equals(e.getActionCommand())) {
+ browser.reloadData();
+ }
+ }
+ });
JSplitPane split = split(other, books, orientationH, 0.5, 0);
import java.awt.event.KeyEvent;
import javax.swing.JButton;
-import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
+import be.nikiroo.fanfix_swing.gui.utils.ListenerPanel;
import be.nikiroo.fanfix_swing.gui.utils.UiHelper;
import be.nikiroo.fanfix_swing.images.IconGenerator;
import be.nikiroo.fanfix_swing.images.IconGenerator.Icon;
*
* @author niki
*/
-public class SearchBar extends JPanel {
+public class SearchBar extends ListenerPanel {
static private final long serialVersionUID = 1L;
private JButton search;
text.requestFocus();
if (realTime) {
- fireActionPerformed();
+ fireActionPerformed(getText());
}
}
});
clear.setVisible(!empty);
if (realTime) {
- fireActionPerformed();
+ fireActionPerformed(getText());
}
}
});
@Override
public void actionPerformed(ActionEvent e) {
if (!realTime) {
- fireActionPerformed();
+ fireActionPerformed(getText());
}
}
});
clear.setVisible(false);
text.requestFocus();
- fireActionPerformed();
+ fireActionPerformed(getText());
}
});
add(clear, BorderLayout.EAST);
}
- /**
- * Adds the specified action listener to receive action events from this
- * {@link SearchBar}.
- *
- * @param listener the action listener to be added
- */
- public synchronized void addActionListener(ActionListener listener) {
- listenerList.add(ActionListener.class, listener);
- }
-
- /**
- * Removes the specified action listener so that it no longer receives action
- * events from this {@link SearchBar}.
- *
- * @param listener the action listener to be removed
- */
- public synchronized void removeActionListener(ActionListener listener) {
- listenerList.remove(ActionListener.class, listener);
- }
-
- /**
- * Notify the listeners of an action.
- */
- protected void fireActionPerformed() {
- ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, getText());
- Object[] listeners = listenerList.getListenerList();
- for (int i = listeners.length - 2; i >= 0; i -= 2) {
- if (listeners[i] == ActionListener.class) {
- ((ActionListener) listeners[i + 1]).actionPerformed(e);
- }
- }
- }
-
/**
* Return the current text displayed by this {@link SearchBar}.
*
public void fireElementChanged(BookInfo book);
+ public void invalidateCache();
}
/**
lib.changeAuthor(luid, fChangeTo, null);
}
}
- // TODO: ^-- this can create new sources/authors, update maybe required?
return null;
}
@Override
protected void done() {
try {
- // Reload anyway
- for (BookInfo book : selected) {
- informer.fireElementChanged(book);
- }
+ // 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();
+
+ // TODO: not enough!!
+ // after move, item disappears in the list, probably caused by the Library
+ // itself
+ // Even if problems occurred, still invalidate the cache
get();
} catch (Exception e) {
UiHelper.error(BookPopup.this.getParent(), e.getLocalizedMessage(), "IOException", e);
import java.util.List;
import java.util.Map;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingWorker;
-import javax.swing.UIDefaults;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
-import javax.swing.plaf.TreeUI;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreePath;
-import be.nikiroo.fanfix.Instance;
import be.nikiroo.fanfix_swing.gui.SearchBar;
+import be.nikiroo.fanfix_swing.gui.utils.ListenerPanel;
import be.nikiroo.fanfix_swing.gui.utils.TreeCellSpanner;
+import be.nikiroo.fanfix_swing.gui.utils.TreeSnapshot;
import be.nikiroo.fanfix_swing.gui.utils.UiHelper;
import be.nikiroo.fanfix_swing.images.IconGenerator;
import be.nikiroo.fanfix_swing.images.IconGenerator.Icon;
import be.nikiroo.fanfix_swing.images.IconGenerator.Size;
-public abstract class BasicTab<T> extends JPanel {
+public abstract class BasicTab<T> extends ListenerPanel {
private int totalCount = 0;
private List<String> selectedElements = new ArrayList<String>();
private T data;
private int index;
private JTree tree;
+ private DefaultMutableTreeNode root;
private SearchBar searchBar;
public BasicTab(int index, String listenerCommand) {
data = createEmptyData();
totalCount = 0;
- final DefaultMutableTreeNode root = new DefaultMutableTreeNode();
+ root = new DefaultMutableTreeNode();
tree = new JTree(root);
tree.setUI(new BasicTreeUI());
BasicTab.this.selectedElements = selectedElements;
- fireActionPerformed();
+ fireActionPerformed(BasicTab.this.listenerCommand);
}
});
searchBar.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
- root.removeAllChildren();
- loadData(root, data, searchBar.getText());
- ((DefaultTreeModel) tree.getModel()).reload();
- fireActionPerformed();
+ reloadData();
}
});
+ reloadData();
+ }
+
+ public void reloadData() {
+ final TreeSnapshot snapshot = new TreeSnapshot(tree);
SwingWorker<Map<String, List<String>>, Integer> worker = new SwingWorker<Map<String, List<String>>, Integer>() {
@Override
protected Map<String, List<String>> doInBackground() throws Exception {
- return Instance.getInstance().getLibrary().getSourcesGrouped();
+ fillData(data);
+ return null;
}
@Override
protected void done() {
- fillData(data);
+ try {
+ get();
+ } catch (Exception e) {
+ // TODO: error
+ }
+
+ // TODO: update is flickering...
+
root.removeAllChildren();
totalCount = loadData(root, data, searchBar.getText());
((DefaultTreeModel) tree.getModel()).reload();
- fireActionPerformed();
+ snapshot.apply();
+
+ fireActionPerformed(listenerCommand);
}
};
worker.execute();
tree.clearSelection();
}
- /**
- * Adds the specified action listener to receive action events from this
- * {@link SearchBar}.
- *
- * @param listener the action listener to be added
- */
- public synchronized void addActionListener(ActionListener listener) {
- listenerList.add(ActionListener.class, listener);
- }
-
- /**
- * Removes the specified action listener so that it no longer receives action
- * events from this {@link SearchBar}.
- *
- * @param listener the action listener to be removed
- */
- public synchronized void removeActionListener(ActionListener listener) {
- listenerList.remove(ActionListener.class, listener);
- }
-
- /**
- * Notify the listeners of an action.
- */
- protected void fireActionPerformed() {
- ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, listenerCommand);
- Object[] listeners = listenerList.getListenerList();
- for (int i = listeners.length - 2; i >= 0; i -= 2) {
- if (listeners[i] == ActionListener.class) {
- ((ActionListener) listeners[i + 1]).actionPerformed(e);
- }
- }
- }
-
protected boolean checkFilter(String filter, String value) {
return (filter == null || filter.isEmpty() || value.toLowerCase().contains(filter.toLowerCase()));
}
--- /dev/null
+package be.nikiroo.fanfix_swing.gui.utils;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import javax.swing.JPanel;
+
+import be.nikiroo.fanfix_swing.gui.SearchBar;
+
+/**
+ * A {@link JPanel} with the default {@link ActionListener} add/remove/fire
+ * methods.
+ *
+ * @author niki
+ */
+public class ListenerPanel extends JPanel {
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Adds the specified action listener to receive action events from this
+ * {@link SearchBar}.
+ *
+ * @param listener the action listener to be added
+ */
+ public synchronized void addActionListener(ActionListener listener) {
+ listenerList.add(ActionListener.class, listener);
+ }
+
+ /**
+ * Removes the specified action listener so that it no longer receives action
+ * events from this {@link SearchBar}.
+ *
+ * @param listener the action listener to be removed
+ */
+ public synchronized void removeActionListener(ActionListener listener) {
+ listenerList.remove(ActionListener.class, listener);
+ }
+
+ /**
+ * Notify the listeners of an action.
+ *
+ * @param listenerCommand A string that may specify a command (possibly one of
+ * several) associated with the event
+ */
+ protected void fireActionPerformed(String listenerCommand) {
+ ActionEvent e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, listenerCommand);
+ Object[] listeners = listenerList.getListenerList();
+ for (int i = listeners.length - 2; i >= 0; i -= 2) {
+ if (listeners[i] == ActionListener.class) {
+ ((ActionListener) listeners[i + 1]).actionPerformed(e);
+ }
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.fanfix_swing.gui.utils;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.swing.JTree;
+import javax.swing.tree.TreeModel;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+
+public class TreeSnapshot {
+ private interface NodeAction {
+ public void run(TreeNode node);
+ }
+
+ private JTree tree;
+ private TreePath[] selectionPaths;
+ private List<TreePath> expanded;
+
+ public TreeSnapshot(JTree tree) {
+ this.tree = tree;
+
+ selectionPaths = tree.getSelectionPaths();
+ if (selectionPaths == null) {
+ selectionPaths = new TreePath[0];
+ }
+
+ expanded = new ArrayList<TreePath>();
+ forEach(tree, new NodeAction() {
+ @Override
+ public void run(TreeNode node) {
+ TreePath path = nodeToPath(node);
+ if (path != null) {
+ if (TreeSnapshot.this.tree.isExpanded(path)) {
+ expanded.add(path);
+ }
+ }
+ }
+ });
+ }
+
+ public void apply() {
+ applyTo(tree);
+ }
+
+ public void applyTo(JTree tree) {
+ final List<TreePath> newExpanded = new ArrayList<TreePath>();
+ final List<TreePath> newSlectionPaths = new ArrayList<TreePath>();
+
+ forEach(tree, new NodeAction() {
+ @Override
+ public void run(TreeNode newNode) {
+ TreePath newPath = nodeToPath(newNode);
+ if (newPath != null) {
+ for (TreePath path : selectionPaths) {
+ if (newPath.toString().equals(path.toString())) {
+ newSlectionPaths.add(newPath);
+ if (expanded.contains(path)) {
+ newExpanded.add(newPath);
+ }
+ }
+ }
+ }
+ }
+ });
+
+ for (TreePath newPath : newExpanded) {
+ tree.expandPath(newPath);
+ }
+
+ tree.setSelectionPaths(newSlectionPaths.toArray(new TreePath[0]));
+ }
+
+ private void forEach(JTree tree, NodeAction action) {
+ forEach(tree.getModel(), tree.getModel().getRoot(), action);
+ }
+
+ private void forEach(TreeModel model, Object parent, NodeAction action) {
+ if (!(parent instanceof TreeNode))
+ return;
+
+ TreeNode node = (TreeNode) parent;
+
+ action.run(node);
+ int count = model.getChildCount(node);
+ for (int i = 0; i < count; i++) {
+ Object child = model.getChild(node, i);
+ forEach(model, child, action);
+ }
+ }
+
+ private static TreePath nodeToPath(TreeNode node) {
+ List<Object> nodes = new LinkedList<Object>();
+ if (node != null) {
+ nodes.add(node);
+ node = node.getParent();
+ while (node != null) {
+ nodes.add(0, node);
+ node = node.getParent();
+ }
+ }
+
+ return nodes.isEmpty() ? null : new TreePath(nodes.toArray());
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Tree Snapshot of: ").append(tree).append("\n");
+ builder.append("Selected paths:\n");
+ for (TreePath path : selectionPaths) {
+ builder.append("\t").append(path).append("\n");
+ }
+ builder.append("Expanded paths:\n");
+ for (TreePath epath : expanded) {
+ builder.append("\t").append(epath).append("\n");
+ }
+
+ return builder.toString();
+ }
+}
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
+import javax.swing.JTree;
import javax.swing.SwingUtilities;
import be.nikiroo.fanfix.Instance;