Skip to content

feat: ModifiedGutter pattern for property editors#6697

Draft
vlsi wants to merge 10 commits into
apache:masterfrom
vlsi:gutter-pattern
Draft

feat: ModifiedGutter pattern for property editors#6697
vlsi wants to merge 10 commits into
apache:masterfrom
vlsi:gutter-pattern

Conversation

@vlsi
Copy link
Copy Markdown
Collaborator

@vlsi vlsi commented May 7, 2026

Summary

Adds a "modified gutter": a thin coloured strip on the left edge of a property
editor that lights up when the field's value is explicitly stored on the test
element. It mirrors the IntelliJ IDEA / VS Code convention, so you can tell at a
glance which fields you have overridden and which still inherit a default.

The series introduces:

  • ModifiedGutter widget plus the UnsetMode / ExpressionMode / ResetMode
    sealed types that configure it.
  • Gutter-aware base controls in jorphan: JEditableCheckBox,
    JEditableComboBox, JEditableTextField (single-line) and JEditableTextArea
    (multi-line).
  • Core property-editor bindings: JBooleanPropertyEditor, JEnumPropertyEditor
    and JStringPropertyEditor.
  • The element name field (NamePanel) and the comment field are wired
    into the gutter; the comment field is backed by JEditableTextArea.
  • A right-click "Reset to default" popup entry, enabled only while the gutter is
    lit (shortcut: an extra Backspace on an empty modified text field). For the
    check-box and combo editors, the same popup carries a "Use Expression" entry
    that switches to a free-form ${expression} field.
  • Symmetric updateUi / updateElement, so a save / reload round-trip preserves
    an explicit value even when it equals the default.
  • A user-manual entry under "Configuring Tree Elements".

Demo

Light theme; for each control the top row is the default (gutter dark) and the
bottom row is the modified state (gutter lit).

Element name field
gutter-name

Checkbox (JBooleanPropertyEditor)

gutter-checkbox

Combo box (JEnumPropertyEditor)

gutter-combobox

Reset from the context menu — right-click a modified control and pick
"Reset to default"; the item is enabled only while the gutter is lit.

gutter-reset-menu

Out of scope

The final commit, "Store raw body in responseData and only decompress when
responseBody is accessed"
, is unrelated to the gutter. It is kept on this branch
only for local debugging and will be submitted as a separate PR — please ignore
it when reviewing.

Follow-up

Roll the gutter-aware editors out to the remaining element GUIs so every input
carries the indicator. Each is a separate change:

  • Test Plan
  • Thread Group
  • HTTP Request and HTTP Request Defaults (the fields not yet converted)
  • Assertions
  • other built-in elements

Test plan

  • :src:jorphan:test and :src:core:test are green
  • Smoke-test in the GUI: HTTP Request → Advanced → toggle "Retrieve all
    embedded resources", "URLs must match" and "URLs must not match"; check the
    element name field and the Comment field on any element. Save / reload a
    .jmx and confirm explicit values are preserved.

🤖 Generated with Claude Code

@vlsi vlsi force-pushed the gutter-pattern branch from c309cd4 to c3baf1c Compare May 28, 2026 18:32
vlsi and others added 9 commits May 29, 2026 22:15
Introduce the shared building blocks the editable property editors
depend on, ahead of the editors themselves:

* ModifiedGutter — a left-side accent strip that lights up when a
  control holds an explicitly stored value, following the gutter
  pattern used by IntelliJ IDEA and VS Code. The slot is reserved even
  when the strip is transparent, so toggling the state never shifts the
  layout, and the colour is muted when the wrapped control is disabled.
* ResetMode — a sealed type that wires a "Reset to default" entry into
  an editor's popup menu, enabled only while the gutter is lit.
* ExpressionMode — a sealed type that controls whether an editor may
  switch to a free-form ${expression} field.

ModifiedGutterTest covers the layout reservation, the property-change
semantics, and the accessibility description.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire JEditableCheckBox and its JBooleanPropertyEditor binding into the
ModifiedGutter pattern and route their captions through a
ResourceLocalizer.

* The check-box label, the true/false captions, and the "Use
  Expression" action are localized from JMeter resource bundles.
* "Use Expression" is offered as a popup-menu entry (right-click the
  control); there is no separate on-screen button.
* The gutter lights up when the value is explicitly stored on the test
  element, and a "Reset to default" entry appears in the popup while it
  is lit. A suppress flag gives explicit-set semantics: any user-driven
  change marks the editor modified and it stays modified until reset,
  even when the explicit value equals the default. updateElement is
  gated on the modified flag, so the gutter, the element, and a
  save/reload round-trip agree.

Tests cover the value getter/setter after the layout refactor, the full
gutter state machine, and the updateElement -> updateUi cycle through a
real AbstractTestElement, including round-trip preservation of an
explicit value that equals the default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a pair of enum-typed editors built on the same gutter and
explicit-set foundation as the check-box editor.

* JEditableComboBox is the underlying control; JEnumPropertyEditor
  binds it to an enum-typed test element property and exposes a
  create(...) factory with sensible defaults plus a Configuration data
  class for full control.
* UnsetMode (Forbid or Allow(unsetValue)) controls whether the user can
  clear the selection; the unset entry renders in italics and the
  editor reports null when it is selected.
* The combo carries the ModifiedGutter, the "Reset to default" and "Use
  Expression" popup entries, and the same explicit-set semantics as the
  check-box editor.

EnumEditor, ConstantThroughputTimer, and CSVDataSet move to the
schema-based descriptors the new editor expects, and
GenericTestBeanCustomizer is tightened to match.

Tests drive the updateElement -> updateUi cycle through a real
AbstractTestElement, including round-trip preservation of explicit
values that equal the default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-as-reset

Adds a third gutter-aware property editor for plain string properties,
together with a backspace gesture that resets the field to its default
when invoked on an already-empty modified field.

Key changes:

* New JEditableTextField in jorphan.gui — a gutter-wrapped JTextField
  with an optional Reset action in the popup menu. There is no
  separate expression card: free-form text already accepts
  ${expressions}, so a card switch would just add chrome.
* "Quick reset" via Backspace / Delete: pressing the delete key on
  an empty modified field is interpreted as "undo my custom value",
  saving a popup trip. The first backspace clears the last character;
  the next backspace on the now-empty field resets to default and
  clears the gutter.
* New JStringPropertyEditor in core.gui — binds JEditableTextField to
  a StringPropertyDescriptor with the same explicit-set semantics as
  JBooleanPropertyEditor / JEnumPropertyEditor (suppress flag,
  value-change listener, updateUi marks the editor modified iff the
  property is stored on the element). Persistence is symmetric:
  updateElement removes the property when not modified and stores the
  value verbatim when modified, so explicit empty strings now survive
  a save/reload round-trip.
* HttpTestSampleGui: embeddedAllowRE / embeddedExcludeRE are now
  JStringPropertyEditor instances and provide the first smoke-test of
  the gutter pattern on text inputs. The MigLayout column constraints
  on the surrounding panel are tightened so that the two URL labels
  share column 0 and their fields line up under the wider checkbox
  row above (the original switch dragged the labels far from their
  fields).
* JEditableTextFieldGutterSemanticsTest covers the full state machine,
  the popup menu wiring, and the backspace / delete gestures.
* JStringPropertyEditorTest exercises updateElement → updateUi through
  a real AbstractTestElement, including the round-trip case for an
  explicit empty string.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a multi-line counterpart to JEditableTextField — same gutter,
same Reset action, same backspace-as-reset gesture (triggered only
when the entire text area is empty).

* New JEditableTextArea in jorphan.gui. The text area is added
  directly to the gutter without a JScrollPane so that short
  comment-style fields render naturally; callers that need scrolling
  can wrap the editor or its inner JTextArea externally.

* AbstractJMeterGuiComponent.commentField is now backed by a
  JEditableTextArea. The legacy `commentField` JTextArea reference is
  retained and points at the editor's inner text area so existing
  setText / getText callers keep working unchanged. The editor uses a
  simple "non-empty == modified" rule rather than the explicit-set
  semantics from JStringPropertyEditor — comments have no notion of
  "default vs absent", just "set vs cleared", so the listener
  recomputes the modified flag from the live text on every value
  change.

* New JEditableTextAreaGutterSemanticsTest covers the same scenarios
  as the text-field test plus a check that multi-line content is
  read back verbatim and that backspace inside multi-line text falls
  through to the standard JTextArea behaviour (16 scenarios).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the name field into the modified-gutter pattern and, while in the
same shared base class, fixes the comment field's Reset action.

* NamePanel wraps its text field in a gutter-aware JEditableTextField.
  The gutter lights up when the name differs from the owning component's
  static label (its default name) and goes dark when it matches.
  AbstractJMeterGuiComponent feeds the static label through
  NamePanel.setDefaultName() on configure() / initGui(), and
  makeTitlePanel() now adds the gutter-aware editor (getNameComponent())
  instead of the raw inner JTextField — adding the raw field bypassed
  the gutter entirely, so it never showed.
* NamePanel.resetToDefault qualifies the call as NamePanel.this.setName;
  an unqualified setName resolved to Component.setName (the Swing
  component name), so Reset and the backspace gesture did nothing for
  the name field.
* The comment editor now overrides resetToDefault to clear the text.
  Previously it inherited the no-op base implementation, so Reset and
  the backspace gesture did nothing for comments either.
* NamePanelTest covers the gutter semantics (lit when name differs from
  default, dark when equal, recomputed on setDefaultName), the Reset
  menu item, the backspace gesture, and that makeTitlePanel adds the
  gutter wrapper rather than the raw field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds org.apache.jmeter.testkit.Screenshots, which renders a Swing
component into a BufferedImage / PNG without showing a window, by briefly
attaching it to a throwaway undecorated frame and painting it on the EDT.

This lets tests (and documentation tooling) capture a component's
appearance without a human driving the GUI — useful for eyeballing the
modified gutter and for auto-generating screenshots for the manual. It
needs a display and throws HeadlessException otherwise, so callers should
skip when head-less (run under Xvfb on CI if needed).

GutterScreenshotDemoTest is a worked example: it renders an unmodified
and a modified NamePanel stacked on a white background and writes
build/screenshots/name-gutter.png, skipping when head-less.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vlsi vlsi force-pushed the gutter-pattern branch from c3baf1c to 8bbbe21 Compare May 29, 2026 20:08
The lazy-decompression refactor dropped the `entity != null` check
before calling `entity.getContent()`. HttpClient4 returns a null
entity when the response has no body — HEAD requests and 304 Not
Modified — so those samples failed with a NullPointerException
("Non HTTP response code: java.lang.NullPointerException").

Restore the guard and set empty response data when there is no body.
This matches HTTPJavaImpl, whose stream is never null. Covered by
TestRedirects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant