Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
fc732ce
Desktop keyboard conventions, and a theme constant that had no reader
shai-almog Sep 18, 2026
37a2685
Separator, GroupBox and Stepper: the three desktop controls CN1 never…
shai-almog Sep 18, 2026
3a63f4f
Desktop themes: a real scrollbar, the surfaces CN1 still draws, and t…
shai-almog Sep 18, 2026
06194a7
A right-click menu, for the event that has always fired and never ope…
shai-almog Sep 18, 2026
df695c9
commandBehavior: Native was a silent way to lose every command
shai-almog Sep 18, 2026
8bd8dd7
Every desktop port installs its platform's theme
shai-almog Sep 18, 2026
d8f2c63
Docs: restore the Desktop section COVERAGE.md lost, and record what c…
shai-almog Sep 18, 2026
a457914
Desktop fidelity: 9 scored controls to 22
shai-almog Sep 18, 2026
bfcadc3
Native menu bars for the Windows and Linux ports
shai-almog Sep 18, 2026
8f4813f
Scope two fidelity rows to the platforms whose references can state them
shai-almog Sep 18, 2026
e2c7be5
Two leaks in the new menu code, found by reading it again
shai-almog Sep 18, 2026
8df3e66
Two macOS references were in the right state and showing the wrong thing
shai-almog Sep 18, 2026
41c2db3
The Fluent tab reference showed two tabs plus two close buttons
shai-almog Sep 18, 2026
dabad71
Golden sets for the second wave, from run 35370740065
shai-almog Sep 18, 2026
3779c2a
The GNOME capture segfaulted once in three runs
shai-almog Sep 18, 2026
0769d27
A test asserted the macOS default by its value, not by its property
shai-almog Sep 18, 2026
d352251
Framework chrome must never become an operating system window
shai-almog Sep 18, 2026
e948c9c
The desktop themes styled two UIIDs nothing writes, so no tab looked …
shai-almog Sep 18, 2026
b9cb4c7
WindowDialogTest stopped testing the thing it documents
shai-almog Sep 18, 2026
55442d7
Record what the tab fix did and did not fix
shai-almog Sep 18, 2026
41a95f6
The tab strip was missing the divider its platforms draw
shai-almog Sep 18, 2026
377965a
PMD: the context menu's loop should be a foreach
shai-almog Sep 18, 2026
0639c46
Fidelity baselines, re-recorded from run 35379975896
shai-almog Sep 18, 2026
971abaf
Linux x64 goldens for the Adwaita flip, from run 35379684156
shai-almog Sep 18, 2026
e3c1ff7
Linux arm64 goldens for the Adwaita flip, from run 35379684156
shai-almog Sep 18, 2026
5e38602
Windows goldens for the Fluent flip, from run 35379684087
shai-almog Sep 18, 2026
c03ebf4
C linkage is the half of a native's identity nothing was checking
shai-almog Sep 18, 2026
a55ae1c
Screenshot coverage for the desktop surface this PR turned on
shai-almog Sep 18, 2026
b18490f
Ten animation filmstrips captured six empty cells, and the capture su…
shai-almog Sep 18, 2026
81bc728
Register the three new screenshot tests in the port status contract
shai-almog Sep 18, 2026
d4c79d8
Rewrap two paragraphs the desktop dialog edit left mid-phrase
shai-almog Sep 18, 2026
9ed0d1d
DesktopSeparator: the row COVERAGE.md already promised and the suite …
shai-almog Sep 18, 2026
b7072ef
Goldens for the three new desktop tests: Linux and Windows
shai-almog Sep 18, 2026
286a839
Goldens for the three new desktop tests: JavaScript, and DesktopMode'…
shai-almog Sep 18, 2026
969ce23
Map the new goldens to their tests in the port status contract
shai-almog Sep 18, 2026
1a78f98
Upload the Android comparison PNGs, so an Android golden can be seede…
shai-almog Sep 18, 2026
faacb4d
The GNOME reference segfault was a dangling widget pointer, not flaki…
shai-almog Sep 18, 2026
ad79786
Separator reference goldens for Aqua and Fluent, and drop a tile over…
shai-almog Sep 18, 2026
a7dd8ab
Goldens for the three new desktop tests: Mac Catalyst
shai-almog Sep 18, 2026
b887909
The tv and watch capture waits ended mid-stream, and blamed the tests…
shai-almog Sep 18, 2026
74f4200
Goldens for the three new desktop tests: tvOS
shai-almog Sep 18, 2026
c957f8c
macOS goldens for the Aqua flip, from run 35395172908
shai-almog Sep 18, 2026
6dd5d22
Goldens for the three new desktop tests: iOS Metal and watchOS
shai-almog Sep 18, 2026
95e4dbd
The eleventh blank filmstrip, found by the guard rather than by me
shai-almog Sep 19, 2026
25ea53b
Goldens for the three new desktop tests: Android, the set CI could no…
shai-almog Sep 19, 2026
84e0516
Copyright header for the transition base this branch modified
shai-almog Sep 19, 2026
39a61c2
Catalyst's DesktopMode golden: the test stopped forcing desktop mode …
shai-almog Sep 19, 2026
f90a9f3
macOS transition goldens, now that their middle frames render
shai-almog Sep 19, 2026
4ff4d48
Goldens for the three new desktop tests: iOS OpenGL, the eleventh and…
shai-almog Sep 19, 2026
f0502aa
tvOS transition goldens: the same layout fix, a different symptom
shai-almog Sep 19, 2026
63719b0
Mac Catalyst transition goldens: the third port that had no incidenta…
shai-almog Sep 19, 2026
093f67a
iOS Metal transition goldens, and a correction to which ports this re…
shai-almog Sep 19, 2026
65afb70
iOS OpenGL transition goldens: the lane that would only have failed a…
shai-almog Sep 19, 2026
8701d4e
GNOME separator goldens, and the segfault fix confirmed on a clean ca…
shai-almog Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 15 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ on:
- 'scripts/check-native-warnings.py'
- 'scripts/check-native-warnings.sh'
- 'scripts/native-warnings/**'
# Same reason: the C++ linkage gate runs from this workflow and nowhere else, so
# ignoring scripts/** would let a change that weakens it merge unexercised.
- 'scripts/check-native-cpp-linkage.py'
# The build hint gates are run from this workflow and nowhere else, and one
# of them holds an empty baseline. Ignoring the whole directory meant a
# change that breaks a gate, or that adds a line to the baseline, could
Expand Down Expand Up @@ -94,6 +97,9 @@ on:
- 'scripts/check-native-warnings.py'
- 'scripts/check-native-warnings.sh'
- 'scripts/native-warnings/**'
# Same reason: the C++ linkage gate runs from this workflow and nowhere else, so
# ignoring scripts/** would let a change that weakens it merge unexercised.
- 'scripts/check-native-cpp-linkage.py'
# The build hint gates are run from this workflow and nowhere else, and one
# of them holds an empty baseline. Ignoring the whole directory meant a
# change that breaks a gate, or that adds a line to the baseline, could
Expand Down Expand Up @@ -566,6 +572,15 @@ jobs:
mvn -B -q -f maven/pom.xml -pl windows,linux -am -DskipTests \
-Dcn1.binaries="${CN1_BINARIES}" compile
scripts/check-native-signatures.sh --require-all
# Linkage, which the signature gate above cannot see: it verifies that a
# native's NAME matches its Java method, and a mangled C++ symbol has the
# right name too. Needs no compiler and no build output, so it sits on its
# own step rather than behind the compile above.
- name: Check native C++ linkage
if: ${{ matrix.java-version == 8 }}
run: |
python3 scripts/check-native-cpp-linkage.py --self-test
python3 scripts/check-native-cpp-linkage.py
# The native warning gate itself runs on the macOS/Windows/Linux legs that
# actually compile C; what runs HERE is its parser, against a hand-authored
# fixture, plus a structural check on the checked-in baselines. Both need no
Expand Down
14 changes: 12 additions & 2 deletions .github/workflows/scripts-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,14 @@ jobs:
uses: actions/upload-artifact@v7
with:
name: android-instrumentation-logs
path: artifacts/connectedAndroidTest*.log
# The comparison also writes a PNG for every screenshot that did not match a
# stored golden, including one that has no golden at all. Uploading only the log
# left those on the runner, so there was no way to seed an Android golden from
# CI -- a new suite test could be added, captured and reported as missing here
# forever, and the only route to a golden was an emulator on somebody's desk.
path: |
artifacts/connectedAndroidTest*.log
artifacts/*.png
if-no-files-found: warn
retention-days: 14
compression-level: 6
Expand All @@ -326,7 +333,10 @@ jobs:
uses: actions/upload-artifact@v7
with:
name: android-instrumentation-logs-${{ matrix.id }}
path: artifacts/connectedAndroidTest*.log
# Same reason as the default leg above.
path: |
artifacts/connectedAndroidTest*.log
artifacts/*.png
if-no-files-found: warn
retention-days: 14
compression-level: 6
Expand Down
27 changes: 27 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,33 @@ calls -- is a warning: it is dead code, not a broken build. Note the offline gat
reads `target/classes`, so **a stale port build reports natives that no longer
exist**; rebuild the module before believing a finding.

#### The right name is not enough in C++

A native defined in a `.cpp` or `.mm` file gets its name **mangled** unless it is
declared `extern "C"`. The file compiles, the symbol it exports is not the one the
generated code calls, and the link fails on the device in a file nobody touched --
naming a symbol that is visibly right there in the source.

`NativeSignatureVerifier` cannot see it. It checks that a native's *name* matches
its Java method, and a mangled function has the right name too; linkage is not part
of a name. #5845 shipped exactly this in `cn1_windows_window.cpp` and only a real
Windows build caught it.

`scripts/check-native-cpp-linkage.py` closes that, over every tracked `.cpp`, `.cc`,
`.cxx` and `.mm`. It needs no compiler and no build output. Like the control-character
gate it has **no baseline and no exclusions**: a native without C linkage is never
intentional, and the fix is always one line. The script is re-included in `pr.yml`'s
`paths` (both triggers) so a change that weakens the gate cannot merge unexercised.

```bash
scripts/check-native-cpp-linkage.py # every tracked C++ translation unit
scripts/check-native-cpp-linkage.py PATH ... # just these
```

Note what it deliberately does not report: a port-internal C++ helper, whose mangling
is correct, and a prototype, which needs no linkage of its own -- only a *definition*
whose symbol the generated code will call.

#### A framework an app gets by accident is a framework it can lose

The iOS port referenced the `UTType` class while `ByteCodeTranslator` linked
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,11 @@ public void released(int x, int y) {
}
final Container con = createPopupContent(subMenu);
Dialog d = new Dialog();
// Framework chrome, never an operating system window: this popup is POSITIONED by the
// framework, and native window mode documents those margins as ignored, so in a window
// it comes out centred and loses the placement that is its whole point. See
// TooltipManager for the full note.
d.setNativeWindowMode(false);
d.setDialogUIID("Container");
d.getContentPane().setUIID("Container");
d.setLayout(new BorderLayout());
Expand Down
175 changes: 175 additions & 0 deletions CodenameOne/src/com/codename1/components/GroupBox.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Codename One in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.components;

import com.codename1.ui.Container;
import com.codename1.ui.Label;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.layouts.Layout;

/// A titled frame around a group of related controls.
///
/// The desktop equivalent of a section header, and the one piece of grouping chrome all
/// three desktop toolkits agree on: `NSBox` with a title, `GtkFrame` with a label widget,
/// and WinUI's headered content. On a phone the same grouping is expressed by a gap and a
/// heading, which is why Codename One never had this -- and why a desktop form built out of
/// plain containers reads as one undifferentiated column of controls.
///
/// Two UIIDs: `GroupBox` styles the frame (its border is the box) and `GroupBoxTitle`
/// styles the caption. A theme that wants the caption to sit *in* the top edge rather than
/// above it does that with a negative top margin on `GroupBoxTitle`; nothing here hard-codes
/// a position, because the three platforms disagree about it.
///
/// ```java
/// GroupBox appearance = new GroupBox("Appearance");
/// appearance.add(new CheckBox("Use the system accent colour"))
/// .add(new CheckBox("Reduce transparency"));
/// ```
///
/// The content is an ordinary `Container`, so `add`, `remove` and the layout all behave as
/// they would anywhere else -- `#getContentPane()` is there for the rare caller that wants
/// the inner container itself.
public class GroupBox extends Container {
private final Label title = new Label("", "GroupBoxTitle");
private final Container content;

/// An untitled group whose content stacks vertically.
public GroupBox() {
this("", com.codename1.ui.layouts.BoxLayout.y());
}

/// A titled group whose content stacks vertically.
///
/// #### Parameters
///
/// - `titleText`: the caption
public GroupBox(String titleText) {
this(titleText, com.codename1.ui.layouts.BoxLayout.y());
}

/// A titled group with a layout of its own.
///
/// #### Parameters
///
/// - `titleText`: the caption
///
/// - `contentLayout`: the layout for the grouped controls
public GroupBox(String titleText, Layout contentLayout) {
super(new BorderLayout());
setUIID("GroupBox");
content = new Container(contentLayout);
content.setUIID("Container");
title.setText(titleText);
// An empty caption must not reserve a strip: an untitled group is a plain box, and a
// blank label with the title style's padding would leave a gap nothing explains.
title.setHidden(titleText == null || titleText.length() == 0);
// super, explicitly: the overrides below route an ordinary add into the content pane,
// which is what every caller means -- and would put the caption and the content pane
// itself inside the content pane if these two went through them.
super.addComponent(BorderLayout.NORTH, title);
super.addComponent(BorderLayout.CENTER, content);
}

/// The caption.
///
/// #### Returns
///
/// the title text, never null
public String getTitle() {
return title.getText();
}

/// Sets the caption. Setting it empty removes the caption strip entirely.
///
/// #### Parameters
///
/// - `titleText`: the caption
public void setTitle(String titleText) {
title.setText(titleText == null ? "" : titleText);
title.setHidden(title.getText().length() == 0);
}

/// The caption component, for a caller that needs to style or replace its icon.
///
/// #### Returns
///
/// the title label
public Label getTitleComponent() {
return title;
}

/// The container the grouped controls live in.
///
/// #### Returns
///
/// the content container
public Container getContentPane() {
return content;
}

/// @inheritDoc
///
/// Routed into the content pane, so an ordinary add means "add to the group" rather than
/// "add beside the caption". `Container#add(Component)` is final and delegates here, so
/// overriding this covers the chaining form too.
@Override
public void addComponent(com.codename1.ui.Component cmp) {
if (content == null) {
// During the super constructor, before the content pane exists.
super.addComponent(cmp);
return;
}
content.addComponent(cmp);
}

/// @inheritDoc
///
/// The constraint belongs to the CONTENT layout, not to the BorderLayout that positions
/// the caption. That layout is an implementation detail, and a caller passing
/// `BorderLayout.SOUTH` means "below the other controls in this group", never "outside the
/// box, under the frame".
@Override
public void addComponent(Object constraints, com.codename1.ui.Component cmp) {
if (content == null) {
super.addComponent(constraints, cmp);
return;
}
content.addComponent(constraints, cmp);
}

/// @inheritDoc
@Override
public void removeComponent(com.codename1.ui.Component cmp) {
if (cmp == title || cmp == content) { //NOPMD CompareObjectsWithEquals
super.removeComponent(cmp);
return;
}
content.removeComponent(cmp);
}

/// @inheritDoc
@Override
public void removeAll() {
content.removeAll();
}
}
Loading
Loading