feat(demo): add language selection window and update localization keys

This commit is contained in:
extio-de
2025-12-29 20:08:49 +01:00
parent 3d696d658b
commit cf14bd3c56
2 changed files with 155 additions and 40 deletions

View File

@@ -1,5 +1,5 @@
--- ---
curId: 8 curId: 10
descriptions: descriptions:
test-2: "" test-2: ""
test-1: "aaa" test-1: "aaa"
@@ -19,6 +19,8 @@ languages:
test-6: "OK" test-6: "OK"
test-7: "Toggle Audio" test-7: "Toggle Audio"
test-8: "ScrollArea Demo" test-8: "ScrollArea Demo"
test-9: "Select Language"
test-10: "Current: "
de: de:
test-1: "hallo" test-1: "hallo"
test-2: "welt" test-2: "welt"
@@ -28,6 +30,8 @@ languages:
test-6: "Ok" test-6: "Ok"
test-7: "Audio umschalten" test-7: "Audio umschalten"
test-8: "ScrollArea Demo" test-8: "ScrollArea Demo"
test-9: "Sprache auswählen"
test-10: "Aktuell: "
languagesInfo: languagesInfo:
en: en:
name: "English" name: "English"

View File

@@ -18,11 +18,10 @@ import de.extio.game_engine.renderer.container.ScrollArea;
import de.extio.game_engine.renderer.container.Window; import de.extio.game_engine.renderer.container.Window;
import de.extio.game_engine.renderer.model.RenderingBoLayer; import de.extio.game_engine.renderer.model.RenderingBoLayer;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo;
import de.extio.game_engine.renderer.model.bo.DrawEffectRenderingBo;
import de.extio.game_engine.renderer.model.bo.DrawEffectRenderingBoEffects;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.ButtonControl; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.ButtonControl;
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.LabelControl; import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.LabelControl;
import de.extio.game_engine.renderer.model.color.RgbaColor; import de.extio.game_engine.renderer.model.bo.DrawEffectRenderingBo;
import de.extio.game_engine.renderer.model.bo.DrawEffectRenderingBoEffects;
import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBo; import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBo;
import de.extio.game_engine.renderer.model.bo.DrawImageRenderingBo; import de.extio.game_engine.renderer.model.bo.DrawImageRenderingBo;
import de.extio.game_engine.renderer.model.bo.HorizontalAlignment; import de.extio.game_engine.renderer.model.bo.HorizontalAlignment;
@@ -44,10 +43,10 @@ public class DemoModule extends AbstractClientModule {
@Autowired @Autowired
private EventService eventService; private EventService eventService;
@Autowired @Autowired
private LocalizationService localizationService; private LocalizationService localizationService;
@Autowired @Autowired
private ThemeManager themeManager; private ThemeManager themeManager;
@@ -58,11 +57,15 @@ public class DemoModule extends AbstractClientModule {
private Window themeSelectionWindow; private Window themeSelectionWindow;
private Window scrollAreaWindow; private Window scrollAreaWindow;
private Window languageSelectionWindow;
private ScrollArea scrollArea; private ScrollArea scrollArea;
private final Map<String, String> themeSelectionByControlId = new HashMap<>(); private final Map<String, String> themeSelectionByControlId = new HashMap<>();
private final Map<String, String> languageSelectionByControlId = new HashMap<>();
private boolean audioMuted; private boolean audioMuted;
@Override @Override
@@ -92,7 +95,14 @@ public class DemoModule extends AbstractClientModule {
this.scrollAreaWindow.setDraggable(true); this.scrollAreaWindow.setDraggable(true);
this.scrollAreaWindow.setCloseButton(true); this.scrollAreaWindow.setCloseButton(true);
this.scrollAreaWindow.setParent(this.mainWindow); this.scrollAreaWindow.setParent(this.mainWindow);
this.languageSelectionWindow = this.applicationContext.getBean(Window.class);
this.languageSelectionWindow.setNormalizedDimension(RendererControl.REFERENCE_RESOLUTION.divide(7).multiply(2));
this.languageSelectionWindow.setNormalizedPosition(centeredPosition(this.languageSelectionWindow.getNormalizedDimension()));
this.languageSelectionWindow.setDraggable(true);
this.languageSelectionWindow.setCloseButton(true);
this.languageSelectionWindow.setParent(this.mainWindow);
this.scrollArea = this.applicationContext.getBean(ScrollArea.class); this.scrollArea = this.applicationContext.getBean(ScrollArea.class);
this.scrollAreaWindow.addComponent(this.scrollArea); this.scrollAreaWindow.addComponent(this.scrollArea);
@@ -106,6 +116,7 @@ public class DemoModule extends AbstractClientModule {
this.getModuleService().unloadModule(this.secondaryWindow.getId()); this.getModuleService().unloadModule(this.secondaryWindow.getId());
this.getModuleService().unloadModule(this.themeSelectionWindow.getId()); this.getModuleService().unloadModule(this.themeSelectionWindow.getId());
this.getModuleService().unloadModule(this.scrollAreaWindow.getId()); this.getModuleService().unloadModule(this.scrollAreaWindow.getId());
this.getModuleService().unloadModule(this.languageSelectionWindow.getId());
} }
@Override @Override
@@ -124,6 +135,7 @@ public class DemoModule extends AbstractClientModule {
this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false); this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false);
this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), false); this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), false);
this.getModuleService().changeActiveState(this.scrollAreaWindow.getId(), false); this.getModuleService().changeActiveState(this.scrollAreaWindow.getId(), false);
this.getModuleService().changeActiveState(this.languageSelectionWindow.getId(), false);
} }
@Override @Override
@@ -138,6 +150,7 @@ public class DemoModule extends AbstractClientModule {
this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false); this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false);
this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), false); this.getModuleService().changeActiveState(this.themeSelectionWindow.getId(), false);
this.getModuleService().changeActiveState(this.scrollAreaWindow.getId(), false); this.getModuleService().changeActiveState(this.scrollAreaWindow.getId(), false);
this.getModuleService().changeActiveState(this.languageSelectionWindow.getId(), false);
this.audioController.stopMusic(); this.audioController.stopMusic();
} }
@@ -167,17 +180,37 @@ public class DemoModule extends AbstractClientModule {
this.getModuleService().changeActiveState(this.scrollAreaWindow.getId(), true); this.getModuleService().changeActiveState(this.scrollAreaWindow.getId(), true);
this.getModuleService().changeDisplayState(this.scrollAreaWindow.getId(), true); this.getModuleService().changeDisplayState(this.scrollAreaWindow.getId(), true);
} }
case "DemoModule_MainWindow_Label_Language" -> {
this.audioController.play(new StaticResource(List.of("audio"), "contact0.ogg"));
this.setupLanguageSelectionWindow();
this.getModuleService().changeActiveState(this.languageSelectionWindow.getId(), true);
this.getModuleService().changeDisplayState(this.languageSelectionWindow.getId(), true);
}
case "DemoModule_SecondaryWindow_Button_Ok" -> { case "DemoModule_SecondaryWindow_Button_Ok" -> {
this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg")); this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg"));
this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false); this.getModuleService().changeActiveState(this.secondaryWindow.getId(), false);
} }
default -> { default -> {
final var themeName = this.themeSelectionByControlId.get(event.getId()); final var themeName = this.themeSelectionByControlId.get(event.getId());
if (themeName != null) { if (themeName != null) {
this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg")); this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg"));
this.themeManager.setCurrentTheme(themeName); this.themeManager.setCurrentTheme(themeName);
break;
}
final var languageShort = this.languageSelectionByControlId.get(event.getId());
if (languageShort != null) {
this.audioController.play(new StaticResource(List.of("audio"), "alert0.ogg"));
this.localizationService.setLanguage(languageShort);
this.getModuleService().changeActiveState(this.languageSelectionWindow.getId(), false);
// refresh UI
this.getModuleService().changeActiveState(this.id, false);
this.getModuleService().changeActiveState(this.id, true);
this.getModuleService().changeDisplayState(this.id, true);
break;
} }
} }
} }
@@ -219,22 +252,32 @@ public class DemoModule extends AbstractClientModule {
bo = this.renderingBoPool.acquire("DemoModule_MainWindow_Label_Start", ControlRenderingBo.class) bo = this.renderingBoPool.acquire("DemoModule_MainWindow_Label_Start", ControlRenderingBo.class)
.setCaption(this.localizationService.translate("test-4")) .setCaption(this.localizationService.translate("test-4"))
.setFontSize(96) .setFontSize(78)
.setType(LabelControl.class) .setType(LabelControl.class)
.setVisible(true) .setVisible(true)
.setEnabled(true) .setEnabled(true)
.withDimensionAbsolute(RendererControl.REFERENCE_RESOLUTION.divide(9).multiply(7).substract(20).getX(), 120) .withDimensionAbsolute(RendererControl.REFERENCE_RESOLUTION.divide(9).multiply(7).substract(20).getX(), 120)
.withPositionRelative(10, 500); .withPositionRelative(10, 480);
this.mainWindow.putRenderingBo(bo);
bo = this.renderingBoPool.acquire("DemoModule_MainWindow_Label_Language", ControlRenderingBo.class)
.setCaption(this.localizationService.translate("test-9"))
.setFontSize(78)
.setType(LabelControl.class)
.setVisible(true)
.setEnabled(true)
.withDimensionAbsolute(RendererControl.REFERENCE_RESOLUTION.divide(9).multiply(7).substract(20).getX(), 120)
.withPositionRelative(10, 580);
this.mainWindow.putRenderingBo(bo); this.mainWindow.putRenderingBo(bo);
bo = this.renderingBoPool.acquire("DemoModule_MainWindow_Button_ScrollDemo", ControlRenderingBo.class) bo = this.renderingBoPool.acquire("DemoModule_MainWindow_Button_ScrollDemo", ControlRenderingBo.class)
.setCaption(this.localizationService.translate("test-8")) .setCaption(this.localizationService.translate("test-8"))
.setFontSize(96) .setFontSize(78)
.setType(LabelControl.class) .setType(LabelControl.class)
.setVisible(true) .setVisible(true)
.setEnabled(true) .setEnabled(true)
.withDimensionAbsolute(RendererControl.REFERENCE_RESOLUTION.divide(9).multiply(7).substract(20).getX(), 120) .withDimensionAbsolute(RendererControl.REFERENCE_RESOLUTION.divide(9).multiply(7).substract(20).getX(), 120)
.withPositionRelative(10, 630); .withPositionRelative(10, 690);
this.mainWindow.putRenderingBo(bo); this.mainWindow.putRenderingBo(bo);
} }
@@ -262,7 +305,7 @@ public class DemoModule extends AbstractClientModule {
this.mainWindow.draw(); this.mainWindow.draw();
return; return;
} }
final var buttonX = Math.max(0, this.mainWindow.getNormalizedDimension().getX() - 64 - 21); final var buttonX = Math.max(0, this.mainWindow.getNormalizedDimension().getX() - 64 - 21);
final var buttonY = 20; final var buttonY = 20;
final DrawImageRenderingBo overlayBo = this.renderingBoPool.acquire("DemoModule_MainWindow_Button_Audio_Overlay", DrawImageRenderingBo.class); final DrawImageRenderingBo overlayBo = this.renderingBoPool.acquire("DemoModule_MainWindow_Button_Audio_Overlay", DrawImageRenderingBo.class);
@@ -300,16 +343,13 @@ public class DemoModule extends AbstractClientModule {
.withPositionRelative(RendererControl.REFERENCE_RESOLUTION.divide(7).getX() - 80, 200); .withPositionRelative(RendererControl.REFERENCE_RESOLUTION.divide(7).getX() - 80, 200);
this.secondaryWindow.putRenderingBo(bo); this.secondaryWindow.putRenderingBo(bo);
} }
private void setupThemeSelectionWindow() { private void setupThemeSelectionWindow() {
this.themeSelectionWindow.clearRenderingBos(); this.themeSelectionWindow.clearRenderingBos();
this.themeSelectionByControlId.clear(); this.themeSelectionByControlId.clear();
final var themes = this.themeManager.getAvailableThemeNames(); final var themes = this.themeManager.getAvailableThemeNames();
final var title = themes.size() <= 1
? "Theme"
: "Select Theme";
final var paddingX = 20; final var paddingX = 20;
final var paddingBottom = 24; final var paddingBottom = 24;
final var columns = 2; final var columns = 2;
@@ -321,24 +361,24 @@ public class DemoModule extends AbstractClientModule {
final var startY = 150; final var startY = 150;
final var gapY = 14; final var gapY = 14;
final var rows = (themes.size() + columns - 1) / columns; final var rows = (themes.size() + columns - 1) / columns;
final var desiredWidth = (paddingX * 2) + (columns * buttonWidth) + ((columns - 1) * columnGap); final var desiredWidth = (paddingX * 2) + (columns * buttonWidth) + ((columns - 1) * columnGap);
final var desiredHeight = startY + (rows * buttonHeight) + (Math.max(0, rows - 1) * gapY) + paddingBottom; final var desiredHeight = startY + (rows * buttonHeight) + (Math.max(0, rows - 1) * gapY) + paddingBottom;
this.themeSelectionWindow.setNormalizedDimension(ImmutableCoordI2.create(desiredWidth, desiredHeight)); this.themeSelectionWindow.setNormalizedDimension(ImmutableCoordI2.create(desiredWidth, desiredHeight));
this.themeSelectionWindow.setNormalizedPosition(centeredPosition(ImmutableCoordI2.create(desiredWidth, desiredHeight))); this.themeSelectionWindow.setNormalizedPosition(centeredPosition(ImmutableCoordI2.create(desiredWidth, desiredHeight)));
var bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Text_Title", DrawFontRenderingBo.class) var bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Text_Title", DrawFontRenderingBo.class)
.setText(title) .setText(this.localizationService.translate("test-4"))
.setSize(42) .setSize(42)
.setAlignment(HorizontalAlignment.CENTER) .setAlignment(HorizontalAlignment.CENTER)
.withDimensionAbsolute(desiredWidth - (paddingX * 2), headerHeight) .withDimensionAbsolute(desiredWidth - (paddingX * 2), headerHeight)
.withPositionRelative(paddingX, 40); .withPositionRelative(paddingX, 40);
this.themeSelectionWindow.putRenderingBo(bo); this.themeSelectionWindow.putRenderingBo(bo);
final var currentTheme = this.themeManager.getCurrentTheme(); final var currentTheme = this.themeManager.getCurrentTheme();
final var currentThemeName = currentTheme != null ? currentTheme.getName() : ""; final var currentThemeName = currentTheme != null ? currentTheme.getName() : "";
bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Text_Current", DrawFontRenderingBo.class) bo = this.renderingBoPool.acquire("DemoModule_ThemeSelect_Text_Current", DrawFontRenderingBo.class)
.setText(currentThemeName.isBlank() ? "" : "Current: " + currentThemeName) .setText(currentThemeName.isBlank() ? "" : this.localizationService.translate("test-10") + currentThemeName)
.setSize(24) .setSize(24)
.setAlignment(HorizontalAlignment.CENTER) .setAlignment(HorizontalAlignment.CENTER)
.withDimensionAbsolute(desiredWidth - (paddingX * 2), currentHeight) .withDimensionAbsolute(desiredWidth - (paddingX * 2), currentHeight)
@@ -353,7 +393,7 @@ public class DemoModule extends AbstractClientModule {
final var row = i / columns; final var row = i / columns;
final var x = paddingX + (column * (buttonWidth + columnGap)); final var x = paddingX + (column * (buttonWidth + columnGap));
final var y = startY + (row * (buttonHeight + gapY)); final var y = startY + (row * (buttonHeight + gapY));
bo = this.renderingBoPool.acquire(controlId, ControlRenderingBo.class) bo = this.renderingBoPool.acquire(controlId, ControlRenderingBo.class)
.setCaption(themeName) .setCaption(themeName)
.setType(ButtonControl.class) .setType(ButtonControl.class)
@@ -365,7 +405,71 @@ public class DemoModule extends AbstractClientModule {
this.themeSelectionWindow.putRenderingBo(bo); this.themeSelectionWindow.putRenderingBo(bo);
} }
} }
private void setupLanguageSelectionWindow() {
this.languageSelectionWindow.clearRenderingBos();
this.languageSelectionByControlId.clear();
final var languages = this.localizationService.getLanguages();
final var title = "Select Language";
final var paddingX = 20;
final var paddingBottom = 24;
final var columns = 2;
final var columnGap = 24;
final var buttonWidth = 340;
final var buttonHeight = 54;
final var headerHeight = 60;
final var currentHeight = 40;
final var startY = 150;
final var gapY = 14;
final var rows = (languages.size() + columns - 1) / columns;
final var desiredWidth = (paddingX * 2) + (columns * buttonWidth) + ((columns - 1) * columnGap);
final var desiredHeight = startY + (rows * buttonHeight) + (Math.max(0, rows - 1) * gapY) + paddingBottom;
this.languageSelectionWindow.setNormalizedDimension(ImmutableCoordI2.create(desiredWidth, desiredHeight));
this.languageSelectionWindow.setNormalizedPosition(centeredPosition(ImmutableCoordI2.create(desiredWidth, desiredHeight)));
var bo = this.renderingBoPool.acquire("DemoModule_LanguageSelect_Text_Title", DrawFontRenderingBo.class)
.setText(title)
.setSize(42)
.setAlignment(HorizontalAlignment.CENTER)
.withDimensionAbsolute(desiredWidth - (paddingX * 2), headerHeight)
.withPositionRelative(paddingX, 40);
this.languageSelectionWindow.putRenderingBo(bo);
final var currentLanguage = this.localizationService.getCurrentLanguage();
final var currentLangObj = languages.stream().filter(l -> l.getShortName().equals(currentLanguage)).findFirst().orElse(null);
final var currentLangName = currentLangObj != null ? currentLangObj.getName() : currentLanguage;
bo = this.renderingBoPool.acquire("DemoModule_LanguageSelect_Text_Current", DrawFontRenderingBo.class)
.setText("Current: " + currentLangName)
.setSize(24)
.setAlignment(HorizontalAlignment.CENTER)
.withDimensionAbsolute(desiredWidth - (paddingX * 2), currentHeight)
.withPositionRelative(paddingX, 95);
this.languageSelectionWindow.putRenderingBo(bo);
for (var i = 0; i < languages.size(); i++) {
final var language = languages.get(i);
final var controlId = "DemoModule_LanguageSelect_Button_" + sanitizeLanguageId(language.getShortName());
this.languageSelectionByControlId.put(controlId, language.getShortName());
final var column = i % columns;
final var row = i / columns;
final var x = paddingX + (column * (buttonWidth + columnGap));
final var y = startY + (row * (buttonHeight + gapY));
bo = this.renderingBoPool.acquire(controlId, ControlRenderingBo.class)
.setCaption(language.getName())
.setType(ButtonControl.class)
.setFontSize(28)
.setVisible(true)
.setEnabled(true)
.withDimensionAbsolute(buttonWidth, buttonHeight)
.withPositionRelative(x, y);
this.languageSelectionWindow.putRenderingBo(bo);
}
}
private void setupScrollAreaWindow() { private void setupScrollAreaWindow() {
this.scrollAreaWindow.clearRenderingBos(); this.scrollAreaWindow.clearRenderingBos();
@@ -376,7 +480,7 @@ public class DemoModule extends AbstractClientModule {
.withDimensionAbsolute(this.scrollAreaWindow.getNormalizedDimension().getX() - 40, 50) .withDimensionAbsolute(this.scrollAreaWindow.getNormalizedDimension().getX() - 40, 50)
.withPositionRelative(20, 30); .withPositionRelative(20, 30);
this.scrollAreaWindow.putRenderingBo(bo); this.scrollAreaWindow.putRenderingBo(bo);
this.scrollArea.setRelativePosition(ImmutableCoordI2.create(20, 65)); this.scrollArea.setRelativePosition(ImmutableCoordI2.create(20, 65));
this.scrollArea.setDimension(this.scrollAreaWindow.getNormalizedDimension().substract(40, 90)); this.scrollArea.setDimension(this.scrollAreaWindow.getNormalizedDimension().substract(40, 90));
@@ -393,7 +497,7 @@ public class DemoModule extends AbstractClientModule {
.withDimensionAbsolute(150, 60) .withDimensionAbsolute(150, 60)
.withPositionRelative(1, y + 2); .withPositionRelative(1, y + 2);
this.scrollArea.putRenderingBo(bo); this.scrollArea.putRenderingBo(bo);
bo = this.renderingBoPool.acquire("DemoModule_ScrollAreaWindow_Text_" + i, DrawFontRenderingBo.class) bo = this.renderingBoPool.acquire("DemoModule_ScrollAreaWindow_Text_" + i, DrawFontRenderingBo.class)
.setText(String.valueOf(i)) .setText(String.valueOf(i))
.setSize(28) .setSize(28)
@@ -401,15 +505,15 @@ public class DemoModule extends AbstractClientModule {
.withDimensionAbsolute(150, 60) .withDimensionAbsolute(150, 60)
.setLayer(RenderingBoLayer.UI0) .setLayer(RenderingBoLayer.UI0)
.withPositionRelative(250, y + 22); .withPositionRelative(250, y + 22);
this.scrollArea.putRenderingBo(bo); this.scrollArea.putRenderingBo(bo);
bo = this.renderingBoPool.acquire("DemoModule_ScrollAreaWindow_Effect_" + i, DrawEffectRenderingBo.class) bo = this.renderingBoPool.acquire("DemoModule_ScrollAreaWindow_Effect_" + i, DrawEffectRenderingBo.class)
.setRelativeCoordinates(List.of(ImmutableCoordI2.create(64, 64))) .setRelativeCoordinates(List.of(ImmutableCoordI2.create(64, 64)))
.setEffect(DrawEffectRenderingBoEffects.DECORATIVE_BORDER_FILLED) .setEffect(DrawEffectRenderingBoEffects.DECORATIVE_BORDER_FILLED)
.setCustomInt0(4) .setCustomInt0(4)
.setLayer(RenderingBoLayer.UI0) .setLayer(RenderingBoLayer.UI0)
.withPositionRelative(350, y); .withPositionRelative(350, y);
this.scrollArea.putRenderingBo(bo); this.scrollArea.putRenderingBo(bo);
bo = this.renderingBoPool.acquire("DemoModule_ScrollArea_Button_" + i, ControlRenderingBo.class) bo = this.renderingBoPool.acquire("DemoModule_ScrollArea_Button_" + i, ControlRenderingBo.class)
.setCaption("Button " + (i + 1)) .setCaption("Button " + (i + 1))
@@ -436,11 +540,18 @@ public class DemoModule extends AbstractClientModule {
final var y = Math.max(0, (ref.getY() - dimension.getY()) / 2); final var y = Math.max(0, (ref.getY() - dimension.getY()) / 2);
return ImmutableCoordI2.create(x, y); return ImmutableCoordI2.create(x, y);
} }
private static String sanitizeThemeId(final String themeName) { private static String sanitizeThemeId(final String themeName) {
if (themeName == null || themeName.isBlank()) { if (themeName == null || themeName.isBlank()) {
return "Unknown"; return "Unknown";
} }
return themeName.replaceAll("[^a-zA-Z0-9]+", "_"); return themeName.replaceAll("[^a-zA-Z0-9]+", "_");
} }
private static String sanitizeLanguageId(final String shortName) {
if (shortName == null || shortName.isBlank()) {
return "Unknown";
}
return shortName.replaceAll("[^a-zA-Z0-9]+", "_");
}
} }