Keep channels the user collapsed collapsed
Every tree rebuild, which any join, leave or talk-power change causes, expanded every row again, so a collapsed channel reopened within seconds. The tree now remembers which channels were collapsed and opens everything else; reopening one restores what is below it. While the find bar filters, everything opens so every match shows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,8 @@ import javax.swing.JViewport;
|
|||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.plaf.basic.BasicTreeUI;
|
import javax.swing.plaf.basic.BasicTreeUI;
|
||||||
|
import javax.swing.event.TreeExpansionEvent;
|
||||||
|
import javax.swing.event.TreeExpansionListener;
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
import javax.swing.tree.TreePath;
|
import javax.swing.tree.TreePath;
|
||||||
@@ -26,7 +28,9 @@ import java.awt.Rectangle;
|
|||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.MouseAdapter;
|
import java.awt.event.MouseAdapter;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The server view: a tree of channels each containing its clients, styled to
|
* The server view: a tree of channels each containing its clients, styled to
|
||||||
@@ -159,8 +163,13 @@ public final class ServerTreePanel extends JPanel {
|
|||||||
if (!nickname.isEmpty()) actions.renameSelf(nickname);
|
if (!nickname.isEmpty()) actions.renameSelf(nickname);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/** True while {@link #rebuild()} clears and restores the selection, to swallow the transient null in between. */
|
/**
|
||||||
|
* True while the tree is changed programmatically: a {@link #rebuild()} clearing and restoring
|
||||||
|
* the selection, or channels being reopened. The listeners ignore what happens meanwhile.
|
||||||
|
*/
|
||||||
private boolean rebuilding;
|
private boolean rebuilding;
|
||||||
|
/** Channels the user collapsed; every other channel is shown open. */
|
||||||
|
private final Set<Integer> collapsedChannels = new HashSet<>();
|
||||||
private final ServerModel model;
|
private final ServerModel model;
|
||||||
private final GroupIcons groupIcons;
|
private final GroupIcons groupIcons;
|
||||||
private final Actions actions;
|
private final Actions actions;
|
||||||
@@ -225,6 +234,30 @@ public final class ServerTreePanel extends JPanel {
|
|||||||
tree.setDropMode(DropMode.ON_OR_INSERT);
|
tree.setDropMode(DropMode.ON_OR_INSERT);
|
||||||
tree.setTransferHandler(new ServerTreeDragAndDrop(model, actions, tree, this::pathOf).transferHandler());
|
tree.setTransferHandler(new ServerTreeDragAndDrop(model, actions, tree, this::pathOf).transferHandler());
|
||||||
|
|
||||||
|
tree.addTreeExpansionListener(new TreeExpansionListener() {
|
||||||
|
@Override
|
||||||
|
public void treeCollapsed(TreeExpansionEvent e) {
|
||||||
|
if (rebuilding) return;
|
||||||
|
Object obj = ((DefaultMutableTreeNode) e.getPath().getLastPathComponent()).getUserObject();
|
||||||
|
if (obj instanceof ChannelNode) collapsedChannels.add(((ChannelNode) obj).id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void treeExpanded(TreeExpansionEvent e) {
|
||||||
|
if (rebuilding) return;
|
||||||
|
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e.getPath().getLastPathComponent();
|
||||||
|
if (!(node.getUserObject() instanceof ChannelNode)) return;
|
||||||
|
collapsedChannels.remove(((ChannelNode) node.getUserObject()).id);
|
||||||
|
// A rebuild while it was shut left everything below it shut too.
|
||||||
|
rebuilding = true;
|
||||||
|
try {
|
||||||
|
expandOpenChannels(node, e.getPath());
|
||||||
|
} finally {
|
||||||
|
rebuilding = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
tree.addTreeSelectionListener(e -> {
|
tree.addTreeSelectionListener(e -> {
|
||||||
if (rebuilding) return;
|
if (rebuilding) return;
|
||||||
TreePath path = tree.getSelectionPath();
|
TreePath path = tree.getSelectionPath();
|
||||||
@@ -364,9 +397,7 @@ public final class ServerTreePanel extends JPanel {
|
|||||||
if (node != null) root.add(node);
|
if (node != null) root.add(node);
|
||||||
}
|
}
|
||||||
treeModel.reload();
|
treeModel.reload();
|
||||||
for (int i = 0; i < tree.getRowCount(); i++) {
|
expandOpenChannels(root, new TreePath(root));
|
||||||
tree.expandRow(i);
|
|
||||||
}
|
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
TreePath path = pathOf(selected);
|
TreePath path = pathOf(selected);
|
||||||
if (path != null) tree.setSelectionPath(path);
|
if (path != null) tree.setSelectionPath(path);
|
||||||
@@ -381,6 +412,21 @@ public final class ServerTreePanel extends JPanel {
|
|||||||
search.treeRebuilt();
|
search.treeRebuilt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens {@code node} and every channel below it, except the ones the user collapsed and
|
||||||
|
* whatever is below those. While the find bar filters, everything opens, so every match shows.
|
||||||
|
*/
|
||||||
|
private void expandOpenChannels(DefaultMutableTreeNode node, TreePath path) {
|
||||||
|
tree.expandPath(path);
|
||||||
|
boolean filtering = search.isFiltering();
|
||||||
|
for (int i = 0; i < node.getChildCount(); i++) {
|
||||||
|
DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
|
||||||
|
if (child.isLeaf() || !(child.getUserObject() instanceof ChannelNode)) continue;
|
||||||
|
if (!filtering && collapsedChannels.contains(((ChannelNode) child.getUserObject()).id)) continue;
|
||||||
|
expandOpenChannels(child, path.pathByAddingChild(child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Object selectedUserObject() {
|
private Object selectedUserObject() {
|
||||||
TreePath path = tree.getSelectionPath();
|
TreePath path = tree.getSelectionPath();
|
||||||
return path == null ? null : ((DefaultMutableTreeNode) path.getLastPathComponent()).getUserObject();
|
return path == null ? null : ((DefaultMutableTreeNode) path.getLastPathComponent()).getUserObject();
|
||||||
@@ -408,6 +454,7 @@ public final class ServerTreePanel extends JPanel {
|
|||||||
public void showDisconnected() {
|
public void showDisconnected() {
|
||||||
root.setUserObject("Not connected");
|
root.setUserObject("Not connected");
|
||||||
root.removeAllChildren();
|
root.removeAllChildren();
|
||||||
|
collapsedChannels.clear();
|
||||||
treeModel.reload();
|
treeModel.reload();
|
||||||
search.treeRebuilt();
|
search.treeRebuilt();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user