-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTagTreeSingleClickAssign.groovy
More file actions
142 lines (112 loc) · 6.26 KB
/
Copy pathTagTreeSingleClickAssign.groovy
File metadata and controls
142 lines (112 loc) · 6.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Copyright (C) 2026 euu2021 (Github)
// SPDX-License-Identifier: GPL-2.0-or-later
// Discussion thread: https://github.com/freeplane/freeplane/issues/2926
// Version: 1.1
/***************************************************************************
Tag Tree Single-Click Assign — assign a tag to the current node with a
SINGLE left-click in the Tag Tree (Attributes & Tags panel), instead of a double-click.
Rationale (issue #2926): in the Tag panel, a single left-click on a tag does nothing
useful (expand/collapse is on the triangle); double-click is what assigns the tag to the
selected node. Over a long coding session, repeating a double-click thousands of times is
tiring. This makes the single click assign, so tagging is one click.
How it works (mirrors the native double-click handler, TagPanelManager:137-147):
- the tag under the click = tagCategories.categorizedTag(treeNodeAtPoint)
- assign = MIconController.insertTagsIntoSelectedNodes([tag]) (assigns to the map's
currently selected node(s); one undoable step)
One GLOBAL AWT mouse listener catches clicks on the panel's JTagTree (survives the tree
being rebuilt on map changes; no per-tree wiring). It is passive — the normal row selection
still happens, and clicks on the expand triangle still expand/collapse (they fall outside a
node's bounds, so getPathForLocation returns null and we skip).
It listens to MOUSE_PRESSED, NOT MOUSE_CLICKED: a click with the tiniest mouse movement
between press and release produces no MOUSE_CLICKED (Swing turns it into a drag, and the
tag tree has drag support), so a CLICKED-based version silently missed some clicks even
though the row got selected. MOUSE_PRESSED always fires. (Trade-off: starting to DRAG a
tag also assigns it once — undoable, and rare in a click-to-tag workflow.)
Scope: the panel tree only (a JTagTree inside the main JFrame), NOT the "Manage tag
categories" dialog (that one is for editing the hierarchy, not assigning).
Assignment is suspended while the tag tree is in edit mode, that is, while the client
property "TagTreeEditMode" is TRUE on the main root pane (set by the companion script
TagTreeKeyboardReorder). Reorganizing the hierarchy means clicking tags to select them,
and every such click would otherwise tag the current node.
The native double-click still works (it just becomes redundant). Run once = ON, again = OFF.
CHANGELOG
---------
1.1 (2026-07-26)
Clicks no longer assign a tag while the tag tree is in edit mode, so reorganizing
the hierarchy with the companion script TagTreeKeyboardReorder does not tag the
current node on every click.
1.0 (2026-07-26)
First versioned release. Earlier history is in the repository log.
*****************************************************************/
// @ExecutionModes({ON_SINGLE_NODE="/menu_bar/euu"})
import groovy.transform.Field
import org.freeplane.features.icon.IconController
import org.freeplane.features.icon.mindmapmode.MIconController
import org.freeplane.features.mode.Controller
import javax.swing.*
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.TreePath
import java.awt.*
import java.awt.event.AWTEventListener
import java.awt.event.MouseEvent
@Field final String LISTENER_KEY = "TagTreeSingleClickAssignListener"
@Field final String EDIT_MODE_KEY = "TagTreeEditMode"
JRootPane anchor = findMainRootPane()
if (anchor == null) { status("Single-click assign: main window not found."); return }
// toggle OFF if already on
Object existing = anchor.getClientProperty(LISTENER_KEY)
if (existing != null) {
Toolkit.getDefaultToolkit().removeAWTEventListener((AWTEventListener) existing)
anchor.putClientProperty(LISTENER_KEY, null)
status("Tag single-click assign: OFF")
return
}
AWTEventListener listener = new AWTEventListener() {
@Override
void eventDispatched(AWTEvent event) {
if (!(event instanceof MouseEvent)) return
MouseEvent e = (MouseEvent) event
if (e.getID() != MouseEvent.MOUSE_PRESSED) return
if (e.getButton() != MouseEvent.BUTTON1 || e.getClickCount() != 1) return
Component comp = e.getComponent()
if (comp == null || !comp.getClass().getName().endsWith("JTagTree")) return
// panel tree only, not the manager dialog
Window w = SwingUtilities.getWindowAncestor(comp)
if (!(w instanceof JFrame)) return
// in edit mode a click selects a tag to be reorganized, it does not assign it
if (Boolean.TRUE == ((JFrame) w).getRootPane().getClientProperty(EDIT_MODE_KEY)) return
try { assignTagAt((JTree) comp, e.getX(), e.getY()) } catch (Throwable ignore) {}
}
}
Toolkit.getDefaultToolkit().addAWTEventListener(listener, AWTEvent.MOUSE_EVENT_MASK)
anchor.putClientProperty(LISTENER_KEY, listener)
status("Tag single-click assign: ON — click a tag to add it to the selected node.")
return
/*
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ Assign ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
*/
void assignTagAt(JTree tree, int x, int y) {
TreePath path = tree.getPathForLocation(x, y)
if (path == null) return // click on the expand triangle or empty area -> ignore
if (tree.getPathBounds(path) == null) return
DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent()
def tagCategories = Controller.currentController.map.getIconRegistry().getTagCategories()
def tag = tagCategories.categorizedTag(node)
if (tag == null) return
((MIconController) IconController.getController()).insertTagsIntoSelectedNodes([tag])
}
/*
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ Assign ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
*/
/*
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ Helpers ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
*/
JRootPane findMainRootPane() {
for (Window w : Window.getWindows()) {
if (w.isShowing() && w instanceof JFrame) return ((JFrame) w).getRootPane()
}
return null
}
void status(String msg) {
try { Controller.currentController.viewController.out(msg) } catch (Throwable ignore) {}
}