阻止在 JFXCustomColorPickerDialog 输入非法颜色信息 (#5368)

Co-authored-by: Glavo <zjx001202@gmail.com>
This commit is contained in:
Damon Lu
2026-02-11 21:28:30 +08:00
committed by GitHub
parent 90e6782193
commit f79d560605
2 changed files with 74 additions and 0 deletions

View File

@@ -34,14 +34,17 @@ import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D; import javafx.geometry.Rectangle2D;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TextFormatter;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.stage.*; import javafx.stage.*;
import javafx.util.Duration; import javafx.util.Duration;
import org.jackhuang.hmcl.setting.StyleSheets; import org.jackhuang.hmcl.setting.StyleSheets;
import org.jackhuang.hmcl.util.StringUtils;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
/** /**
* @author Shadi Shaheen * @author Shadi Shaheen
@@ -95,14 +98,17 @@ public class JFXCustomColorPickerDialog extends StackPane {
rgbField.getStyleClass().add("custom-color-field"); rgbField.getStyleClass().add("custom-color-field");
rgbField.setPromptText("RGB Color"); rgbField.setPromptText("RGB Color");
rgbField.setTextFormatter(colorCharFormatter());
rgbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal)); rgbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
hsbField.getStyleClass().add("custom-color-field"); hsbField.getStyleClass().add("custom-color-field");
hsbField.setPromptText("HSB Color"); hsbField.setPromptText("HSB Color");
hsbField.setTextFormatter(colorCharFormatter());
hsbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal)); hsbField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
hexField.getStyleClass().add("custom-color-field"); hexField.getStyleClass().add("custom-color-field");
hexField.setPromptText("#HEX Color"); hexField.setPromptText("#HEX Color");
hexField.setTextFormatter(colorCharFormatter());
hexField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal)); hexField.textProperty().addListener((o, oldVal, newVal) -> updateColorFromUserInput(newVal));
StackPane tabContent = new StackPane(); StackPane tabContent = new StackPane();
@@ -404,4 +410,20 @@ public class JFXCustomColorPickerDialog extends StackPane {
dialog.setMinWidth(minWidth); dialog.setMinWidth(minWidth);
dialog.setMinHeight(minHeight); dialog.setMinHeight(minHeight);
} }
private static final Pattern COLOR_CHAR_PATTERN = Pattern.compile("[0-9a-zA-Z#(),%.\\s]*");
private static TextFormatter<String> colorCharFormatter() {
return new TextFormatter<>(change -> {
if (!change.isContentChange()) return change;
String ins = StringUtils.toHalfWidth(change.getText());
if (!COLOR_CHAR_PATTERN.matcher(ins).matches()) return null;
String full = StringUtils.toHalfWidth(change.getControlNewText());
long h = full.chars().filter(c -> c == '#').count();
if (h > 1 || (h == 1 && full.indexOf('#') != 0)) return null;
change.setText(ins);
return change;
});
}
} }

View File

@@ -276,6 +276,58 @@ public final class StringUtils {
return false; return false;
} }
/// Check if the code point is a full-width character.
public static boolean isFullWidth(int codePoint) {
return codePoint >= '\uff10' && codePoint <= '\uff19' // full-width digits
|| codePoint >= '\uff21' && codePoint <= '\uff3a' // full-width uppercase letters
|| codePoint >= '\uff41' && codePoint <= '\uff5a' // full-width lowercase letters
|| codePoint == '\uff08' // full-width left parenthesis
|| codePoint == '\uff09' // full-width right parenthesis
|| codePoint == '\uff0c' // full-width comma
|| codePoint == '\uff05' // full-width percent sign
|| codePoint == '\uff0e' // full-width period
|| codePoint == '\u3000' // full-width ideographic space
|| codePoint == '\uff03'; // full-width number sign
}
/// Convert full-width characters to half-width characters.
public static String toHalfWidth(String str) {
int i = 0;
while (i < str.length()) {
int cp = str.codePointAt(i);
if (isFullWidth(cp)) {
break;
}
i += Character.charCount(cp);
}
if (i == str.length())
return str;
var builder = new StringBuilder(str.length());
builder.append(str, 0, i);
while (i < str.length()) {
int c = str.codePointAt(i);
if (c >= '\uff10' && c <= '\uff19') builder.append((char) (c - 0xfee0));
else if (c >= '\uff21' && c <= '\uff3a') builder.append((char) (c - 0xfee0));
else if (c >= '\uff41' && c <= '\uff5a') builder.append((char) (c - 0xfee0));
else if (c == '\uff08') builder.append('(');
else if (c == '\uff09') builder.append(')');
else if (c == '\uff0c') builder.append(',');
else if (c == '\uff05') builder.append('%');
else if (c == '\uff0e') builder.append('.');
else if (c == '\u3000') builder.append(' ');
else if (c == '\uff03') builder.append('#');
else builder.appendCodePoint(c);
i += Character.charCount(c);
}
return builder.toString();
}
private static boolean isVarNameStart(char ch) { private static boolean isVarNameStart(char ch) {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_'; return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
} }