Add tooltip support to config editor via _TIP convention

ObjectEditorPanel now looks up a static String constant named
FIELD_NAME_TIP (e.g. myField → MY_FIELD_TIP) on the ConfigObject after
calling getEditor(), and sets it as a tooltip on the editor panel.

No changes to ConfigObject's API — existing getEditor() overrides
continue to work and automatically receive tooltips.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
fourbites
2026-01-03 19:45:40 +01:00
parent 4185d621f8
commit 7b52b24215
3 changed files with 59 additions and 41 deletions
@@ -9,6 +9,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import com.samskivert.swing.ScrollablePanel;
import com.samskivert.swing.VGroupLayout;
@@ -89,7 +90,7 @@ public class ObjectEditorPanel extends ScrollablePanel
// if the field is anything but a plain old public field,
// we don't want to edit it
if (field.getModifiers() == Modifier.PUBLIC) {
add(_object.getEditor(_ctx, field));
add(applyTip(object, field, _object.getEditor(_ctx, field)));
}
}
@@ -106,6 +107,31 @@ public class ObjectEditorPanel extends ScrollablePanel
log.warning("Unable to subscribe to config object: " + cause);
}
/**
* Looks up a {@code FIELD_NAME_TIP} static String constant on the config object and applies
* it as a tooltip on the editor panel.
*/
protected static JPanel applyTip (ConfigObject object, Field field, JPanel editor)
{
String tipName = toUpperSnake(field.getName()) + "_TIP";
try {
Field tipField = object.getClass().getField(tipName);
if (tipField.getType() == String.class &&
Modifier.isStatic(tipField.getModifiers())) {
editor.setToolTipText((String) tipField.get(null));
}
} catch (NoSuchFieldException | IllegalAccessException e) {
// no tip constant for this field
}
return editor;
}
/** Converts a {@code lowerCamelCase} name to {@code UPPER_SNAKE_CASE}. */
private static String toUpperSnake (String name)
{
return name.replaceAll("([a-z0-9])([A-Z])", "$1_$2").toUpperCase();
}
protected PresentsContext _ctx;
protected String _key;
protected SafeSubscriber<ConfigObject> _safesub;
@@ -870,11 +870,7 @@ public class Client
}
// otherwise queue this notification up to run on the run queue thread
else {
_runQueue.postRunnable(new Runnable() {
public void run () {
_observers.apply(op);
}
});
_runQueue.postRunnable(() -> _observers.apply(op));
}
}
@@ -894,8 +890,7 @@ public class Client
// CLIENT_DID_LOGOFF; that may not have been invoked yet, so we don't want to clear out our
// communicator reference immediately; instead we queue up a runnable unit to do so to
// ensure that it won't happen until CLIENT_DID_LOGOFF was dispatched
_runQueue.postRunnable(new Runnable() {
public void run () {
_runQueue.postRunnable(() -> {
// tell the object manager that we're no longer connected to the server
if (_omgr instanceof ClientDObjectMgr) {
((ClientDObjectMgr)_omgr).cleanup();
@@ -925,7 +920,6 @@ public class Client
}
}
});
}
});
}
@@ -234,10 +234,8 @@ public class ClientDObjectMgr
_flusher.cancel();
_flushes.clear();
_dead.clear();
_client.getRunQueue().postRunnable(new Runnable() {
public void run () {
_client.getRunQueue().postRunnable(() -> {
_ocache.clear();
}
});
}