From 7b52b24215079b05ffc6affc91b91bb28f708a30 Mon Sep 17 00:00:00 2001 From: fourbites Date: Sat, 3 Jan 2026 19:45:40 +0100 Subject: [PATCH] Add tooltip support to config editor via _TIP convention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../admin/client/ObjectEditorPanel.java | 28 +++++++- .../threerings/presents/client/Client.java | 66 +++++++++---------- .../presents/client/ClientDObjectMgr.java | 6 +- 3 files changed, 59 insertions(+), 41 deletions(-) diff --git a/core/src/main/java/com/threerings/admin/client/ObjectEditorPanel.java b/core/src/main/java/com/threerings/admin/client/ObjectEditorPanel.java index c14c9e5c1..69c20717d 100644 --- a/core/src/main/java/com/threerings/admin/client/ObjectEditorPanel.java +++ b/core/src/main/java/com/threerings/admin/client/ObjectEditorPanel.java @@ -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 _safesub; diff --git a/core/src/main/java/com/threerings/presents/client/Client.java b/core/src/main/java/com/threerings/presents/client/Client.java index a2cb216c4..52e99e3c5 100644 --- a/core/src/main/java/com/threerings/presents/client/Client.java +++ b/core/src/main/java/com/threerings/presents/client/Client.java @@ -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,38 +890,36 @@ 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 () { - // tell the object manager that we're no longer connected to the server - if (_omgr instanceof ClientDObjectMgr) { - ((ClientDObjectMgr)_omgr).cleanup(); - } - - // clear out our references - _comm = null; - _bstrap = null; - _omgr = null; - _clobj = null; - _connectionId = _cloid = -1; - _standalone = false; - - // and let our invocation director know we're logged off - _invdir.cleanup(); - - // if we were cleaned up due to a failure to logon, we can report the logon error - // now that the communicator is cleaned up; this allows a logon failure listener to - // immediately try another logon (hopefully with something changed like the server - // or port) - notifyObservers(new ObserverOps.Client(Client.this) { - @Override protected void notify (ClientObserver obs) { - if (logonError != null) { - obs.clientFailedToLogon(_client, logonError); - } else { - obs.clientDidClear(_client); - } - } - }); + _runQueue.postRunnable(() -> { + // tell the object manager that we're no longer connected to the server + if (_omgr instanceof ClientDObjectMgr) { + ((ClientDObjectMgr)_omgr).cleanup(); } + + // clear out our references + _comm = null; + _bstrap = null; + _omgr = null; + _clobj = null; + _connectionId = _cloid = -1; + _standalone = false; + + // and let our invocation director know we're logged off + _invdir.cleanup(); + + // if we were cleaned up due to a failure to logon, we can report the logon error + // now that the communicator is cleaned up; this allows a logon failure listener to + // immediately try another logon (hopefully with something changed like the server + // or port) + notifyObservers(new ObserverOps.Client(Client.this) { + @Override protected void notify (ClientObserver obs) { + if (logonError != null) { + obs.clientFailedToLogon(_client, logonError); + } else { + obs.clientDidClear(_client); + } + } + }); }); } diff --git a/core/src/main/java/com/threerings/presents/client/ClientDObjectMgr.java b/core/src/main/java/com/threerings/presents/client/ClientDObjectMgr.java index 49cfc089e..0a84a98d4 100644 --- a/core/src/main/java/com/threerings/presents/client/ClientDObjectMgr.java +++ b/core/src/main/java/com/threerings/presents/client/ClientDObjectMgr.java @@ -234,10 +234,8 @@ public class ClientDObjectMgr _flusher.cancel(); _flushes.clear(); _dead.clear(); - _client.getRunQueue().postRunnable(new Runnable() { - public void run () { - _ocache.clear(); - } + _client.getRunQueue().postRunnable(() -> { + _ocache.clear(); }); }