Skip to content

Commit eee091a

Browse files
committed
Merge branch 'master' into simd-revisite
2 parents d2fa976 + d9ae900 commit eee091a

File tree

19 files changed

+617
-54
lines changed

19 files changed

+617
-54
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env python3
2+
"""Updates Codename One Material icon font and FontImage constants from upstream."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import hashlib
8+
import pathlib
9+
import re
10+
import sys
11+
import urllib.request
12+
13+
REPO_ROOT = pathlib.Path(__file__).resolve().parents[2]
14+
FONT_PATH = REPO_ROOT / "CodenameOne" / "src" / "material-design-font.ttf"
15+
FONT_IMAGE_PATH = REPO_ROOT / "CodenameOne" / "src" / "com" / "codename1" / "ui" / "FontImage.java"
16+
17+
MATERIAL_FONT_URL = (
18+
"https://raw.githubusercontent.com/google/material-design-icons/master/font/MaterialIcons-Regular.ttf"
19+
)
20+
MATERIAL_CODEPOINTS_URL = (
21+
"https://raw.githubusercontent.com/google/material-design-icons/master/font/MaterialIcons-Regular.codepoints"
22+
)
23+
MATERIAL_CSS_URL = "https://fonts.googleapis.com/icon?family=Material+Icons"
24+
25+
CONSTANT_DOC = " /// Material design icon font character code see\\n /// https://www.material.io/resources/icons/ for full list\\n"
26+
CONSTANT_RE = re.compile(r"^\s*public static final char MATERIAL_")
27+
SENTINEL = " private static Font materialDesignFont;"
28+
29+
30+
def download_text(url: str) -> str:
31+
with urllib.request.urlopen(url) as response:
32+
return response.read().decode("utf-8")
33+
34+
35+
def download_bytes(url: str) -> bytes:
36+
with urllib.request.urlopen(url) as response:
37+
return response.read()
38+
39+
40+
def hash_bytes(data: bytes) -> str:
41+
return hashlib.sha256(data).hexdigest()
42+
43+
44+
def material_constant_name(icon_name: str) -> str:
45+
return "MATERIAL_" + re.sub(r"[^A-Z0-9_]", "_", icon_name.upper())
46+
47+
48+
def parse_codepoints(codepoints_text: str) -> list[tuple[str, str]]:
49+
out: list[tuple[str, str]] = []
50+
for line in codepoints_text.splitlines():
51+
line = line.strip()
52+
if not line:
53+
continue
54+
parts = line.split()
55+
if len(parts) != 2:
56+
raise ValueError(f"Unexpected codepoints line format: {line}")
57+
out.append((parts[0], parts[1]))
58+
return out
59+
60+
61+
def generate_constants_block(entries: list[tuple[str, str]]) -> str:
62+
lines: list[str] = []
63+
for icon_name, codepoint in entries:
64+
const_name = material_constant_name(icon_name)
65+
lines.append(CONSTANT_DOC.rstrip("\n"))
66+
lines.append(f" public static final char {const_name} = '\\u{codepoint.upper()}';")
67+
return "\n".join(lines) + "\n"
68+
69+
70+
def update_fontimage(constants_block: str, check_only: bool) -> bool:
71+
source = FONT_IMAGE_PATH.read_text(encoding="utf-8")
72+
lines = source.splitlines(keepends=True)
73+
74+
start_idx = None
75+
end_idx = None
76+
for i, line in enumerate(lines):
77+
if start_idx is None and CONSTANT_RE.match(line):
78+
start_idx = i
79+
if line.strip("\n") == SENTINEL:
80+
end_idx = i
81+
break
82+
83+
if start_idx is None or end_idx is None or start_idx >= end_idx:
84+
raise RuntimeError("Couldn't locate material constants block in FontImage.java")
85+
86+
new_source = "".join(lines[:start_idx]) + constants_block + "".join(lines[end_idx:])
87+
changed = new_source != source
88+
if changed and not check_only:
89+
FONT_IMAGE_PATH.write_text(new_source, encoding="utf-8")
90+
return changed
91+
92+
93+
def update_font(remote_font_bytes: bytes, check_only: bool) -> bool:
94+
local_font_bytes = FONT_PATH.read_bytes()
95+
changed = hash_bytes(local_font_bytes) != hash_bytes(remote_font_bytes)
96+
if changed and not check_only:
97+
FONT_PATH.write_bytes(remote_font_bytes)
98+
return changed
99+
100+
101+
def extract_css_font_version(css: str) -> str | None:
102+
match = re.search(r"/materialicons/v(\d+)/", css)
103+
return match.group(1) if match else None
104+
105+
106+
def main() -> int:
107+
parser = argparse.ArgumentParser(description=__doc__)
108+
parser.add_argument("--check", action="store_true", help="Only check if an update is needed")
109+
args = parser.parse_args()
110+
111+
print("Fetching Material Icons metadata...")
112+
css = download_text(MATERIAL_CSS_URL)
113+
version = extract_css_font_version(css)
114+
if version:
115+
print(f"Detected Google Fonts Material Icons version: v{version}")
116+
117+
remote_font = download_bytes(MATERIAL_FONT_URL)
118+
codepoints = download_text(MATERIAL_CODEPOINTS_URL)
119+
entries = parse_codepoints(codepoints)
120+
constants_block = generate_constants_block(entries)
121+
122+
font_changed = update_font(remote_font, check_only=args.check)
123+
constants_changed = update_fontimage(constants_block, check_only=args.check)
124+
125+
if font_changed or constants_changed:
126+
print("Material icons update detected.")
127+
return 0
128+
129+
print("Material icons are already up to date.")
130+
return 0
131+
132+
133+
if __name__ == "__main__":
134+
sys.exit(main())
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
name: Material Icons Update
2+
3+
on:
4+
schedule:
5+
- cron: '0 6 * * 1'
6+
workflow_dispatch:
7+
pull_request:
8+
paths:
9+
- '.github/workflows/material-icons-update.yml'
10+
- '.github/scripts/update_material_icons.py'
11+
12+
permissions:
13+
contents: write
14+
pull-requests: write
15+
16+
jobs:
17+
update-material-icons:
18+
runs-on: ubuntu-latest
19+
20+
steps:
21+
- name: Check out repository
22+
uses: actions/checkout@v4
23+
24+
- name: Set up Python
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version: '3.x'
28+
29+
- name: Update Material Icons font and constants (scheduled/manual)
30+
if: github.event_name != 'pull_request'
31+
run: python .github/scripts/update_material_icons.py
32+
33+
- name: Create pull request
34+
if: github.event_name != 'pull_request'
35+
uses: peter-evans/create-pull-request@v7
36+
with:
37+
commit-message: "Update Material Icons font and FontImage constants"
38+
title: "Update Material Icons font and FontImage constants"
39+
body: |
40+
## Summary
41+
- Updates `CodenameOne/src/material-design-font.ttf` from upstream Material Icons.
42+
- Regenerates `FontImage.MATERIAL_*` constants from upstream codepoints.
43+
44+
This PR was created automatically by the weekly Material Icons update workflow.
45+
base: master
46+
branch: automation/material-icons-update
47+
delete-branch: true
48+
labels: |
49+
dependencies
50+
automation
51+
52+
- name: Build update preview for PR validation
53+
if: github.event_name == 'pull_request'
54+
run: |
55+
python .github/scripts/update_material_icons.py
56+
if git diff --quiet; then
57+
echo "Material icons are already up to date." > material-icons-update-summary.txt
58+
else
59+
echo "Material icons update detected; see attached patch artifact." > material-icons-update-summary.txt
60+
git diff --binary > material-icons-update.patch
61+
fi
62+
63+
- name: Upload PR validation artifacts
64+
if: github.event_name == 'pull_request'
65+
uses: actions/upload-artifact@v4
66+
with:
67+
name: material-icons-update-preview
68+
path: |
69+
material-icons-update-summary.txt
70+
material-icons-update.patch
71+
if-no-files-found: error
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
* This code is free software; you can redistribute it and/or modify it
5+
* under the terms of the GNU General Public License version 2 only, as
6+
* published by the Free Software Foundation. Codename One designates this
7+
* particular file as subject to the "Classpath" exception as provided
8+
* by Oracle in the LICENSE file that accompanied this code.
9+
*
10+
* This code is distributed in the hope that it will be useful, but WITHOUT
11+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13+
* version 2 for more details (a copy is included in the LICENSE file that
14+
* accompanied this code).
15+
*
16+
* You should have received a copy of the GNU General Public License version
17+
* 2 along with this work; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
19+
*
20+
* Please contact Codename One through http://www.codenameone.com/ if you
21+
* need additional information or have any questions.
22+
*/
23+
package com.codename1.annotations;
24+
25+
import java.lang.annotation.ElementType;
26+
import java.lang.annotation.Retention;
27+
import java.lang.annotation.RetentionPolicy;
28+
import java.lang.annotation.Target;
29+
30+
/// Indicates that a class has a known concrete implementation that ParparVM can
31+
/// target directly in native (C/Objective-C) pipelines.
32+
///
33+
/// When present, the translator may bypass virtual lookup when invoking methods
34+
/// on this type by preferring the concrete class provided in {@link #name()},
35+
/// and falling back to the annotated type implementation if the concrete class
36+
/// doesn't implement the method.
37+
@Retention(RetentionPolicy.CLASS)
38+
@Target(ElementType.TYPE)
39+
public @interface Concrete {
40+
/// The fully-qualified class name of the concrete implementation to prefer
41+
/// during ParparVM native translation.
42+
String name();
43+
}

CodenameOne/src/com/codename1/components/InteractionDialog.java

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -637,8 +637,11 @@ public void showPopupDialog(Component c) {
637637
///
638638
/// - `c`: the context component which is used to position the dialog and can also be pointed at
639639
///
640-
/// - `bias`: @param bias biases the dialog to appear above/below or to the sides.
641-
/// This is ignored if there isn't enough space
640+
/// - `bias`: directional bias value. This parameter is not supported.
641+
///
642+
/// #### Deprecated
643+
///
644+
/// @deprecated The `bias` parameter is not supported for `InteractionDialog` popups. Use `#showPopupDialog(Component)` instead.
642645
public void showPopupDialog(Component c, boolean bias) {
643646
if (c == null) {
644647
throw new IllegalArgumentException("Component cannot be null");
@@ -653,7 +656,7 @@ public void showPopupDialog(Component c, boolean bias) {
653656
componentPos.setX(componentPos.getX() - c.getScrollX());
654657
componentPos.setY(componentPos.getY() - c.getScrollY());
655658
setOwner(c);
656-
showPopupDialog(componentPos, bias);
659+
showPopupDialog(componentPos);
657660
}
658661

659662
/// A popup dialog is shown with the context of a component and its selection. You should use `#setDisposeWhenPointerOutOfBounds(boolean)` to make it dispose
@@ -664,7 +667,7 @@ public void showPopupDialog(Component c, boolean bias) {
664667
///
665668
/// - `rect`: the screen rectangle to which the popup should point
666669
public void showPopupDialog(Rectangle rect) {
667-
showPopupDialog(rect, Display.getInstance().isPortrait());
670+
showPopupDialogImpl(rect, Display.getInstance().isPortrait());
668671
}
669672

670673
/// A popup dialog is shown with the context of a component and its selection. You should use `#setDisposeWhenPointerOutOfBounds(boolean)` to make it dispose
@@ -675,9 +678,16 @@ public void showPopupDialog(Rectangle rect) {
675678
///
676679
/// - `rect`: the screen rectangle to which the popup should point
677680
///
678-
/// - `bias`: @param bias biases the dialog to appear above/below or to the sides.
679-
/// This is ignored if there isn't enough space
681+
/// - `bias`: directional bias value. This parameter is not supported.
682+
///
683+
/// #### Deprecated
684+
///
685+
/// @deprecated The `bias` parameter is not supported for `InteractionDialog` popups. Use `#showPopupDialog(Rectangle)` instead.
680686
public void showPopupDialog(Rectangle rect, boolean bias) {
687+
showPopupDialog(rect);
688+
}
689+
690+
private void showPopupDialogImpl(Rectangle rect, boolean bias) {
681691
if (rect == null) {
682692
throw new IllegalArgumentException("rect cannot be null");
683693
}

CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
*/
2424
package com.codename1.impl;
2525

26+
import com.codename1.annotations.Concrete;
2627
import com.codename1.capture.VideoCaptureConstraints;
2728
import com.codename1.codescan.CodeScanner;
2829
import com.codename1.components.AudioRecorderComponent;
@@ -110,6 +111,7 @@
110111
/// Display specifically for key, pointer events and screen resolution.
111112
///
112113
/// @author Shai Almog
114+
@Concrete(name = "com.codename1.impl.ios.IOSImplementation")
113115
public abstract class CodenameOneImplementation {
114116
/// Indicates the range of "hard" RTL bidi characters in unicode
115117
private static final int RTL_RANGE_BEGIN = 0x590;

CodenameOne/src/com/codename1/ui/TextArea.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,13 @@ public void setText(String t) {
597597
//zero the ArrayList in order to initialize it on the next paint
598598
rowStrings = null;
599599
}
600+
if (growByContent
601+
&& !Objects.equals(text, old)
602+
&& getParent() != null
603+
&& Display.getInstance().isTextEditing(this)
604+
&& textMightGrowByContent(old, text)) {
605+
getParent().revalidateLater();
606+
}
600607
if (!Objects.equals(text, old)) {
601608
fireDataChanged(DataChangedListener.CHANGED, -1);
602609
}
@@ -610,6 +617,53 @@ public void setText(String t) {
610617
repaint();
611618
}
612619

620+
private boolean textMightGrowByContent(String oldText, String newText) {
621+
int oldNewLines = countNewLines(oldText);
622+
int newNewLines = countNewLines(newText);
623+
if (newNewLines > oldNewLines) {
624+
return true;
625+
}
626+
if (newText == null || oldText == null || newText.length() <= oldText.length()) {
627+
return false;
628+
}
629+
int cols = getColumns();
630+
if (cols > 1) {
631+
return estimateLineCount(newText, cols) > estimateLineCount(oldText, cols);
632+
}
633+
return false;
634+
}
635+
636+
private int countNewLines(String value) {
637+
if (value == null || value.length() == 0) {
638+
return 0;
639+
}
640+
int count = 0;
641+
for (int i = 0; i < value.length(); i++) {
642+
if (value.charAt(i) == '\n') {
643+
count++;
644+
}
645+
}
646+
return count;
647+
}
648+
649+
private int estimateLineCount(String value, int cols) {
650+
if (value == null || value.length() == 0) {
651+
return 1;
652+
}
653+
int lines = 0;
654+
int segmentLength = 0;
655+
for (int i = 0; i < value.length(); i++) {
656+
if (value.charAt(i) == '\n') {
657+
lines += Math.max(1, (segmentLength + cols - 1) / cols);
658+
segmentLength = 0;
659+
} else {
660+
segmentLength++;
661+
}
662+
}
663+
lines += Math.max(1, (segmentLength + cols - 1) / cols);
664+
return lines;
665+
}
666+
613667
/// Convenience method for numeric text fields, returns the value as a number or invalid if the value in the
614668
/// text field isn't a number
615669
///

0 commit comments

Comments
 (0)