Rename yourself by double-clicking your own tree row

The row turns into a text field holding the current nickname, selected so
typing replaces it; Enter commits, Escape leaves the name alone. Only our
own row edits — every other double click keeps its meaning, and ours no
longer opens a private chat with ourselves.

The editor keeps the row's own icon (the inherited one knows only the
renderer's default leaf icon) and selects the text on a short delay,
after the release that opened it has been turned into a caret placement.
The tree nodes only mirror the connection's model, so the typed name is
sent to the server rather than written into the node; the row follows
when the rename is reported back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-19 19:38:49 +00:00
parent fa99cadc44
commit 6e65797f25
4 changed files with 168 additions and 3 deletions

View File

@@ -103,16 +103,31 @@ public final class ServerTreePanel extends JScrollPane {
/** Edits a client's description — our own included. */
void changeClientDescription(ClientEntry client);
/** A new nickname for ourselves, typed into the tree row. */
void renameSelf(String nickname);
}
private final DropIndicatorTree tree;
private final DefaultMutableTreeNode root = new DefaultMutableTreeNode();
private final DefaultTreeModel treeModel = new DefaultTreeModel(root);
/**
* The nodes only mirror the connection's model, so an edited row is not written
* back into the tree: the new nickname goes to the server, and the row follows
* once it reports the rename.
*/
private final DefaultTreeModel treeModel = new DefaultTreeModel(root) {
@Override
public void valueForPathChanged(TreePath path, Object newValue) {
String nickname = String.valueOf(newValue).trim();
if (!nickname.isEmpty()) actions.renameSelf(nickname);
}
};
/** True while {@link #rebuild()} clears and restores the selection, to swallow the transient null in between. */
private boolean rebuilding;
private final ServerModel model;
private final GroupIcons groupIcons;
private final Actions actions;
private final NicknameCellEditor nicknameEditor;
private int selfClientId = -1;
public ServerTreePanel(ServerModel model, GroupIcons groupIcons, Actions actions) {
@@ -134,7 +149,13 @@ public final class ServerTreePanel extends JScrollPane {
tree.setRowHeight(20);
tree.setBackground(Theme.TREE_BG);
tree.setFont(Theme.UI_FONT);
tree.setCellRenderer(new ServerTreeCellRenderer());
ServerTreeCellRenderer renderer = new ServerTreeCellRenderer();
tree.setCellRenderer(renderer);
nicknameEditor = NicknameCellEditor.create(tree, renderer, id -> id == selfClientId);
tree.setCellEditor(nicknameEditor);
tree.setEditable(true);
tree.setPathEditable(nicknameEditor::editsPath);
tree.setInvokesStopCellEditing(true);
setViewportView(tree);
getViewport().setBackground(Theme.TREE_BG);
// The icon strip is drawn against the viewport's right edge, so the blitted
@@ -175,7 +196,7 @@ public final class ServerTreePanel extends JScrollPane {
if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
if (obj instanceof ChannelNode && !Spacers.isSpacer(((ChannelNode) obj).name)) {
actions.joinChannel(((ChannelNode) obj).id);
} else if (obj instanceof ClientEntry) {
} else if (obj instanceof ClientEntry && ((ClientEntry) obj).id != selfClientId) {
actions.openPrivateChat((ClientEntry) obj);
}
} else if (SwingUtilities.isMiddleMouseButton(e) && obj instanceof ClientEntry) {