Skip to content

Commit 012bbec

Browse files
committed
Fix 3110
- Increased component size. - Fixed issue where name field had focus/trigger issues. - Added tests
1 parent 2f7298f commit 012bbec

3 files changed

Lines changed: 134 additions & 14 deletions

File tree

ugs-designer/src/main/java/com/willwinder/ugs/designer/gui/toollibrary/ToolEditorPanel.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ This file is part of Universal Gcode Sender (UGS).
3232
import javax.swing.JSeparator;
3333
import javax.swing.JTextField;
3434
import javax.swing.SwingConstants;
35-
import javax.swing.event.DocumentEvent;
36-
import javax.swing.event.DocumentListener;
3735
import java.awt.Dimension;
36+
import java.awt.event.FocusAdapter;
37+
import java.awt.event.FocusEvent;
3838
import java.util.function.Consumer;
3939

4040
/**
@@ -88,12 +88,18 @@ public void setChangeListener(Consumer<ToolDefinition> listener) {
8888

8989
private void initComponents() {
9090
setLayout(new MigLayout("fillx, wrap 2", "[pref!][grow,fill]"));
91-
setPreferredSize(new Dimension(360, 420));
92-
setMinimumSize(new Dimension(360, 420));
91+
setPreferredSize(new Dimension(420, 420));
92+
setMinimumSize(new Dimension(420, 420));
9393

9494
add(new JLabel("Name"));
9595
nameField = new JTextField();
96-
nameField.getDocument().addDocumentListener(simpleDocListener(this::fireChange));
96+
nameField.addActionListener(e -> { if (!suppressEvents) fireChange(); });
97+
nameField.addFocusListener(new FocusAdapter() {
98+
@Override
99+
public void focusLost(FocusEvent e) {
100+
if (!suppressEvents) fireChange();
101+
}
102+
});
97103
add(nameField, "growx");
98104

99105
add(new JLabel("Shape"));
@@ -328,11 +334,4 @@ private void validateAndReportError() {
328334
errorLabel.setText(error.isEmpty() ? " " : error);
329335
}
330336

331-
private DocumentListener simpleDocListener(Runnable onChange) {
332-
return new DocumentListener() {
333-
@Override public void insertUpdate(DocumentEvent e) { if (!suppressEvents) onChange.run(); }
334-
@Override public void removeUpdate(DocumentEvent e) { if (!suppressEvents) onChange.run(); }
335-
@Override public void changedUpdate(DocumentEvent e) { if (!suppressEvents) onChange.run(); }
336-
};
337-
}
338337
}

ugs-designer/src/main/java/com/willwinder/ugs/designer/gui/toollibrary/ToolLibraryDialog.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public ToolLibraryDialog(Window owner, ToolLibraryService service, UnitUtils.Uni
7575

7676
private void initComponents() {
7777
setLayout(new MigLayout("fill", "[250!][grow]", "[grow][]"));
78-
setPreferredSize(new Dimension(720, 520));
78+
setPreferredSize(new Dimension(780, 520));
7979

8080
toolList = new JList<>(listModel);
8181
toolList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@@ -134,7 +134,7 @@ public Component getListCellRendererComponent(JList<?> list, Object value, int i
134134
JComponent.WHEN_IN_FOCUSED_WINDOW);
135135

136136
setSize(getPreferredSize());
137-
setMinimumSize(new Dimension(560, 400));
137+
setMinimumSize(new Dimension(620, 400));
138138
setLocationRelativeTo(getOwner());
139139
}
140140

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
Copyright 2026 Damian Nikodem
3+
4+
This file is part of Universal Gcode Sender (UGS).
5+
6+
UGS is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
UGS is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with UGS. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
package com.willwinder.ugs.designer.gui.toollibrary;
20+
21+
import com.willwinder.ugs.designer.model.toollibrary.EndmillShape;
22+
import com.willwinder.ugs.designer.model.toollibrary.ToolDefinition;
23+
import com.willwinder.universalgcodesender.model.UnitUtils;
24+
import org.junit.Assume;
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
28+
import javax.swing.JTextField;
29+
import java.awt.GraphicsEnvironment;
30+
import java.awt.event.ActionEvent;
31+
import java.awt.event.FocusAdapter;
32+
import java.awt.event.FocusEvent;
33+
import java.awt.event.FocusListener;
34+
import java.lang.reflect.Field;
35+
import java.util.concurrent.atomic.AtomicInteger;
36+
import java.util.concurrent.atomic.AtomicReference;
37+
38+
import static org.junit.Assert.assertEquals;
39+
import static org.junit.Assert.assertNotNull;
40+
41+
/**
42+
* Regression guard for the Tool Library name-field focus-loss bug. Typing into the Name field used
43+
* to commit on every keystroke via a DocumentListener, which triggered a refresh cycle that
44+
* disabled the field mid-edit and kicked focus to the Shape combo. The fix moves commit to
45+
* Enter / focus-lost so the field stays focused while the user types.
46+
*/
47+
public class ToolEditorPanelNameCommitTest {
48+
49+
private ToolEditorPanel panel;
50+
private JTextField nameField;
51+
private AtomicInteger fireCount;
52+
private AtomicReference<ToolDefinition> lastEdit;
53+
54+
@Before
55+
public void setUp() throws Exception {
56+
Assume.assumeFalse(GraphicsEnvironment.isHeadless());
57+
panel = new ToolEditorPanel(UnitUtils.Units.MM);
58+
nameField = readField("nameField");
59+
60+
fireCount = new AtomicInteger();
61+
lastEdit = new AtomicReference<>();
62+
panel.setChangeListener(t -> {
63+
fireCount.incrementAndGet();
64+
lastEdit.set(t);
65+
});
66+
67+
ToolDefinition tool = new ToolDefinition();
68+
tool.setName("Initial");
69+
tool.setShape(EndmillShape.UPCUT);
70+
tool.setDiameter(3.0);
71+
tool.setDiameterUnit(UnitUtils.Units.MM);
72+
tool.setFeedSpeed(900);
73+
tool.setPlungeSpeed(300);
74+
tool.setDepthPerPass(1.0);
75+
tool.setStepOverPercent(0.4);
76+
tool.setMaxSpindleSpeed(18000);
77+
panel.setTool(tool, false);
78+
fireCount.set(0);
79+
lastEdit.set(null);
80+
}
81+
82+
@Test
83+
public void typingDoesNotFireChange() throws Exception {
84+
nameField.getDocument().insertString(0, "A", null);
85+
nameField.getDocument().insertString(1, "B", null);
86+
nameField.getDocument().insertString(2, "C", null);
87+
assertEquals("Typing into the name field must not commit per keystroke.",
88+
0, fireCount.get());
89+
}
90+
91+
@Test
92+
public void enterCommitsName() {
93+
nameField.setText("Renamed");
94+
nameField.getActionListeners()[0].actionPerformed(
95+
new ActionEvent(nameField, ActionEvent.ACTION_PERFORMED, ""));
96+
assertEquals(1, fireCount.get());
97+
assertNotNull(lastEdit.get());
98+
assertEquals("Renamed", lastEdit.get().getName());
99+
}
100+
101+
@Test
102+
public void focusLostCommitsName() {
103+
nameField.setText("AfterTab");
104+
FocusEvent event = new FocusEvent(nameField, FocusEvent.FOCUS_LOST);
105+
for (FocusListener listener : nameField.getFocusListeners()) {
106+
if (listener instanceof FocusAdapter) {
107+
listener.focusLost(event);
108+
}
109+
}
110+
assertEquals(1, fireCount.get());
111+
assertNotNull(lastEdit.get());
112+
assertEquals("AfterTab", lastEdit.get().getName());
113+
}
114+
115+
@SuppressWarnings("unchecked")
116+
private <T> T readField(String name) throws Exception {
117+
Field field = ToolEditorPanel.class.getDeclaredField(name);
118+
field.setAccessible(true);
119+
return (T) field.get(panel);
120+
}
121+
}

0 commit comments

Comments
 (0)