ML: Hot reload locale system
This commit is contained in:
@@ -17,6 +17,10 @@ import serverCommandParser
|
||||
import ime
|
||||
import uiScriptLocale
|
||||
|
||||
# Multi-language hot-reload system
|
||||
from uilocaleselector import LocaleSelector
|
||||
from uilocalechange import LocaleChangeManager
|
||||
|
||||
LOGIN_DELAY_SEC = 0.0
|
||||
SKIP_LOGIN_PHASE = False
|
||||
SKIP_LOGIN_PHASE_SUPPORT_CHANNEL = False
|
||||
@@ -55,6 +59,13 @@ def GetLoginDelay():
|
||||
|
||||
app.SetGuildMarkPath("test")
|
||||
|
||||
###############################################################################
|
||||
# Multi-Language Hot-Reload System
|
||||
# All locale selector and hot-reload logic moved to:
|
||||
# - uilocaleselector.py (UI component)
|
||||
# - uilocalechange.py (hot-reload manager)
|
||||
###############################################################################
|
||||
|
||||
class ConnectingDialog(ui.ScriptWindow):
|
||||
|
||||
def __init__(self):
|
||||
@@ -144,11 +155,9 @@ class LoginWindow(ui.ScriptWindow):
|
||||
self.virtualKeyboardIsUpper = False
|
||||
self.timeOutMsg = False #Fix
|
||||
|
||||
self.language_list = []
|
||||
self.flag_button_list = []
|
||||
self.language_board = None
|
||||
self.language_popup = None
|
||||
self.__LoadLocale()
|
||||
# Multi-language hot-reload system
|
||||
self.localeSelector = None
|
||||
self.localeChangeManager = LocaleChangeManager(self)
|
||||
|
||||
def __del__(self):
|
||||
net.ClearPhaseWindow(net.PHASE_WINDOW_LOGIN, self)
|
||||
@@ -276,10 +285,9 @@ class LoginWindow(ui.ScriptWindow):
|
||||
self.connectingDialog = None
|
||||
self.loadingImage = None
|
||||
|
||||
self.language_list = []
|
||||
self.flag_button_list = []
|
||||
self.language_board = None
|
||||
self.language_popup = None
|
||||
if self.localeSelector:
|
||||
self.localeSelector.Destroy()
|
||||
self.localeSelector = None
|
||||
|
||||
self.serverBoard = None
|
||||
self.serverList = None
|
||||
@@ -465,29 +473,11 @@ class LoginWindow(ui.ScriptWindow):
|
||||
self.GetChild("key_at").SetToggleDownEvent(lambda : self.__VirtualKeyboard_SetSymbolMode())
|
||||
self.GetChild("key_at").SetToggleUpEvent(lambda : self.__VirtualKeyboard_SetAlphabetMode())
|
||||
|
||||
self.language_board = ui.ThinBoard()
|
||||
self.language_board.SetParent(self)
|
||||
self.language_board.SetSize(wndMgr.GetScreenWidth(), 35)
|
||||
self.language_board.SetPosition(0, 20)
|
||||
self.language_board.Show()
|
||||
|
||||
step = wndMgr.GetScreenWidth() / len(self.language_list)
|
||||
x = 0
|
||||
|
||||
for i, lang in enumerate(self.language_list):
|
||||
img_path = "d:/ymir work/ui/intro/login/server_flag_%s.sub" % lang
|
||||
btn = ui.Button()
|
||||
btn.SetParent(self.language_board)
|
||||
btn.SetPosition(x + 15, 10)
|
||||
btn.SetUpVisual(img_path)
|
||||
btn.SetOverVisual(img_path)
|
||||
btn.SetDownVisual(img_path)
|
||||
btn.SetToolTipText(lang.upper())
|
||||
btn.SetEvent(ui.__mem_func__(self.__ClickLanguage), i)
|
||||
btn.Show()
|
||||
|
||||
self.flag_button_list.append(btn)
|
||||
x += step
|
||||
# Create locale selector (only if it doesn't exist - during hot-reload we keep the old one)
|
||||
if not self.localeSelector:
|
||||
self.localeSelector = LocaleSelector()
|
||||
self.localeSelector.Create(self)
|
||||
self.localeSelector.SetLocaleChangedEvent(ui.__mem_func__(self.__OnLocaleChanged))
|
||||
|
||||
except:
|
||||
import exception
|
||||
@@ -608,49 +598,12 @@ class LoginWindow(ui.ScriptWindow):
|
||||
def __OnClickExitButton(self):
|
||||
self.stream.SetPhaseWindow(0)
|
||||
|
||||
def __LoadLocale(self):
|
||||
self.language_list = [
|
||||
"ae", "en", "cz", "de", "dk",
|
||||
"es", "fr", "gr", "hu", "it",
|
||||
"nl", "pl", "pt", "ro", "ru", "tr",
|
||||
]
|
||||
|
||||
def __SaveLocale(self, locale):
|
||||
try:
|
||||
with open("config/locale.cfg", "wt") as f:
|
||||
f.write(locale)
|
||||
except:
|
||||
import dbg
|
||||
dbg.LogBox("__SaveLocale error locale.cfg")
|
||||
app.Abort()
|
||||
|
||||
def __ClickLanguage(self, index):
|
||||
if index >= len(self.language_list):
|
||||
return
|
||||
|
||||
self.locale = self.language_list[index]
|
||||
|
||||
if not self.language_popup:
|
||||
self.language_popup = uiCommon.QuestionDialog()
|
||||
|
||||
self.language_popup.SetText("Change language and restart the client?")
|
||||
self.language_popup.SetAcceptEvent(ui.__mem_func__(self.__OnAcceptLanguage))
|
||||
self.language_popup.SetCancelEvent(ui.__mem_func__(self.__OnCancelLanguage))
|
||||
self.language_popup.Open()
|
||||
|
||||
def __OnAcceptLanguage(self):
|
||||
if self.language_popup:
|
||||
self.language_popup.Close()
|
||||
|
||||
self.__SaveLocale(self.locale)
|
||||
|
||||
import os
|
||||
app.Exit()
|
||||
os.popen('start "" "Metin2_Debug.exe"')
|
||||
|
||||
def __OnCancelLanguage(self):
|
||||
if self.language_popup:
|
||||
self.language_popup.Close()
|
||||
def __OnLocaleChanged(self, newLocaleCode):
|
||||
"""
|
||||
Callback when user confirms locale change.
|
||||
All the heavy lifting is done by LocaleChangeManager - this is just 3 lines!
|
||||
"""
|
||||
self.localeChangeManager.ReloadWithNewLocale(newLocaleCode, "uiscript/LoginWindow.py")
|
||||
|
||||
def __SetServerInfo(self, name):
|
||||
net.SetServerInfo(name.strip())
|
||||
|
||||
@@ -13,6 +13,8 @@ BLEND_POTION_NO_INFO = 'BLEND_POTION_NO_INFO'
|
||||
LOGIN_FAILURE_WRONG_SOCIALID = 'LOGIN_FAILURE_WRONG_SOCIALID'
|
||||
LOGIN_FAILURE_SHUTDOWN_TIME = 'LOGIN_FAILURE_SHUTDOWN_TIME'
|
||||
|
||||
LOCALE_CHANGE_CONFIRM = 'Change language to %s and reload the game?'
|
||||
|
||||
GUILD_MEMBER_COUNT_INFINITY = 'INFINITY'
|
||||
GUILD_MARK_MIN_LEVEL = '3'
|
||||
GUILD_BUILDING_LIST_TXT = '{:s}/GuildBuildingList.txt'.format(APP_GET_LOCALE_PATH)
|
||||
|
||||
472
assets/root/uilocalechange.py
Normal file
472
assets/root/uilocalechange.py
Normal file
@@ -0,0 +1,472 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Multi-Language Hot-Reload System
|
||||
=================================
|
||||
|
||||
This module provides a clean, reusable system for hot-reloading locale changes
|
||||
without restarting the client. It's designed to be window-agnostic and can be
|
||||
used with any UI window (login, game, etc.).
|
||||
|
||||
Architecture:
|
||||
- C++ side handles: LoadConfig() → LoadLocaleData() → Clear Python module cache
|
||||
- Python side handles: Module reimport → UI state save/restore → Window recreation
|
||||
|
||||
Usage:
|
||||
from uilocalechange import LocaleChangeManager
|
||||
|
||||
# In your window class __init__:
|
||||
self.localeChangeManager = LocaleChangeManager(self)
|
||||
|
||||
# When locale changes (e.g., from a locale selector):
|
||||
self.localeChangeManager.ReloadWithNewLocale(newLocaleCode)
|
||||
|
||||
Features:
|
||||
- Automatically saves and restores UI state (text inputs, selections, etc.)
|
||||
- Handles board visibility states
|
||||
- Preserves virtual keyboard state
|
||||
- Works with any window that follows the standard pattern
|
||||
"""
|
||||
|
||||
import app
|
||||
import dbg
|
||||
import ui
|
||||
import sys
|
||||
|
||||
|
||||
class LocaleChangeManager:
|
||||
"""
|
||||
Manages locale hot-reload for a UI window.
|
||||
|
||||
This class handles the complete lifecycle of a locale change:
|
||||
1. Save current UI state
|
||||
2. Call C++ to reload locale data
|
||||
3. Reimport Python locale modules
|
||||
4. Recreate UI with new locale
|
||||
5. Restore saved state
|
||||
"""
|
||||
|
||||
def __init__(self, window):
|
||||
"""
|
||||
Initialize the locale change manager.
|
||||
|
||||
Args:
|
||||
window: The UI window instance to manage (e.g., LoginWindow, GameWindow)
|
||||
"""
|
||||
self.window = window
|
||||
self.stateHandlers = {}
|
||||
self._RegisterDefaultStateHandlers()
|
||||
|
||||
def _RegisterDefaultStateHandlers(self):
|
||||
"""Register default state save/restore handlers for common UI elements."""
|
||||
# Text input fields
|
||||
self.RegisterStateHandler("idEditLine", self._SaveEditLineText, self._RestoreEditLineText)
|
||||
self.RegisterStateHandler("pwdEditLine", self._SaveEditLineText, self._RestoreEditLineText)
|
||||
|
||||
# List selections
|
||||
self.RegisterStateHandler("serverList", self._SaveListSelection, self._RestoreListSelection)
|
||||
self.RegisterStateHandler("channelList", self._SaveListSelection, self._RestoreListSelection)
|
||||
|
||||
# Virtual keyboard
|
||||
self.RegisterStateHandler("virtualKeyboard", self._SaveVisibility, self._RestoreVisibility)
|
||||
|
||||
# Server info text
|
||||
self.RegisterStateHandler("serverInfo", self._SaveTextLineText, self._RestoreTextLineText)
|
||||
|
||||
def RegisterStateHandler(self, elementName, saveFunc, restoreFunc):
|
||||
"""
|
||||
Register a custom state handler for a UI element.
|
||||
|
||||
Args:
|
||||
elementName: The attribute name of the UI element (e.g., "idEditLine")
|
||||
saveFunc: Function(element) -> stateData to save state
|
||||
restoreFunc: Function(element, stateData) to restore state
|
||||
"""
|
||||
self.stateHandlers[elementName] = (saveFunc, restoreFunc)
|
||||
|
||||
def _SaveEditLineText(self, editLine):
|
||||
"""Save text from an edit line."""
|
||||
if editLine:
|
||||
return editLine.GetText()
|
||||
return ""
|
||||
|
||||
def _RestoreEditLineText(self, editLine, text):
|
||||
"""Restore text to an edit line."""
|
||||
if editLine and text:
|
||||
editLine.SetText(text)
|
||||
|
||||
def _SaveListSelection(self, listBox):
|
||||
"""Save selected item ID from a list box."""
|
||||
if listBox:
|
||||
return listBox.GetSelectedItem()
|
||||
return None
|
||||
|
||||
def _RestoreListSelection(self, listBox, selectedID):
|
||||
"""Restore selected item in a list box by finding its position."""
|
||||
if listBox and selectedID is not None:
|
||||
# Find position for the saved ID
|
||||
for position, itemID in listBox.keyDict.items():
|
||||
if itemID == selectedID:
|
||||
listBox.SelectItem(position)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _SaveVisibility(self, element):
|
||||
"""Save visibility state of an element."""
|
||||
if element:
|
||||
return element.IsShow()
|
||||
return False
|
||||
|
||||
def _RestoreVisibility(self, element, wasVisible):
|
||||
"""Restore visibility state of an element."""
|
||||
if element:
|
||||
if wasVisible:
|
||||
element.Show()
|
||||
else:
|
||||
element.Hide()
|
||||
|
||||
def _SaveTextLineText(self, textLine):
|
||||
"""Save text from a text line."""
|
||||
if textLine:
|
||||
return textLine.GetText()
|
||||
return ""
|
||||
|
||||
def _RestoreTextLineText(self, textLine, text):
|
||||
"""Restore text to a text line."""
|
||||
if textLine and text:
|
||||
textLine.SetText(text)
|
||||
|
||||
def SaveLocaleCode(self, localeCode):
|
||||
"""
|
||||
Save locale code to config/locale.cfg file.
|
||||
|
||||
Args:
|
||||
localeCode: Two-letter locale code (e.g., "en", "ro", "de")
|
||||
|
||||
Returns:
|
||||
True if saved successfully, False otherwise
|
||||
"""
|
||||
try:
|
||||
import os
|
||||
if not os.path.exists("config"):
|
||||
os.makedirs("config")
|
||||
with open("config/locale.cfg", "w") as f:
|
||||
f.write(localeCode)
|
||||
return True
|
||||
except Exception as e:
|
||||
dbg.TraceError("Failed to save locale config: %s" % str(e))
|
||||
return False
|
||||
|
||||
def ReloadWithNewLocale(self, newLocaleCode, scriptPath="uiscript/LoginWindow.py"):
|
||||
"""
|
||||
Hot-reload the UI with a new locale.
|
||||
|
||||
This is the main entry point for locale changes. It handles the complete
|
||||
reload process:
|
||||
1. Save locale code to config file
|
||||
2. Call C++ to reload locale data and clear Python module cache
|
||||
3. Reimport Python locale modules
|
||||
4. Save current UI state
|
||||
5. Recreate UI
|
||||
6. Restore UI state
|
||||
|
||||
Args:
|
||||
newLocaleCode: The new locale code to switch to
|
||||
scriptPath: Path to the UI script file to reload (default: LoginWindow.py)
|
||||
|
||||
Returns:
|
||||
True if reload succeeded, False otherwise
|
||||
"""
|
||||
try:
|
||||
dbg.TraceError("=== Starting Locale Hot-Reload to '%s' ===" % newLocaleCode)
|
||||
|
||||
# Step 1: Save locale code to file
|
||||
if not self.SaveLocaleCode(newLocaleCode):
|
||||
dbg.TraceError("Failed to save locale code")
|
||||
return False
|
||||
|
||||
# Step 2: Call C++ comprehensive reload
|
||||
# This does: LoadConfig() → LoadLocaleData() → Clear Python module cache
|
||||
reloadSuccess = app.ReloadLocale()
|
||||
if not reloadSuccess:
|
||||
dbg.TraceError("C++ ReloadLocale() failed")
|
||||
return False
|
||||
|
||||
# Step 3: Reimport Python locale modules
|
||||
self._ReimportLocaleModules()
|
||||
|
||||
# Step 4: Save current UI state
|
||||
savedState = self._SaveWindowState()
|
||||
|
||||
# Step 5: Recreate UI
|
||||
self._RecreateUI(scriptPath)
|
||||
|
||||
# Step 6: Restore UI state
|
||||
self._RestoreWindowState(savedState)
|
||||
|
||||
dbg.TraceError("=== Locale Hot-Reload Complete ===")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
dbg.TraceError("Error in ReloadWithNewLocale: %s" % str(e))
|
||||
import exception
|
||||
exception.Abort("ReloadWithNewLocale")
|
||||
return False
|
||||
|
||||
def _ReimportLocaleModules(self):
|
||||
"""Force reimport of locale modules after C++ cleared the cache."""
|
||||
dbg.TraceError("Reimporting locale modules...")
|
||||
|
||||
# Import fresh modules - C++ already deleted them from sys.modules
|
||||
localeInfo = __import__('localeInfo')
|
||||
uiScriptLocale = __import__('uiScriptLocale')
|
||||
|
||||
# Update sys.modules references
|
||||
sys.modules['localeInfo'] = localeInfo
|
||||
sys.modules['uiScriptLocale'] = uiScriptLocale
|
||||
|
||||
# CRITICAL: Update the window module's globals, not ours!
|
||||
# Get the window's module (e.g., intrologin module)
|
||||
windowModule = sys.modules.get(self.window.__module__)
|
||||
if windowModule:
|
||||
dbg.TraceError("Updating globals in module: %s" % self.window.__module__)
|
||||
windowModule.localeInfo = localeInfo
|
||||
windowModule.uiScriptLocale = uiScriptLocale
|
||||
else:
|
||||
dbg.TraceError("WARNING: Could not find window module: %s" % self.window.__module__)
|
||||
|
||||
# Also update this module's globals for safety
|
||||
globals()['localeInfo'] = localeInfo
|
||||
globals()['uiScriptLocale'] = uiScriptLocale
|
||||
|
||||
dbg.TraceError("Locale modules reimported successfully")
|
||||
|
||||
def _SaveWindowState(self):
|
||||
"""
|
||||
Save the current state of all registered UI elements.
|
||||
|
||||
Returns:
|
||||
Dictionary containing saved state data
|
||||
"""
|
||||
state = {
|
||||
"elements": {},
|
||||
"visibleBoard": None,
|
||||
}
|
||||
|
||||
# Save state of registered elements
|
||||
for elementName, (saveFunc, _) in self.stateHandlers.items():
|
||||
if hasattr(self.window, elementName):
|
||||
element = getattr(self.window, elementName)
|
||||
if element:
|
||||
state["elements"][elementName] = saveFunc(element)
|
||||
|
||||
# Determine which board is currently visible
|
||||
for boardName in ["loginBoard", "serverBoard", "connectBoard"]:
|
||||
if hasattr(self.window, boardName):
|
||||
board = getattr(self.window, boardName)
|
||||
if board and board.IsShow():
|
||||
state["visibleBoard"] = boardName
|
||||
break
|
||||
|
||||
dbg.TraceError("Saved window state: visibleBoard=%s" % state["visibleBoard"])
|
||||
return state
|
||||
|
||||
def _RecreateUI(self, scriptPath):
|
||||
"""
|
||||
Recreate the UI by clearing and reloading the script.
|
||||
|
||||
Args:
|
||||
scriptPath: Path to the UI script file
|
||||
"""
|
||||
dbg.TraceError("Recreating UI from script: %s" % scriptPath)
|
||||
|
||||
# Completely destroy the locale selector - we'll recreate it fresh
|
||||
if hasattr(self.window, 'localeSelector') and self.window.localeSelector:
|
||||
dbg.TraceError("Destroying old locale selector before UI recreation")
|
||||
self.window.localeSelector.Destroy()
|
||||
self.window.localeSelector = None
|
||||
|
||||
# Clear existing UI elements
|
||||
if hasattr(self.window, 'ClearDictionary'):
|
||||
self.window.ClearDictionary()
|
||||
|
||||
# Reload the UI script file with new locale strings
|
||||
# This will create a fresh locale selector through __LoadScript
|
||||
if hasattr(self.window, '_LoginWindow__LoadScript'):
|
||||
# Private method name mangling for LoginWindow
|
||||
self.window._LoginWindow__LoadScript(scriptPath)
|
||||
elif hasattr(self.window, 'LoadScript'):
|
||||
self.window.LoadScript(scriptPath)
|
||||
|
||||
# __LoadScript should have created a new locale selector
|
||||
# Make sure it's visible
|
||||
if hasattr(self.window, 'localeSelector') and self.window.localeSelector:
|
||||
dbg.TraceError("Locale selector created by __LoadScript, ensuring visibility")
|
||||
self.window.localeSelector.Show()
|
||||
self.window.localeSelector.SetTop()
|
||||
else:
|
||||
# If __LoadScript didn't create it, create it manually
|
||||
dbg.TraceError("Creating locale selector manually")
|
||||
from uilocaleselector import LocaleSelector
|
||||
self.window.localeSelector = LocaleSelector()
|
||||
self.window.localeSelector.Create(self.window)
|
||||
# Set the event handler to call back to the window's method
|
||||
if hasattr(self.window, '_LoginWindow__OnLocaleChanged'):
|
||||
import ui
|
||||
self.window.localeSelector.SetLocaleChangedEvent(ui.__mem_func__(self.window._LoginWindow__OnLocaleChanged))
|
||||
self.window.localeSelector.Show()
|
||||
self.window.localeSelector.SetTop()
|
||||
|
||||
# Hide all boards to reset state
|
||||
for boardName in ["loginBoard", "serverBoard", "connectBoard"]:
|
||||
if hasattr(self.window, boardName):
|
||||
board = getattr(self.window, boardName)
|
||||
if board:
|
||||
board.Hide()
|
||||
|
||||
# Hide virtual keyboard
|
||||
if hasattr(self.window, "virtualKeyboard"):
|
||||
vk = getattr(self.window, "virtualKeyboard")
|
||||
if vk:
|
||||
vk.Hide()
|
||||
|
||||
def _RestoreWindowState(self, state):
|
||||
"""
|
||||
Restore the saved UI state.
|
||||
|
||||
Args:
|
||||
state: Dictionary containing saved state data
|
||||
"""
|
||||
dbg.TraceError("Restoring window state...")
|
||||
|
||||
# Restore element states
|
||||
for elementName, savedData in state["elements"].items():
|
||||
if elementName in self.stateHandlers:
|
||||
_, restoreFunc = self.stateHandlers[elementName]
|
||||
if hasattr(self.window, elementName):
|
||||
element = getattr(self.window, elementName)
|
||||
if element:
|
||||
restoreFunc(element, savedData)
|
||||
|
||||
# Rebuild locale-dependent dictionaries (like loginFailureMsgDict)
|
||||
self._RebuildLocaleDictionaries()
|
||||
|
||||
# Restore visible board
|
||||
visibleBoard = state.get("visibleBoard")
|
||||
if visibleBoard:
|
||||
self._RestoreBoardVisibility(visibleBoard, state)
|
||||
|
||||
# CRITICAL: Make sure locale selector is visible and on top after everything is restored
|
||||
if hasattr(self.window, 'localeSelector') and self.window.localeSelector:
|
||||
dbg.TraceError("Final check: ensuring locale selector is visible")
|
||||
self.window.localeSelector.Show()
|
||||
self.window.localeSelector.SetTop()
|
||||
|
||||
def _RebuildLocaleDictionaries(self):
|
||||
"""Rebuild any dictionaries that depend on localeInfo strings."""
|
||||
# Check if this is a LoginWindow with loginFailureMsgDict
|
||||
if hasattr(self.window, 'loginFailureMsgDict'):
|
||||
import localeInfo
|
||||
dbg.TraceError("Rebuilding loginFailureMsgDict with new locale strings")
|
||||
self.window.loginFailureMsgDict = {
|
||||
"ALREADY" : localeInfo.LOGIN_FAILURE_ALREAY,
|
||||
"NOID" : localeInfo.LOGIN_FAILURE_NOT_EXIST_ID,
|
||||
"WRONGPWD" : localeInfo.LOGIN_FAILURE_WRONG_PASSWORD,
|
||||
"FULL" : localeInfo.LOGIN_FAILURE_TOO_MANY_USER,
|
||||
"SHUTDOWN" : localeInfo.LOGIN_FAILURE_SHUTDOWN,
|
||||
"REPAIR" : localeInfo.LOGIN_FAILURE_REPAIR_ID,
|
||||
"BLOCK" : localeInfo.LOGIN_FAILURE_BLOCK_ID,
|
||||
"BESAMEKEY" : localeInfo.LOGIN_FAILURE_BE_SAME_KEY,
|
||||
"NOTAVAIL" : localeInfo.LOGIN_FAILURE_NOT_AVAIL,
|
||||
"NOBILL" : localeInfo.LOGIN_FAILURE_NOBILL,
|
||||
"BLKLOGIN" : localeInfo.LOGIN_FAILURE_BLOCK_LOGIN,
|
||||
"WEBBLK" : localeInfo.LOGIN_FAILURE_WEB_BLOCK,
|
||||
"BADSCLID" : localeInfo.LOGIN_FAILURE_WRONG_SOCIALID,
|
||||
"AGELIMIT" : localeInfo.LOGIN_FAILURE_SHUTDOWN_TIME,
|
||||
}
|
||||
|
||||
if hasattr(self.window, 'loginFailureFuncDict'):
|
||||
self.window.loginFailureFuncDict = {
|
||||
"WRONGPWD" : self.window._LoginWindow__DisconnectAndInputPassword if hasattr(self.window, '_LoginWindow__DisconnectAndInputPassword') else None,
|
||||
"QUIT" : __import__('app').Exit,
|
||||
}
|
||||
|
||||
def _RestoreBoardVisibility(self, boardName, state):
|
||||
"""
|
||||
Restore the visibility of a specific board and its state.
|
||||
|
||||
Args:
|
||||
boardName: Name of the board to show ("loginBoard", "serverBoard", "connectBoard")
|
||||
state: Full saved state dictionary
|
||||
"""
|
||||
dbg.TraceError("Restoring board visibility: %s" % boardName)
|
||||
|
||||
if boardName == "loginBoard":
|
||||
self._RestoreLoginBoard()
|
||||
elif boardName == "serverBoard":
|
||||
self._RestoreServerBoard(state)
|
||||
elif boardName == "connectBoard":
|
||||
self._RestoreConnectBoard()
|
||||
|
||||
def _RestoreLoginBoard(self):
|
||||
"""Show and configure the login board."""
|
||||
if hasattr(self.window, "loginBoard"):
|
||||
self.window.loginBoard.Show()
|
||||
|
||||
def _RestoreServerBoard(self, state):
|
||||
"""Show and configure the server board with saved selections."""
|
||||
if not hasattr(self.window, "serverBoard"):
|
||||
return
|
||||
|
||||
self.window.serverBoard.Show()
|
||||
|
||||
# Refresh server list first
|
||||
if hasattr(self.window, '_LoginWindow__RefreshServerList'):
|
||||
self.window._LoginWindow__RefreshServerList()
|
||||
|
||||
# Now restore server selection AFTER the list is refreshed
|
||||
savedServerID = state["elements"].get("serverList")
|
||||
if savedServerID is not None and hasattr(self.window, 'serverList') and self.window.serverList:
|
||||
# Find the position index for the saved server ID
|
||||
# keyDict maps position -> server ID
|
||||
for position, serverID in self.window.serverList.keyDict.items():
|
||||
if serverID == savedServerID:
|
||||
# SelectItem expects position index, not server ID
|
||||
self.window.serverList.SelectItem(position)
|
||||
|
||||
# Refresh channel list for the selected server
|
||||
if hasattr(self.window, '_LoginWindow__RequestServerStateList'):
|
||||
self.window._LoginWindow__RequestServerStateList()
|
||||
if hasattr(self.window, '_LoginWindow__RefreshServerStateList'):
|
||||
self.window._LoginWindow__RefreshServerStateList()
|
||||
|
||||
# Restore channel selection AFTER channel list is refreshed
|
||||
savedChannelID = state["elements"].get("channelList")
|
||||
if savedChannelID is not None and hasattr(self.window, 'channelList') and self.window.channelList:
|
||||
# Find the position index for the saved channel ID
|
||||
for channelPos, channelID in self.window.channelList.keyDict.items():
|
||||
if channelID == savedChannelID:
|
||||
self.window.channelList.SelectItem(channelPos)
|
||||
break
|
||||
break
|
||||
|
||||
def _RestoreConnectBoard(self):
|
||||
"""Show and configure the connect board."""
|
||||
if not hasattr(self.window, "connectBoard"):
|
||||
return
|
||||
|
||||
# Connect board overlays login board
|
||||
if hasattr(self.window, "loginBoard"):
|
||||
self.window.loginBoard.Show()
|
||||
|
||||
self.window.connectBoard.Show()
|
||||
|
||||
# Explicitly show connect board children (they may not auto-show)
|
||||
if hasattr(self.window, "selectConnectButton"):
|
||||
btn = getattr(self.window, "selectConnectButton")
|
||||
if btn:
|
||||
btn.Show()
|
||||
|
||||
if hasattr(self.window, "serverInfo"):
|
||||
info = getattr(self.window, "serverInfo")
|
||||
if info:
|
||||
info.Show()
|
||||
191
assets/root/uilocaleselector.py
Normal file
191
assets/root/uilocaleselector.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Locale Selector UI Component
|
||||
=============================
|
||||
|
||||
A reusable UI component for selecting and changing the client language.
|
||||
Can be added to any window (login, game settings, etc.).
|
||||
|
||||
Usage:
|
||||
from uilocaleselector import LocaleSelector
|
||||
|
||||
# In your window class:
|
||||
self.localeSelector = LocaleSelector()
|
||||
self.localeSelector.Create(self)
|
||||
self.localeSelector.SetLocaleChangedEvent(ui.__mem_func__(self.__OnLocaleChanged))
|
||||
|
||||
# Implement the callback:
|
||||
def __OnLocaleChanged(self, newLocaleCode):
|
||||
# Handle UI recreation here
|
||||
pass
|
||||
"""
|
||||
|
||||
import ui
|
||||
import uiCommon
|
||||
import localeInfo
|
||||
import wndMgr
|
||||
|
||||
|
||||
# Available locales configuration
|
||||
AVAILABLE_LOCALES = [
|
||||
{"code": "ae", "name": "Arabic", "flag": "ae"},
|
||||
{"code": "en", "name": "English", "flag": "en"},
|
||||
{"code": "cz", "name": "Čeština", "flag": "cz"},
|
||||
{"code": "de", "name": "Deutsch", "flag": "de"},
|
||||
{"code": "dk", "name": "Dansk", "flag": "dk"},
|
||||
{"code": "es", "name": "Español", "flag": "es"},
|
||||
{"code": "fr", "name": "Français", "flag": "fr"},
|
||||
{"code": "gr", "name": "Ελληνικά", "flag": "gr"},
|
||||
{"code": "hu", "name": "Magyar", "flag": "hu"},
|
||||
{"code": "it", "name": "Italiano", "flag": "it"},
|
||||
{"code": "nl", "name": "Nederlands", "flag": "nl"},
|
||||
{"code": "pl", "name": "Polski", "flag": "pl"},
|
||||
{"code": "pt", "name": "Português", "flag": "pt"},
|
||||
{"code": "ro", "name": "Română", "flag": "ro"},
|
||||
{"code": "ru", "name": "Русский", "flag": "ru"},
|
||||
{"code": "tr", "name": "Türkçe", "flag": "tr"},
|
||||
]
|
||||
|
||||
# Flag image path template
|
||||
FLAG_IMAGE_PATH = "d:/ymir work/ui/intro/login/server_flag_%s.sub"
|
||||
|
||||
|
||||
class LocaleSelector(ui.Window):
|
||||
"""
|
||||
UI component for selecting and changing client language.
|
||||
|
||||
Features:
|
||||
- Displays flag buttons for all available locales
|
||||
- Shows confirmation dialog before changing
|
||||
- Triggers callback when locale is confirmed
|
||||
- Self-contained and reusable
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
ui.Window.__init__(self)
|
||||
self.background = None
|
||||
self.flagButtons = []
|
||||
self.confirmDialog = None
|
||||
self.selectedLocaleCode = None
|
||||
self.eventLocaleChanged = None
|
||||
|
||||
def __del__(self):
|
||||
ui.Window.__del__(self)
|
||||
|
||||
def Destroy(self):
|
||||
"""Clean up resources when destroying the selector."""
|
||||
self.eventLocaleChanged = None
|
||||
self.selectedLocaleCode = None
|
||||
|
||||
if self.confirmDialog:
|
||||
self.confirmDialog.Close()
|
||||
self.confirmDialog = None
|
||||
|
||||
for btn in self.flagButtons:
|
||||
btn.Hide()
|
||||
btn = None
|
||||
self.flagButtons = []
|
||||
|
||||
if self.background:
|
||||
self.background.Hide()
|
||||
self.background = None
|
||||
|
||||
def Create(self, parent):
|
||||
"""
|
||||
Create and display the locale selector UI.
|
||||
|
||||
Args:
|
||||
parent: The parent window to attach to
|
||||
"""
|
||||
self.SetParent(parent)
|
||||
self.SetSize(wndMgr.GetScreenWidth(), 35)
|
||||
self.SetPosition(0, 20)
|
||||
|
||||
# Create background board
|
||||
self.background = ui.ThinBoard()
|
||||
self.background.SetParent(self)
|
||||
self.background.SetSize(wndMgr.GetScreenWidth(), 35)
|
||||
self.background.SetPosition(0, 0)
|
||||
self.background.Show()
|
||||
|
||||
# Create flag buttons
|
||||
self._CreateFlagButtons()
|
||||
|
||||
self.Show()
|
||||
|
||||
def _CreateFlagButtons(self):
|
||||
"""Create flag buttons for all available locales."""
|
||||
localeCount = len(AVAILABLE_LOCALES)
|
||||
if localeCount == 0:
|
||||
return
|
||||
|
||||
buttonSpacing = wndMgr.GetScreenWidth() / localeCount
|
||||
xPosition = 0
|
||||
|
||||
for locale in AVAILABLE_LOCALES:
|
||||
flagPath = FLAG_IMAGE_PATH % locale["flag"]
|
||||
|
||||
button = ui.Button()
|
||||
button.SetParent(self.background)
|
||||
button.SetPosition(xPosition + 15, 10)
|
||||
button.SetUpVisual(flagPath)
|
||||
button.SetOverVisual(flagPath)
|
||||
button.SetDownVisual(flagPath)
|
||||
button.SetToolTipText(locale["name"])
|
||||
button.SetEvent(ui.__mem_func__(self._OnClickFlag), locale["code"])
|
||||
button.Show()
|
||||
|
||||
self.flagButtons.append(button)
|
||||
xPosition += buttonSpacing
|
||||
|
||||
def _OnClickFlag(self, localeCode):
|
||||
"""
|
||||
Handle flag button click - show confirmation dialog.
|
||||
|
||||
Args:
|
||||
localeCode: The locale code that was clicked
|
||||
"""
|
||||
self.selectedLocaleCode = localeCode
|
||||
|
||||
# Get locale name for display
|
||||
localeName = "Unknown"
|
||||
for locale in AVAILABLE_LOCALES:
|
||||
if locale["code"] == localeCode:
|
||||
localeName = locale["name"]
|
||||
break
|
||||
|
||||
# Show confirmation dialog
|
||||
if not self.confirmDialog:
|
||||
self.confirmDialog = uiCommon.QuestionDialog()
|
||||
|
||||
self.confirmDialog.SetText(localeInfo.LOCALE_CHANGE_CONFIRM % localeName)
|
||||
self.confirmDialog.SetAcceptEvent(ui.__mem_func__(self._OnConfirmLocaleChange))
|
||||
self.confirmDialog.SetCancelEvent(ui.__mem_func__(self._OnCancelLocaleChange))
|
||||
self.confirmDialog.Open()
|
||||
|
||||
def _OnConfirmLocaleChange(self):
|
||||
"""User confirmed locale change - trigger the callback."""
|
||||
if self.confirmDialog:
|
||||
self.confirmDialog.Close()
|
||||
|
||||
if not self.selectedLocaleCode:
|
||||
return
|
||||
|
||||
# Notify parent window to handle the locale change
|
||||
if self.eventLocaleChanged:
|
||||
self.eventLocaleChanged(self.selectedLocaleCode)
|
||||
|
||||
def _OnCancelLocaleChange(self):
|
||||
"""User cancelled locale change."""
|
||||
if self.confirmDialog:
|
||||
self.confirmDialog.Close()
|
||||
self.selectedLocaleCode = None
|
||||
|
||||
def SetLocaleChangedEvent(self, event):
|
||||
"""
|
||||
Set callback function to be called when locale is confirmed.
|
||||
|
||||
Args:
|
||||
event: Callback function(localeCode) to handle locale change
|
||||
"""
|
||||
self.eventLocaleChanged = event
|
||||
Reference in New Issue
Block a user