From 2a03ecc0ae449a05763db2d47935a4c256cd092f Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Wed, 8 Apr 2020 11:53:08 +0200 Subject: [PATCH] work on refresh after popup action --- .../nikiroo/fanfix_swing/gui/BooksPanel.java | 19 ++- .../fanfix_swing/gui/BrowserPanel.java | 25 ++-- .../nikiroo/fanfix_swing/gui/MainFrame.java | 13 +- .../nikiroo/fanfix_swing/gui/SearchBar.java | 45 +------ .../fanfix_swing/gui/book/BookPopup.java | 14 +- .../fanfix_swing/gui/browser/BasicTab.java | 73 ++++------- .../fanfix_swing/gui/utils/ListenerPanel.java | 54 ++++++++ .../fanfix_swing/gui/utils/TreeSnapshot.java | 122 ++++++++++++++++++ .../fanfix_swing/gui/utils/UiHelper.java | 1 + 9 files changed, 253 insertions(+), 113 deletions(-) create mode 100644 src/be/nikiroo/fanfix_swing/gui/utils/ListenerPanel.java create mode 100644 src/be/nikiroo/fanfix_swing/gui/utils/TreeSnapshot.java diff --git a/src/be/nikiroo/fanfix_swing/gui/BooksPanel.java b/src/be/nikiroo/fanfix_swing/gui/BooksPanel.java index 579f394..73de77e 100644 --- a/src/be/nikiroo/fanfix_swing/gui/BooksPanel.java +++ b/src/be/nikiroo/fanfix_swing/gui/BooksPanel.java @@ -18,7 +18,6 @@ import java.util.concurrent.ExecutionException; import javax.swing.DefaultListModel; import javax.swing.JList; -import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; @@ -33,10 +32,11 @@ 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.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 { +public class BooksPanel extends ListenerPanel { + private class ListModel extends DefaultListModel { public void fireElementChanged(BookInfo element) { int index = indexOf(element); if (index >= 0) { @@ -45,6 +45,8 @@ public class BooksPanel extends JPanel { } } + static public final String INVALIDATE_CACHE = "invalidate_cache"; + private List bookInfos = new ArrayList(); private Map books = new HashMap(); private boolean seeWordCount; @@ -68,7 +70,7 @@ public class BooksPanel extends JPanel { searchBar.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - reload(searchBar.getText()); + filter(searchBar.getText()); } }); @@ -155,11 +157,11 @@ public class BooksPanel extends JPanel { 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())) { @@ -227,6 +229,11 @@ public class BooksPanel extends JPanel { } return null; } + + @Override + public void invalidateCache() { + fireActionPerformed(INVALIDATE_CACHE); + } }); list.addMouseMotionListener(new MouseAdapter() { diff --git a/src/be/nikiroo/fanfix_swing/gui/BrowserPanel.java b/src/be/nikiroo/fanfix_swing/gui/BrowserPanel.java index 47e55e4..25f7c9d 100644 --- a/src/be/nikiroo/fanfix_swing/gui/BrowserPanel.java +++ b/src/be/nikiroo/fanfix_swing/gui/BrowserPanel.java @@ -145,19 +145,13 @@ public class BrowserPanel extends JPanel { */ public BookInfo getHighlight() { BasicLibrary lib = Instance.getInstance().getLibrary(); - if (tabs.getSelectedComponent() == sourceTab) { - List sel = sourceTab.getSelectedElements(); - if (!sel.isEmpty()) { + List sel = sourceTab.getSelectedElements(); + if (!sel.isEmpty()) { + if (tabs.getSelectedComponent() == sourceTab) { return BookInfo.fromSource(lib, sel.get(0)); - } - } else if (tabs.getSelectedComponent() == authorTab) { - List sel = authorTab.getSelectedElements(); - if (!sel.isEmpty()) { + } else if (tabs.getSelectedComponent() == authorTab) { return BookInfo.fromAuthor(lib, sel.get(0)); - } - } else if (tabs.getSelectedComponent() == tagsTab) { - List sel = tagsTab.getSelectedElements(); - if (!sel.isEmpty()) { + } else if (tabs.getSelectedComponent() == tagsTab) { return BookInfo.fromTag(lib, sel.get(0)); } } @@ -192,6 +186,15 @@ public class BrowserPanel extends JPanel { 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}. diff --git a/src/be/nikiroo/fanfix_swing/gui/MainFrame.java b/src/be/nikiroo/fanfix_swing/gui/MainFrame.java index 0f94fa5..36e3c6a 100644 --- a/src/be/nikiroo/fanfix_swing/gui/MainFrame.java +++ b/src/be/nikiroo/fanfix_swing/gui/MainFrame.java @@ -1,10 +1,8 @@ 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; @@ -12,9 +10,7 @@ import javax.swing.JLabel; 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; @@ -50,6 +46,7 @@ public class MainFrame extends JFrame { } books = new BooksPanel(true); + browser.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { @@ -57,6 +54,14 @@ public class MainFrame extends JFrame { 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); diff --git a/src/be/nikiroo/fanfix_swing/gui/SearchBar.java b/src/be/nikiroo/fanfix_swing/gui/SearchBar.java index ee5896e..2dd4587 100644 --- a/src/be/nikiroo/fanfix_swing/gui/SearchBar.java +++ b/src/be/nikiroo/fanfix_swing/gui/SearchBar.java @@ -8,10 +8,10 @@ import java.awt.event.KeyAdapter; 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; @@ -22,7 +22,7 @@ import be.nikiroo.fanfix_swing.images.IconGenerator.Size; * * @author niki */ -public class SearchBar extends JPanel { +public class SearchBar extends ListenerPanel { static private final long serialVersionUID = 1L; private JButton search; @@ -47,7 +47,7 @@ public class SearchBar extends JPanel { text.requestFocus(); if (realTime) { - fireActionPerformed(); + fireActionPerformed(getText()); } } }); @@ -64,7 +64,7 @@ public class SearchBar extends JPanel { clear.setVisible(!empty); if (realTime) { - fireActionPerformed(); + fireActionPerformed(getText()); } } }); @@ -74,7 +74,7 @@ public class SearchBar extends JPanel { @Override public void actionPerformed(ActionEvent e) { if (!realTime) { - fireActionPerformed(); + fireActionPerformed(getText()); } } }); @@ -89,7 +89,7 @@ public class SearchBar extends JPanel { clear.setVisible(false); text.requestFocus(); - fireActionPerformed(); + fireActionPerformed(getText()); } }); @@ -98,39 +98,6 @@ public class SearchBar extends JPanel { 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}. * diff --git a/src/be/nikiroo/fanfix_swing/gui/book/BookPopup.java b/src/be/nikiroo/fanfix_swing/gui/book/BookPopup.java index 0710549..42f7e3b 100644 --- a/src/be/nikiroo/fanfix_swing/gui/book/BookPopup.java +++ b/src/be/nikiroo/fanfix_swing/gui/book/BookPopup.java @@ -47,6 +47,7 @@ public class BookPopup extends JPopupMenu { public void fireElementChanged(BookInfo book); + public void invalidateCache(); } /** @@ -438,7 +439,6 @@ public class BookPopup extends JPopupMenu { lib.changeAuthor(luid, fChangeTo, null); } } - // TODO: ^-- this can create new sources/authors, update maybe required? return null; } @@ -446,11 +446,15 @@ public class BookPopup extends JPopupMenu { @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); diff --git a/src/be/nikiroo/fanfix_swing/gui/browser/BasicTab.java b/src/be/nikiroo/fanfix_swing/gui/browser/BasicTab.java index d467a91..e9e8edc 100644 --- a/src/be/nikiroo/fanfix_swing/gui/browser/BasicTab.java +++ b/src/be/nikiroo/fanfix_swing/gui/browser/BasicTab.java @@ -8,14 +8,10 @@ import java.util.ArrayList; 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; @@ -23,15 +19,16 @@ import javax.swing.tree.DefaultTreeModel; 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 extends JPanel { +public abstract class BasicTab extends ListenerPanel { private int totalCount = 0; private List selectedElements = new ArrayList(); private T data; @@ -40,6 +37,7 @@ public abstract class BasicTab extends JPanel { private int index; private JTree tree; + private DefaultMutableTreeNode root; private SearchBar searchBar; public BasicTab(int index, String listenerCommand) { @@ -51,7 +49,7 @@ public abstract class BasicTab extends JPanel { data = createEmptyData(); totalCount = 0; - final DefaultMutableTreeNode root = new DefaultMutableTreeNode(); + root = new DefaultMutableTreeNode(); tree = new JTree(root); tree.setUI(new BasicTreeUI()); @@ -81,7 +79,7 @@ public abstract class BasicTab extends JPanel { BasicTab.this.selectedElements = selectedElements; - fireActionPerformed(); + fireActionPerformed(BasicTab.this.listenerCommand); } }); @@ -92,27 +90,39 @@ public abstract class BasicTab extends JPanel { 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>, Integer> worker = new SwingWorker>, Integer>() { @Override protected Map> 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(); @@ -158,39 +168,6 @@ public abstract class BasicTab extends JPanel { 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())); } diff --git a/src/be/nikiroo/fanfix_swing/gui/utils/ListenerPanel.java b/src/be/nikiroo/fanfix_swing/gui/utils/ListenerPanel.java new file mode 100644 index 0000000..7ed5f86 --- /dev/null +++ b/src/be/nikiroo/fanfix_swing/gui/utils/ListenerPanel.java @@ -0,0 +1,54 @@ +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); + } + } + } +} diff --git a/src/be/nikiroo/fanfix_swing/gui/utils/TreeSnapshot.java b/src/be/nikiroo/fanfix_swing/gui/utils/TreeSnapshot.java new file mode 100644 index 0000000..e202544 --- /dev/null +++ b/src/be/nikiroo/fanfix_swing/gui/utils/TreeSnapshot.java @@ -0,0 +1,122 @@ +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 expanded; + + public TreeSnapshot(JTree tree) { + this.tree = tree; + + selectionPaths = tree.getSelectionPaths(); + if (selectionPaths == null) { + selectionPaths = new TreePath[0]; + } + + expanded = new ArrayList(); + 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 newExpanded = new ArrayList(); + final List newSlectionPaths = new ArrayList(); + + 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 nodes = new LinkedList(); + 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(); + } +} diff --git a/src/be/nikiroo/fanfix_swing/gui/utils/UiHelper.java b/src/be/nikiroo/fanfix_swing/gui/utils/UiHelper.java index 3318bcc..5d23ab5 100644 --- a/src/be/nikiroo/fanfix_swing/gui/utils/UiHelper.java +++ b/src/be/nikiroo/fanfix_swing/gui/utils/UiHelper.java @@ -7,6 +7,7 @@ import javax.swing.JButton; 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; -- 2.27.0